mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
refactor(Injector): switch to file-scoped namespaces
This commit is contained in:
parent
b5f34c3199
commit
f30ebe5166
3 changed files with 1288 additions and 1291 deletions
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue