mirror of
https://github.com/waynebian01/Shigure.git
synced 2026-09-03 07:15:24 +08:00
257 lines
8.5 KiB
C#
257 lines
8.5 KiB
C#
using System.Diagnostics;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace Shigure;
|
|
|
|
internal static class RandomizedExecutableLauncher
|
|
{
|
|
private const string RelaunchedEnvironmentKey = "SHIGURE_RANDOMIZED_PROCESS";
|
|
private const string RandomSuffixMarker = "SHIGURE-RANDOM-HASH-MARKER";
|
|
private const string TemporaryDirectoryName = "tmp";
|
|
private const int RandomNameLength = 16;
|
|
private const int RandomHashBytes = 64;
|
|
private static readonly string[] RuntimeFileExtensions = [".dll", ".pdb"];
|
|
private static readonly byte[] RandomSuffixMarkerBytes = System.Text.Encoding.UTF8.GetBytes(RandomSuffixMarker);
|
|
private static readonly char[] FileNameChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();
|
|
|
|
public static RandomizedRelaunchResult TryRelaunch(string[] args)
|
|
{
|
|
if (Environment.GetEnvironmentVariable(RelaunchedEnvironmentKey) == "1")
|
|
{
|
|
return RandomizedRelaunchResult.AlreadyRelaunched;
|
|
}
|
|
|
|
var currentExecutable = Environment.ProcessPath;
|
|
if (string.IsNullOrWhiteSpace(currentExecutable) || !File.Exists(currentExecutable))
|
|
{
|
|
return RandomizedRelaunchResult.Failed;
|
|
}
|
|
|
|
var extension = Path.GetExtension(currentExecutable);
|
|
if (!string.Equals(extension, ".exe", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return RandomizedRelaunchResult.Failed;
|
|
}
|
|
|
|
try
|
|
{
|
|
CleanupTemporaryRoot(currentExecutable);
|
|
var randomizedExecutable = CreateRandomizedExecutable(currentExecutable);
|
|
var randomizedDirectory = Path.GetDirectoryName(randomizedExecutable);
|
|
var startInfo = new ProcessStartInfo(randomizedExecutable)
|
|
{
|
|
UseShellExecute = false,
|
|
WorkingDirectory = randomizedDirectory ?? Environment.CurrentDirectory
|
|
};
|
|
startInfo.Environment[RelaunchedEnvironmentKey] = "1";
|
|
startInfo.Environment[AppPaths.OriginalBaseDirectoryEnvironmentKey] = Path.GetDirectoryName(currentExecutable) ?? AppContext.BaseDirectory;
|
|
startInfo.Environment[AppPaths.RandomizedDisplayNameEnvironmentKey] = Path.GetFileNameWithoutExtension(randomizedExecutable);
|
|
foreach (var arg in args)
|
|
{
|
|
startInfo.ArgumentList.Add(arg);
|
|
}
|
|
|
|
return Process.Start(startInfo) is null
|
|
? RandomizedRelaunchResult.Failed
|
|
: RandomizedRelaunchResult.Started;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show(
|
|
$"无法创建随机运行文件: {ex.Message}",
|
|
"Shigure",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Error);
|
|
return RandomizedRelaunchResult.Failed;
|
|
}
|
|
}
|
|
|
|
private static void CleanupTemporaryRoot(string sourcePath)
|
|
{
|
|
var tempRoot = GetTemporaryRoot(sourcePath);
|
|
if (!Directory.Exists(tempRoot))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
Directory.Delete(tempRoot, recursive: true);
|
|
return;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// A previous randomized run may still be active; clean what is safe below.
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
// A previous randomized run may still be active; clean what is safe below.
|
|
}
|
|
|
|
foreach (var directory in Directory.EnumerateDirectories(tempRoot))
|
|
{
|
|
if (!IsRandomizedDirectoryName(directory) || !ContainsRandomizedExecutable(directory))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
Directory.Delete(directory, recursive: true);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// A previous randomized run may still be active.
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
// Leave locked or protected files alone.
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string CreateRandomizedExecutable(string sourcePath)
|
|
{
|
|
var sourceDirectory = Path.GetDirectoryName(sourcePath) ?? AppContext.BaseDirectory;
|
|
var tempRoot = GetTemporaryRoot(sourcePath);
|
|
Directory.CreateDirectory(tempRoot);
|
|
|
|
string runDirectory;
|
|
do
|
|
{
|
|
runDirectory = Path.Combine(tempRoot, CreateRandomFileName());
|
|
}
|
|
while (Directory.Exists(runDirectory));
|
|
|
|
Directory.CreateDirectory(runDirectory);
|
|
CopyRuntimeFiles(sourceDirectory, runDirectory);
|
|
|
|
string targetPath;
|
|
do
|
|
{
|
|
targetPath = Path.Combine(runDirectory, $"{CreateRandomFileName()}.exe");
|
|
}
|
|
while (File.Exists(targetPath));
|
|
|
|
File.Copy(sourcePath, targetPath, overwrite: false);
|
|
AppendRandomHashMarker(targetPath);
|
|
return targetPath;
|
|
}
|
|
|
|
private static string GetTemporaryRoot(string sourcePath)
|
|
{
|
|
var directory = Path.GetDirectoryName(sourcePath) ?? AppContext.BaseDirectory;
|
|
return Path.Combine(directory, TemporaryDirectoryName);
|
|
}
|
|
|
|
private static void CopyRuntimeFiles(string sourceDirectory, string targetDirectory)
|
|
{
|
|
foreach (var file in Directory.EnumerateFiles(sourceDirectory, "*", SearchOption.TopDirectoryOnly))
|
|
{
|
|
if (!ShouldCopyRuntimeFile(file))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
File.Copy(file, Path.Combine(targetDirectory, Path.GetFileName(file)), overwrite: true);
|
|
}
|
|
|
|
}
|
|
|
|
private static bool ShouldCopyRuntimeFile(string path)
|
|
{
|
|
var fileName = Path.GetFileName(path);
|
|
var extension = Path.GetExtension(path);
|
|
return RuntimeFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
|
|
|| fileName.EndsWith(".deps.json", StringComparison.OrdinalIgnoreCase)
|
|
|| fileName.EndsWith(".runtimeconfig.json", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static string CreateRandomFileName()
|
|
{
|
|
Span<char> buffer = stackalloc char[RandomNameLength];
|
|
for (var i = 0; i < buffer.Length; i++)
|
|
{
|
|
buffer[i] = FileNameChars[RandomNumberGenerator.GetInt32(FileNameChars.Length)];
|
|
}
|
|
|
|
return new string(buffer);
|
|
}
|
|
|
|
private static bool IsRandomizedExecutableName(string path)
|
|
{
|
|
var name = Path.GetFileNameWithoutExtension(path);
|
|
return name.Length == RandomNameLength
|
|
&& name.All(char.IsAsciiLetterOrDigit);
|
|
}
|
|
|
|
private static bool IsRandomizedDirectoryName(string path)
|
|
{
|
|
var name = Path.GetFileName(path);
|
|
return name.Length == RandomNameLength
|
|
&& name.All(char.IsAsciiLetterOrDigit);
|
|
}
|
|
|
|
private static bool ContainsRandomizedExecutable(string directory)
|
|
{
|
|
try
|
|
{
|
|
return Directory.EnumerateFiles(directory, "*.exe", SearchOption.TopDirectoryOnly)
|
|
.Any(file => IsRandomizedExecutableName(file) && HasRandomHashMarker(file));
|
|
}
|
|
catch (IOException)
|
|
{
|
|
return false;
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static bool HasRandomHashMarker(string path)
|
|
{
|
|
try
|
|
{
|
|
var markerLength = RandomSuffixMarkerBytes.Length;
|
|
var minimumLength = markerLength + RandomHashBytes;
|
|
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
if (stream.Length < minimumLength)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
stream.Position = stream.Length - minimumLength;
|
|
Span<byte> markerBuffer = stackalloc byte[markerLength];
|
|
return stream.Read(markerBuffer) == markerLength
|
|
&& markerBuffer.SequenceEqual(RandomSuffixMarkerBytes);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
return false;
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static void AppendRandomHashMarker(string path)
|
|
{
|
|
Span<byte> randomBytes = stackalloc byte[RandomHashBytes];
|
|
RandomNumberGenerator.Fill(randomBytes);
|
|
|
|
using var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);
|
|
using var writer = new BinaryWriter(stream);
|
|
writer.Write(RandomSuffixMarker);
|
|
writer.Write(randomBytes);
|
|
}
|
|
}
|
|
|
|
internal enum RandomizedRelaunchResult
|
|
{
|
|
Started,
|
|
AlreadyRelaunched,
|
|
Failed
|
|
}
|