refactor(Injector): switch to file-scoped namespaces

This commit is contained in:
goat 2021-11-17 19:46:33 +01:00
parent b5f34c3199
commit f30ebe5166
No known key found for this signature in database
GPG key ID: 7773BB5B43BA52E5
3 changed files with 1288 additions and 1291 deletions

View file

@ -16,307 +16,306 @@ using Serilog.Events;
using static Dalamud.Injector.NativeFunctions; using static Dalamud.Injector.NativeFunctions;
namespace Dalamud.Injector namespace Dalamud.Injector;
/// <summary>
/// Entrypoint to the program.
/// </summary>
public sealed class EntryPoint
{ {
/// <summary> /// <summary>
/// Entrypoint to the program. /// A delegate used during initialization of the CLR from Dalamud.Injector.Boot.
/// </summary> /// </summary>
public sealed class EntryPoint /// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">char** string arguments.</param>
public delegate void MainDelegate(int argc, IntPtr argvPtr);
/// <summary>
/// Start the Dalamud injector.
/// </summary>
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">byte** string arguments.</param>
public static void Main(int argc, IntPtr argvPtr)
{ {
/// <summary> InitUnhandledException();
/// A delegate used during initialization of the CLR from Dalamud.Injector.Boot. InitLogging();
/// </summary>
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">char** string arguments.</param>
public delegate void MainDelegate(int argc, IntPtr argvPtr);
/// <summary> var args = new string[argc];
/// Start the Dalamud injector.
/// </summary> unsafe
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">byte** string arguments.</param>
public static void Main(int argc, IntPtr argvPtr)
{ {
InitUnhandledException(); var argv = (IntPtr*)argvPtr;
InitLogging(); for (var i = 0; i < argc; i++)
var args = new string[argc];
unsafe
{ {
var argv = (IntPtr*)argvPtr; args[i] = Marshal.PtrToStringUni(argv[i]);
for (var i = 0; i < argc; i++)
{
args[i] = Marshal.PtrToStringUni(argv[i]);
}
} }
var cwd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
if (cwd.FullName != Directory.GetCurrentDirectory())
{
Log.Debug($"Changing cwd to {cwd}");
Directory.SetCurrentDirectory(cwd.FullName);
}
var process = GetProcess(args.ElementAtOrDefault(1));
var startInfo = GetStartInfo(args.ElementAtOrDefault(2), process);
// TODO: XL does not set this!!! we need to keep this line here for now, otherwise we crash in the Dalamud entrypoint
startInfo.WorkingDirectory = Directory.GetCurrentDirectory();
// This seems to help with the STATUS_INTERNAL_ERROR condition
Thread.Sleep(1000);
Inject(process, startInfo);
Thread.Sleep(1000);
} }
private static void InitUnhandledException() var cwd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
if (cwd.FullName != Directory.GetCurrentDirectory())
{ {
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) => Log.Debug($"Changing cwd to {cwd}");
Directory.SetCurrentDirectory(cwd.FullName);
}
var process = GetProcess(args.ElementAtOrDefault(1));
var startInfo = GetStartInfo(args.ElementAtOrDefault(2), process);
// TODO: XL does not set this!!! we need to keep this line here for now, otherwise we crash in the Dalamud entrypoint
startInfo.WorkingDirectory = Directory.GetCurrentDirectory();
// This seems to help with the STATUS_INTERNAL_ERROR condition
Thread.Sleep(1000);
Inject(process, startInfo);
Thread.Sleep(1000);
}
private static void InitUnhandledException()
{
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
{
if (Log.Logger == null)
{ {
if (Log.Logger == null) Console.WriteLine($"A fatal error has occurred: {eventArgs.ExceptionObject}");
}
else
{
var exObj = eventArgs.ExceptionObject;
if (exObj is Exception ex)
{ {
Console.WriteLine($"A fatal error has occurred: {eventArgs.ExceptionObject}"); Log.Error(ex, "A fatal error has occurred.");
} }
else else
{ {
var exObj = eventArgs.ExceptionObject; Log.Error($"A fatal error has occurred: {eventArgs.ExceptionObject}");
if (exObj is Exception ex)
{
Log.Error(ex, "A fatal error has occurred.");
}
else
{
Log.Error($"A fatal error has occurred: {eventArgs.ExceptionObject}");
}
} }
}
#if DEBUG #if DEBUG
var caption = "Debug Error"; var caption = "Debug Error";
var message = var message =
$"Couldn't inject.\nMake sure that Dalamud was not injected into your target process " + $"Couldn't inject.\nMake sure that Dalamud was not injected into your target process " +
$"as a release build before and that the target process can be accessed with VM_WRITE permissions.\n\n" + $"as a release build before and that the target process can be accessed with VM_WRITE permissions.\n\n" +
$"{eventArgs.ExceptionObject}"; $"{eventArgs.ExceptionObject}";
#else #else
var caption = "XIVLauncher Error"; var caption = "XIVLauncher Error";
var message = var message =
"Failed to inject the XIVLauncher in-game addon.\nPlease try restarting your game and your PC.\n" + "Failed to inject the XIVLauncher in-game addon.\nPlease try restarting your game and your PC.\n" +
"If this keeps happening, please report this error."; "If this keeps happening, please report this error.";
#endif #endif
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok); _ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
Environment.Exit(0); Environment.Exit(0);
}; };
} }
private static void InitLogging() private static void InitLogging()
{ {
var baseDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var baseDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
#if DEBUG #if DEBUG
var logPath = Path.Combine(baseDirectory, "dalamud.injector.log"); var logPath = Path.Combine(baseDirectory, "dalamud.injector.log");
#else #else
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.injector.log"); var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.injector.log");
#endif #endif
var levelSwitch = new LoggingLevelSwitch(); var levelSwitch = new LoggingLevelSwitch();
#if DEBUG #if DEBUG
levelSwitch.MinimumLevel = LogEventLevel.Verbose; levelSwitch.MinimumLevel = LogEventLevel.Verbose;
#else #else
levelSwitch.MinimumLevel = LogEventLevel.Information; levelSwitch.MinimumLevel = LogEventLevel.Information;
#endif #endif
CullLogFile(logPath, 1 * 1024 * 1024); CullLogFile(logPath, 1 * 1024 * 1024);
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File(logPath)) .WriteTo.Async(a => a.File(logPath))
.MinimumLevel.ControlledBy(levelSwitch) .MinimumLevel.ControlledBy(levelSwitch)
.CreateLogger(); .CreateLogger();
} }
private static void CullLogFile(string logPath, int cullingFileSize) private static void CullLogFile(string logPath, int cullingFileSize)
{
try
{ {
try var bufferSize = 4096;
{
var bufferSize = 4096;
var logFile = new FileInfo(logPath); var logFile = new FileInfo(logPath);
if (!logFile.Exists) if (!logFile.Exists)
logFile.Create(); logFile.Create();
if (logFile.Length <= cullingFileSize) if (logFile.Length <= cullingFileSize)
return;
var amountToCull = logFile.Length - cullingFileSize;
if (amountToCull < bufferSize)
return;
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite));
reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin);
var read = -1;
var total = 0;
var buffer = new byte[bufferSize];
while (read != 0)
{
read = reader.Read(buffer, 0, buffer.Length);
writer.Write(buffer, 0, read);
total += read;
}
writer.BaseStream.SetLength(total);
}
catch (Exception)
{
/*
var caption = "XIVLauncher Error";
var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
*/
}
}
private static Process GetProcess(string arg)
{
Process process;
var pid = -1;
if (arg != default)
{
pid = int.Parse(arg);
}
switch (pid)
{
case -1:
process = Process.GetProcessesByName("ffxiv_dx11").FirstOrDefault();
if (process == default)
{
throw new Exception("Could not find process");
}
break;
case -2:
var exePath = "C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\ffxiv_dx11.exe";
var exeArgs = new StringBuilder()
.Append("DEV.TestSID=0 DEV.UseSqPack=1 DEV.DataPathType=1 ")
.Append("DEV.LobbyHost01=127.0.0.1 DEV.LobbyPort01=54994 ")
.Append("DEV.LobbyHost02=127.0.0.1 DEV.LobbyPort02=54994 ")
.Append("DEV.LobbyHost03=127.0.0.1 DEV.LobbyPort03=54994 ")
.Append("DEV.LobbyHost04=127.0.0.1 DEV.LobbyPort04=54994 ")
.Append("DEV.LobbyHost05=127.0.0.1 DEV.LobbyPort05=54994 ")
.Append("DEV.LobbyHost06=127.0.0.1 DEV.LobbyPort06=54994 ")
.Append("DEV.LobbyHost07=127.0.0.1 DEV.LobbyPort07=54994 ")
.Append("DEV.LobbyHost08=127.0.0.1 DEV.LobbyPort08=54994 ")
.Append("SYS.Region=0 language=1 version=1.0.0.0 ")
.Append("DEV.MaxEntitledExpansionID=2 DEV.GMServerHost=127.0.0.1 DEV.GameQuitMessageBox=0").ToString();
process = Process.Start(exePath, exeArgs);
Thread.Sleep(1000);
break;
default:
process = Process.GetProcessById(pid);
break;
}
return process;
}
private static DalamudStartInfo GetStartInfo(string arg, Process process)
{
DalamudStartInfo startInfo;
if (arg != default)
{
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(arg)));
}
else
{
var ffxivDir = Path.GetDirectoryName(process.MainModule.FileName);
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
var gameVer = GameVersion.Parse(gameVerStr);
startInfo = new DalamudStartInfo
{
WorkingDirectory = null,
ConfigurationPath = Path.Combine(xivlauncherDir, "dalamudConfig.json"),
PluginDirectory = Path.Combine(xivlauncherDir, "installedPlugins"),
DefaultPluginDirectory = Path.Combine(xivlauncherDir, "devPlugins"),
AssetDirectory = Path.Combine(xivlauncherDir, "dalamudAssets"),
GameVersion = gameVer,
Language = ClientLanguage.English,
OptOutMbCollection = false,
};
Log.Debug(
"Creating a new StartInfo with:\n" +
$" WorkingDirectory: {startInfo.WorkingDirectory}\n" +
$" ConfigurationPath: {startInfo.ConfigurationPath}\n" +
$" PluginDirectory: {startInfo.PluginDirectory}\n" +
$" DefaultPluginDirectory: {startInfo.DefaultPluginDirectory}\n" +
$" AssetDirectory: {startInfo.AssetDirectory}\n" +
$" GameVersion: {startInfo.GameVersion}\n" +
$" Language: {startInfo.Language}\n" +
$" OptOutMbCollection: {startInfo.OptOutMbCollection}");
Log.Information("A Dalamud start info was not found in the program arguments. One has been generated for you.");
Log.Information("Copy the following contents into the program arguments:");
var startInfoJson = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(startInfo)));
Log.Information(startInfoJson);
}
return startInfo;
}
private static void Inject(Process process, DalamudStartInfo startInfo)
{
var nethostName = "nethost.dll";
var bootName = "Dalamud.Boot.dll";
var nethostPath = Path.GetFullPath(nethostName);
var bootPath = Path.GetFullPath(bootName);
// ======================================================
using var injector = new Injector(process);
injector.LoadLibrary(nethostPath, out _);
injector.LoadLibrary(bootPath, out var bootModule);
// ======================================================
var startInfoJson = JsonConvert.SerializeObject(startInfo);
var startInfoBytes = Encoding.UTF8.GetBytes(startInfoJson);
using var startInfoBuffer = new MemoryBufferHelper(process).CreatePrivateMemoryBuffer(startInfoBytes.Length + 0x8);
var startInfoAddress = startInfoBuffer.Add(startInfoBytes);
if (startInfoAddress == IntPtr.Zero)
throw new Exception("Unable to allocate start info JSON");
injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress);
injector.CallRemoteFunction(initAddress, startInfoAddress, out var exitCode);
// ======================================================
if (exitCode > 0)
{
Log.Error($"Dalamud.Boot::Initialize returned {exitCode}");
return; return;
var amountToCull = logFile.Length - cullingFileSize;
if (amountToCull < bufferSize)
return;
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite));
reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin);
var read = -1;
var total = 0;
var buffer = new byte[bufferSize];
while (read != 0)
{
read = reader.Read(buffer, 0, buffer.Length);
writer.Write(buffer, 0, read);
total += read;
} }
Log.Information("Done"); writer.BaseStream.SetLength(total);
}
catch (Exception)
{
/*
var caption = "XIVLauncher Error";
var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
*/
} }
} }
private static Process GetProcess(string arg)
{
Process process;
var pid = -1;
if (arg != default)
{
pid = int.Parse(arg);
}
switch (pid)
{
case -1:
process = Process.GetProcessesByName("ffxiv_dx11").FirstOrDefault();
if (process == default)
{
throw new Exception("Could not find process");
}
break;
case -2:
var exePath = "C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\ffxiv_dx11.exe";
var exeArgs = new StringBuilder()
.Append("DEV.TestSID=0 DEV.UseSqPack=1 DEV.DataPathType=1 ")
.Append("DEV.LobbyHost01=127.0.0.1 DEV.LobbyPort01=54994 ")
.Append("DEV.LobbyHost02=127.0.0.1 DEV.LobbyPort02=54994 ")
.Append("DEV.LobbyHost03=127.0.0.1 DEV.LobbyPort03=54994 ")
.Append("DEV.LobbyHost04=127.0.0.1 DEV.LobbyPort04=54994 ")
.Append("DEV.LobbyHost05=127.0.0.1 DEV.LobbyPort05=54994 ")
.Append("DEV.LobbyHost06=127.0.0.1 DEV.LobbyPort06=54994 ")
.Append("DEV.LobbyHost07=127.0.0.1 DEV.LobbyPort07=54994 ")
.Append("DEV.LobbyHost08=127.0.0.1 DEV.LobbyPort08=54994 ")
.Append("SYS.Region=0 language=1 version=1.0.0.0 ")
.Append("DEV.MaxEntitledExpansionID=2 DEV.GMServerHost=127.0.0.1 DEV.GameQuitMessageBox=0").ToString();
process = Process.Start(exePath, exeArgs);
Thread.Sleep(1000);
break;
default:
process = Process.GetProcessById(pid);
break;
}
return process;
}
private static DalamudStartInfo GetStartInfo(string arg, Process process)
{
DalamudStartInfo startInfo;
if (arg != default)
{
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(arg)));
}
else
{
var ffxivDir = Path.GetDirectoryName(process.MainModule.FileName);
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
var gameVer = GameVersion.Parse(gameVerStr);
startInfo = new DalamudStartInfo
{
WorkingDirectory = null,
ConfigurationPath = Path.Combine(xivlauncherDir, "dalamudConfig.json"),
PluginDirectory = Path.Combine(xivlauncherDir, "installedPlugins"),
DefaultPluginDirectory = Path.Combine(xivlauncherDir, "devPlugins"),
AssetDirectory = Path.Combine(xivlauncherDir, "dalamudAssets"),
GameVersion = gameVer,
Language = ClientLanguage.English,
OptOutMbCollection = false,
};
Log.Debug(
"Creating a new StartInfo with:\n" +
$" WorkingDirectory: {startInfo.WorkingDirectory}\n" +
$" ConfigurationPath: {startInfo.ConfigurationPath}\n" +
$" PluginDirectory: {startInfo.PluginDirectory}\n" +
$" DefaultPluginDirectory: {startInfo.DefaultPluginDirectory}\n" +
$" AssetDirectory: {startInfo.AssetDirectory}\n" +
$" GameVersion: {startInfo.GameVersion}\n" +
$" Language: {startInfo.Language}\n" +
$" OptOutMbCollection: {startInfo.OptOutMbCollection}");
Log.Information("A Dalamud start info was not found in the program arguments. One has been generated for you.");
Log.Information("Copy the following contents into the program arguments:");
var startInfoJson = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(startInfo)));
Log.Information(startInfoJson);
}
return startInfo;
}
private static void Inject(Process process, DalamudStartInfo startInfo)
{
var nethostName = "nethost.dll";
var bootName = "Dalamud.Boot.dll";
var nethostPath = Path.GetFullPath(nethostName);
var bootPath = Path.GetFullPath(bootName);
// ======================================================
using var injector = new Injector(process);
injector.LoadLibrary(nethostPath, out _);
injector.LoadLibrary(bootPath, out var bootModule);
// ======================================================
var startInfoJson = JsonConvert.SerializeObject(startInfo);
var startInfoBytes = Encoding.UTF8.GetBytes(startInfoJson);
using var startInfoBuffer = new MemoryBufferHelper(process).CreatePrivateMemoryBuffer(startInfoBytes.Length + 0x8);
var startInfoAddress = startInfoBuffer.Add(startInfoBytes);
if (startInfoAddress == IntPtr.Zero)
throw new Exception("Unable to allocate start info JSON");
injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress);
injector.CallRemoteFunction(initAddress, startInfoAddress, out var exitCode);
// ======================================================
if (exitCode > 0)
{
Log.Error($"Dalamud.Boot::Initialize returned {exitCode}");
return;
}
Log.Information("Done");
}
} }

View file

@ -16,344 +16,343 @@ using Serilog;
using static Dalamud.Injector.NativeFunctions; using static Dalamud.Injector.NativeFunctions;
using static Iced.Intel.AssemblerRegisters; using static Iced.Intel.AssemblerRegisters;
namespace Dalamud.Injector namespace Dalamud.Injector;
/// <summary>
/// This class implements injecting into a remote process. It is a highly stripped down version of the
/// https://github.com/Reloaded-Project injector/assembler implementation due to issues with Lutris and
/// Wine.
/// </summary>
internal sealed class Injector : IDisposable
{ {
private readonly Process targetProcess;
private readonly ExternalMemory extMemory;
private readonly CircularBuffer circularBuffer;
private readonly PrivateMemoryBuffer memoryBuffer;
private IntPtr loadLibraryShellPtr;
private IntPtr loadLibraryRetPtr;
private IntPtr getProcAddressShellPtr;
private IntPtr getProcAddressRetPtr;
/// <summary> /// <summary>
/// This class implements injecting into a remote process. It is a highly stripped down version of the /// Initializes a new instance of the <see cref="Injector"/> class.
/// https://github.com/Reloaded-Project injector/assembler implementation due to issues with Lutris and
/// Wine.
/// </summary> /// </summary>
internal sealed class Injector : IDisposable /// <param name="targetProcess">Process to inject.</param>
public Injector(Process targetProcess)
{ {
private readonly Process targetProcess; this.targetProcess = targetProcess;
private readonly ExternalMemory extMemory;
private readonly CircularBuffer circularBuffer;
private readonly PrivateMemoryBuffer memoryBuffer;
private IntPtr loadLibraryShellPtr; this.extMemory = new ExternalMemory(targetProcess);
private IntPtr loadLibraryRetPtr; this.circularBuffer = new CircularBuffer(4096, this.extMemory);
this.memoryBuffer = new MemoryBufferHelper(targetProcess).CreatePrivateMemoryBuffer(4096);
private IntPtr getProcAddressShellPtr; using var kernel32Module = this.GetProcessModule("KERNEL32.DLL");
private IntPtr getProcAddressRetPtr; var kernel32PeFile = new PeFile(kernel32Module.FileName);
var kernel32Exports = kernel32PeFile.ExportedFunctions;
/// <summary> this.SetupLoadLibrary(kernel32Module, kernel32Exports);
/// Initializes a new instance of the <see cref="Injector"/> class. this.SetupGetProcAddress(kernel32Module, kernel32Exports);
/// </summary> }
/// <param name="targetProcess">Process to inject.</param>
public Injector(Process targetProcess)
{
this.targetProcess = targetProcess;
this.extMemory = new ExternalMemory(targetProcess); /// <summary>
this.circularBuffer = new CircularBuffer(4096, this.extMemory); /// Finalizes an instance of the <see cref="Injector"/> class.
this.memoryBuffer = new MemoryBufferHelper(targetProcess).CreatePrivateMemoryBuffer(4096); /// </summary>
~Injector() => this.Dispose();
using var kernel32Module = this.GetProcessModule("KERNEL32.DLL"); /// <inheritdoc/>
var kernel32PeFile = new PeFile(kernel32Module.FileName); public void Dispose()
var kernel32Exports = kernel32PeFile.ExportedFunctions; {
GC.SuppressFinalize(this);
this.SetupLoadLibrary(kernel32Module, kernel32Exports); this.targetProcess?.Dispose();
this.SetupGetProcAddress(kernel32Module, kernel32Exports); this.circularBuffer?.Dispose();
} this.memoryBuffer?.Dispose();
}
/// <summary> /// <summary>
/// Finalizes an instance of the <see cref="Injector"/> class. /// Load a module by absolute file path.
/// </summary> /// </summary>
~Injector() => this.Dispose(); /// <param name="modulePath">Absolute file path.</param>
/// <param name="address">Address to the module.</param>
public void LoadLibrary(string modulePath, out IntPtr address)
{
var lpParameter = this.WriteNullTerminatedUnicodeString(modulePath);
/// <inheritdoc/> if (lpParameter == IntPtr.Zero)
public void Dispose() throw new Exception("Unable to allocate LoadLibraryW parameter");
{
GC.SuppressFinalize(this);
this.targetProcess?.Dispose(); Log.Verbose($"CreateRemoteThread: call 0x{this.loadLibraryShellPtr.ToInt64():X} with 0x{lpParameter.ToInt64():X}");
this.circularBuffer?.Dispose();
this.memoryBuffer?.Dispose();
}
/// <summary> var threadHandle = CreateRemoteThread(
/// Load a module by absolute file path. this.targetProcess.Handle,
/// </summary> IntPtr.Zero,
/// <param name="modulePath">Absolute file path.</param> UIntPtr.Zero,
/// <param name="address">Address to the module.</param> this.loadLibraryShellPtr,
public void LoadLibrary(string modulePath, out IntPtr address) lpParameter,
{ CreateThreadFlags.RunImmediately,
var lpParameter = this.WriteNullTerminatedUnicodeString(modulePath); out _);
if (lpParameter == IntPtr.Zero) _ = WaitForSingleObject(threadHandle, uint.MaxValue);
throw new Exception("Unable to allocate LoadLibraryW parameter");
Log.Verbose($"CreateRemoteThread: call 0x{this.loadLibraryShellPtr.ToInt64():X} with 0x{lpParameter.ToInt64():X}"); address = this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr);
var threadHandle = CreateRemoteThread( if (address == IntPtr.Zero)
this.targetProcess.Handle, throw new Exception($"Error calling LoadLibraryW with {modulePath}");
IntPtr.Zero, }
UIntPtr.Zero,
this.loadLibraryShellPtr,
lpParameter,
CreateThreadFlags.RunImmediately,
out _);
_ = WaitForSingleObject(threadHandle, uint.MaxValue); /// <summary>
/// Get the address of an exported module function.
/// </summary>
/// <param name="module">Module address.</param>
/// <param name="functionName">Name of the exported method.</param>
/// <param name="address">Address to the function.</param>
public void GetFunctionAddress(IntPtr module, string functionName, out IntPtr address)
{
var functionNamePtr = this.WriteNullTerminatedASCIIString(functionName);
var getProcAddressParams = new GetProcAddressParams(module, functionNamePtr);
var lpParameter = this.circularBuffer.Add(ref getProcAddressParams);
address = this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr); if (lpParameter == IntPtr.Zero)
throw new Exception("Unable to allocate GetProcAddress parameter ptr");
if (address == IntPtr.Zero) var threadHandle = CreateRemoteThread(
throw new Exception($"Error calling LoadLibraryW with {modulePath}"); this.targetProcess.Handle,
} IntPtr.Zero,
UIntPtr.Zero,
this.getProcAddressShellPtr,
lpParameter,
CreateThreadFlags.RunImmediately,
out _);
/// <summary> _ = WaitForSingleObject(threadHandle, uint.MaxValue);
/// Get the address of an exported module function.
/// </summary>
/// <param name="module">Module address.</param>
/// <param name="functionName">Name of the exported method.</param>
/// <param name="address">Address to the function.</param>
public void GetFunctionAddress(IntPtr module, string functionName, out IntPtr address)
{
var functionNamePtr = this.WriteNullTerminatedASCIIString(functionName);
var getProcAddressParams = new GetProcAddressParams(module, functionNamePtr);
var lpParameter = this.circularBuffer.Add(ref getProcAddressParams);
if (lpParameter == IntPtr.Zero) this.extMemory.Read(this.getProcAddressRetPtr, out address);
throw new Exception("Unable to allocate GetProcAddress parameter ptr");
var threadHandle = CreateRemoteThread( if (address == IntPtr.Zero)
this.targetProcess.Handle, throw new Exception($"Error calling GetProcAddress with {functionName}");
IntPtr.Zero, }
UIntPtr.Zero,
this.getProcAddressShellPtr,
lpParameter,
CreateThreadFlags.RunImmediately,
out _);
_ = WaitForSingleObject(threadHandle, uint.MaxValue); /// <summary>
/// Call a method in a remote process via CreateRemoteThread.
/// </summary>
/// <param name="methodAddress">Method address.</param>
/// <param name="parameterAddress">Parameter address.</param>
/// <param name="exitCode">Thread exit code.</param>
public void CallRemoteFunction(IntPtr methodAddress, IntPtr parameterAddress, out uint exitCode)
{
// Create and initialize a thread at our address and parameter address.
var threadHandle = CreateRemoteThread(
this.targetProcess.Handle,
IntPtr.Zero,
UIntPtr.Zero,
methodAddress,
parameterAddress,
CreateThreadFlags.RunImmediately,
out _);
this.extMemory.Read(this.getProcAddressRetPtr, out address); _ = WaitForSingleObject(threadHandle, uint.MaxValue);
if (address == IntPtr.Zero) GetExitCodeThread(threadHandle, out exitCode);
throw new Exception($"Error calling GetProcAddress with {functionName}");
}
/// <summary> CloseHandle(threadHandle);
/// Call a method in a remote process via CreateRemoteThread. }
/// </summary>
/// <param name="methodAddress">Method address.</param>
/// <param name="parameterAddress">Parameter address.</param>
/// <param name="exitCode">Thread exit code.</param>
public void CallRemoteFunction(IntPtr methodAddress, IntPtr parameterAddress, out uint exitCode)
{
// Create and initialize a thread at our address and parameter address.
var threadHandle = CreateRemoteThread(
this.targetProcess.Handle,
IntPtr.Zero,
UIntPtr.Zero,
methodAddress,
parameterAddress,
CreateThreadFlags.RunImmediately,
out _);
_ = WaitForSingleObject(threadHandle, uint.MaxValue); private void SetupLoadLibrary(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
{
var offset = this.GetExportedFunctionOffset(kernel32Exports, "LoadLibraryW");
var functionAddr = kernel32Module.BaseAddress + (int)offset;
Log.Verbose($"LoadLibraryW: 0x{functionAddr.ToInt64():X}");
GetExitCodeThread(threadHandle, out exitCode); var functionPtr = this.memoryBuffer.Add(ref functionAddr);
Log.Verbose($"LoadLibraryPtr: 0x{functionPtr.ToInt64():X}");
CloseHandle(threadHandle); if (functionPtr == IntPtr.Zero)
} throw new Exception("Unable to allocate LoadLibraryW function ptr");
private void SetupLoadLibrary(ProcessModule kernel32Module, ExportFunction[] kernel32Exports) var dummy = IntPtr.Zero;
{ this.loadLibraryRetPtr = this.memoryBuffer.Add(ref dummy);
var offset = this.GetExportedFunctionOffset(kernel32Exports, "LoadLibraryW"); Log.Verbose($"LoadLibraryRetPtr: 0x{this.loadLibraryRetPtr.ToInt64():X}");
var functionAddr = kernel32Module.BaseAddress + (int)offset;
Log.Verbose($"LoadLibraryW: 0x{functionAddr.ToInt64():X}");
var functionPtr = this.memoryBuffer.Add(ref functionAddr); if (this.loadLibraryRetPtr == IntPtr.Zero)
Log.Verbose($"LoadLibraryPtr: 0x{functionPtr.ToInt64():X}"); throw new Exception("Unable to allocate LoadLibraryW return value");
if (functionPtr == IntPtr.Zero) var func = functionPtr.ToInt64();
throw new Exception("Unable to allocate LoadLibraryW function ptr"); var retVal = this.loadLibraryRetPtr.ToInt64();
var dummy = IntPtr.Zero; var asm = new Assembler(64);
this.loadLibraryRetPtr = this.memoryBuffer.Add(ref dummy);
Log.Verbose($"LoadLibraryRetPtr: 0x{this.loadLibraryRetPtr.ToInt64():X}");
if (this.loadLibraryRetPtr == IntPtr.Zero) asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
throw new Exception("Unable to allocate LoadLibraryW return value"); asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] // CreateRemoteThread lpParameter with string already in ECX.
asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal], rax //
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
asm.ret(); // ret // Restore stack ptr. (Callee cleanup)
var func = functionPtr.ToInt64(); var bytes = this.Assemble(asm);
var retVal = this.loadLibraryRetPtr.ToInt64(); this.loadLibraryShellPtr = this.memoryBuffer.Add(bytes);
Log.Verbose($"LoadLibraryShellPtr: 0x{this.loadLibraryShellPtr.ToInt64():X}");
var asm = new Assembler(64); if (this.loadLibraryShellPtr == IntPtr.Zero)
throw new Exception("Unable to allocate LoadLibraryW shellcode");
asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary + shadow space. this.extMemory.ChangePermission(this.loadLibraryShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE);
asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] // CreateRemoteThread lpParameter with string already in ECX.
asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal], rax //
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
asm.ret(); // ret // Restore stack ptr. (Callee cleanup)
var bytes = this.Assemble(asm);
this.loadLibraryShellPtr = this.memoryBuffer.Add(bytes);
Log.Verbose($"LoadLibraryShellPtr: 0x{this.loadLibraryShellPtr.ToInt64():X}");
if (this.loadLibraryShellPtr == IntPtr.Zero)
throw new Exception("Unable to allocate LoadLibraryW shellcode");
this.extMemory.ChangePermission(this.loadLibraryShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE);
#if DEBUG #if DEBUG
var outFunctionPtr = this.extMemory.Read<IntPtr>(functionPtr); var outFunctionPtr = this.extMemory.Read<IntPtr>(functionPtr);
Log.Verbose($"LoadLibraryPtr: {this.GetResultMarker(outFunctionPtr == functionAddr)}"); Log.Verbose($"LoadLibraryPtr: {this.GetResultMarker(outFunctionPtr == functionAddr)}");
var outRetPtr = this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr); var outRetPtr = this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr);
Log.Verbose($"LoadLibraryRet: {this.GetResultMarker(dummy == outRetPtr)}"); Log.Verbose($"LoadLibraryRet: {this.GetResultMarker(dummy == outRetPtr)}");
this.extMemory.ReadRaw(this.loadLibraryShellPtr, out var outBytes, bytes.Length); this.extMemory.ReadRaw(this.loadLibraryShellPtr, out var outBytes, bytes.Length);
Log.Verbose($"LoadLibraryShellPtr: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))}"); Log.Verbose($"LoadLibraryShellPtr: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))}");
#endif #endif
} }
private void SetupGetProcAddress(ProcessModule kernel32Module, ExportFunction[] kernel32Exports) private void SetupGetProcAddress(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
{ {
var offset = this.GetExportedFunctionOffset(kernel32Exports, "GetProcAddress"); var offset = this.GetExportedFunctionOffset(kernel32Exports, "GetProcAddress");
var functionAddr = kernel32Module.BaseAddress + (int)offset; var functionAddr = kernel32Module.BaseAddress + (int)offset;
Log.Verbose($"GetProcAddress: 0x{functionAddr.ToInt64():X}"); Log.Verbose($"GetProcAddress: 0x{functionAddr.ToInt64():X}");
var functionPtr = this.memoryBuffer.Add(ref functionAddr); var functionPtr = this.memoryBuffer.Add(ref functionAddr);
Log.Verbose($"GetProcAddressPtr: 0x{functionPtr.ToInt64():X}"); Log.Verbose($"GetProcAddressPtr: 0x{functionPtr.ToInt64():X}");
if (functionPtr == IntPtr.Zero) if (functionPtr == IntPtr.Zero)
throw new Exception("Unable to allocate GetProcAddress function ptr"); throw new Exception("Unable to allocate GetProcAddress function ptr");
var dummy = IntPtr.Zero; var dummy = IntPtr.Zero;
this.getProcAddressRetPtr = this.memoryBuffer.Add(ref dummy); this.getProcAddressRetPtr = this.memoryBuffer.Add(ref dummy);
Log.Verbose($"GetProcAddressRetPtr: 0x{this.loadLibraryRetPtr.ToInt64():X}"); Log.Verbose($"GetProcAddressRetPtr: 0x{this.loadLibraryRetPtr.ToInt64():X}");
if (this.getProcAddressRetPtr == IntPtr.Zero) if (this.getProcAddressRetPtr == IntPtr.Zero)
throw new Exception("Unable to allocate GetProcAddress return value"); throw new Exception("Unable to allocate GetProcAddress return value");
var func = functionPtr.ToInt64(); var func = functionPtr.ToInt64();
var retVal = this.getProcAddressRetPtr.ToInt64(); var retVal = this.getProcAddressRetPtr.ToInt64();
var asm = new Assembler(64); var asm = new Assembler(64);
asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary +32 shadow space asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary +32 shadow space
asm.mov(rdx, __qword_ptr[__qword_ptr[rcx + 8]]); // mov rdx, qword [qword rcx + 8] // lpProcName asm.mov(rdx, __qword_ptr[__qword_ptr[rcx + 8]]); // mov rdx, qword [qword rcx + 8] // lpProcName
asm.mov(rcx, __qword_ptr[__qword_ptr[rcx + 0]]); // mov rcx, qword [qword rcx + 0] // hModule asm.mov(rcx, __qword_ptr[__qword_ptr[rcx + 0]]); // mov rcx, qword [qword rcx + 0] // hModule
asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] // asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] //
asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal] // asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal] //
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space. asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
asm.ret(); // ret // Restore stack ptr. (Callee cleanup) asm.ret(); // ret // Restore stack ptr. (Callee cleanup)
var bytes = this.Assemble(asm); var bytes = this.Assemble(asm);
this.getProcAddressShellPtr = this.memoryBuffer.Add(bytes); this.getProcAddressShellPtr = this.memoryBuffer.Add(bytes);
Log.Verbose($"GetProcAddressShellPtr: 0x{this.getProcAddressShellPtr.ToInt64():X}"); Log.Verbose($"GetProcAddressShellPtr: 0x{this.getProcAddressShellPtr.ToInt64():X}");
if (this.getProcAddressShellPtr == IntPtr.Zero) if (this.getProcAddressShellPtr == IntPtr.Zero)
throw new Exception("Unable to allocate GetProcAddress shellcode"); throw new Exception("Unable to allocate GetProcAddress shellcode");
this.extMemory.ChangePermission(this.getProcAddressShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE); this.extMemory.ChangePermission(this.getProcAddressShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE);
#if DEBUG #if DEBUG
var outFunctionPtr = this.extMemory.Read<IntPtr>(functionPtr); var outFunctionPtr = this.extMemory.Read<IntPtr>(functionPtr);
Log.Verbose($"GetProcAddressPtr: {this.GetResultMarker(outFunctionPtr == functionAddr)}"); Log.Verbose($"GetProcAddressPtr: {this.GetResultMarker(outFunctionPtr == functionAddr)}");
var outRetPtr = this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr); var outRetPtr = this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr);
Log.Verbose($"GetProcAddressRet: {this.GetResultMarker(dummy == outRetPtr)}"); Log.Verbose($"GetProcAddressRet: {this.GetResultMarker(dummy == outRetPtr)}");
this.extMemory.ReadRaw(this.getProcAddressShellPtr, out var outBytes, bytes.Length); this.extMemory.ReadRaw(this.getProcAddressShellPtr, out var outBytes, bytes.Length);
Log.Verbose($"GetProcAddressShellPtr: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))}"); Log.Verbose($"GetProcAddressShellPtr: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))}");
#endif #endif
}
private byte[] Assemble(Assembler assembler)
{
using var stream = new MemoryStream();
assembler.Assemble(new StreamCodeWriter(stream), 0);
stream.Position = 0;
var reader = new StreamCodeReader(stream);
int next;
var bytes = new byte[stream.Length];
while ((next = reader.ReadByte()) >= 0)
{
bytes[stream.Position - 1] = (byte)next;
} }
private byte[] Assemble(Assembler assembler) return bytes;
}
private ProcessModule GetProcessModule(string moduleName)
{
var modules = this.targetProcess.Modules;
for (var i = 0; i < modules.Count; i++)
{ {
using var stream = new MemoryStream(); var module = modules[i];
assembler.Assemble(new StreamCodeWriter(stream), 0); if (module.ModuleName.Equals(moduleName, StringComparison.InvariantCultureIgnoreCase))
stream.Position = 0;
var reader = new StreamCodeReader(stream);
int next;
var bytes = new byte[stream.Length];
while ((next = reader.ReadByte()) >= 0)
{ {
bytes[stream.Position - 1] = (byte)next; return module;
} }
return bytes;
} }
private ProcessModule GetProcessModule(string moduleName) throw new Exception($"Failed to find {moduleName} in target process' modules");
{ }
var modules = this.targetProcess.Modules;
for (var i = 0; i < modules.Count; i++)
{
var module = modules[i];
if (module.ModuleName.Equals(moduleName, StringComparison.InvariantCultureIgnoreCase))
{
return module;
}
}
throw new Exception($"Failed to find {moduleName} in target process' modules"); private uint GetExportedFunctionOffset(ExportFunction[] exportFunctions, string functionName)
} {
var exportFunction = exportFunctions.FirstOrDefault(func => func.Name == functionName);
private uint GetExportedFunctionOffset(ExportFunction[] exportFunctions, string functionName) if (exportFunction == default)
{ throw new Exception($"Failed to find exported function {functionName} in target module's exports");
var exportFunction = exportFunctions.FirstOrDefault(func => func.Name == functionName);
if (exportFunction == default) return exportFunction.Address;
throw new Exception($"Failed to find exported function {functionName} in target module's exports"); }
return exportFunction.Address; private IntPtr WriteNullTerminatedASCIIString(string value)
} {
var bytes = Encoding.ASCII.GetBytes(value + '\0');
var address = this.circularBuffer.Add(bytes);
private IntPtr WriteNullTerminatedASCIIString(string value) if (address == IntPtr.Zero)
{ throw new Exception("Unable to write ASCII string to buffer");
var bytes = Encoding.ASCII.GetBytes(value + '\0');
var address = this.circularBuffer.Add(bytes);
if (address == IntPtr.Zero)
throw new Exception("Unable to write ASCII string to buffer");
#if DEBUG #if DEBUG
this.extMemory.ReadRaw(address, out var outBytes, bytes.Length); this.extMemory.ReadRaw(address, out var outBytes, bytes.Length);
Log.Verbose($"WriteASCII: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address.ToInt64():X} {value}"); Log.Verbose($"WriteASCII: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address.ToInt64():X} {value}");
#endif #endif
return address; return address;
} }
private IntPtr WriteNullTerminatedUnicodeString(string value) private IntPtr WriteNullTerminatedUnicodeString(string value)
{ {
var bytes = Encoding.Unicode.GetBytes(value + '\0'); var bytes = Encoding.Unicode.GetBytes(value + '\0');
var address = this.circularBuffer.Add(bytes); var address = this.circularBuffer.Add(bytes);
if (address == IntPtr.Zero) if (address == IntPtr.Zero)
throw new Exception("Unable to write Unicode string to buffer"); throw new Exception("Unable to write Unicode string to buffer");
#if DEBUG #if DEBUG
this.extMemory.ReadRaw(address, out var outBytes, bytes.Length); this.extMemory.ReadRaw(address, out var outBytes, bytes.Length);
Log.Verbose($"WriteUnicode: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address.ToInt64():X} {value}"); Log.Verbose($"WriteUnicode: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address.ToInt64():X} {value}");
#endif #endif
return address; return address;
} }
#if DEBUG #if DEBUG
private string GetResultMarker(bool result) => result ? "✅" : "❌"; private string GetResultMarker(bool result) => result ? "✅" : "❌";
#endif #endif
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
private struct GetProcAddressParams private struct GetProcAddressParams
{
public GetProcAddressParams(IntPtr hModule, IntPtr lPProcName)
{ {
public GetProcAddressParams(IntPtr hModule, IntPtr lPProcName) this.HModule = hModule.ToInt64();
{ this.LPProcName = lPProcName.ToInt64();
this.HModule = hModule.ToInt64();
this.LPProcName = lPProcName.ToInt64();
}
public long HModule { get; set; }
public long LPProcName { get; set; }
} }
public long HModule { get; set; }
public long LPProcName { get; set; }
} }
} }

File diff suppressed because it is too large Load diff