Implement core diagnostic memory layer, execution helpers, and high-level facade slices
Implemented: - Core: UTF-16 ReadString boundary/alignment fix, target bitness and process id on MemoryBase - function interception: PatchManager, DetourManager, InstructionAnalyzer, MainThreadDispatcher - Execution: BackgroundTaskExecutor, InProcessInvoker - High-level: Magic facade, RemotePointer, async wrappers - Discovery/external code loading/Window groundwork (PEB/TEB, pattern scanning, raw allocations, DLL external code loading, window/input) Tests: 180 passing, 4 integration/interactive tests skipped.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using WhiteMagic.Memory;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagic.Injection;
|
||||
|
||||
/// <summary>
|
||||
/// Injects raw machine code into a process's memory.
|
||||
/// </summary>
|
||||
public static class CodeInjector
|
||||
{
|
||||
/// <summary>
|
||||
/// Injects code at a specific address.
|
||||
/// </summary>
|
||||
/// <param name="memory">The memory accessor.</param>
|
||||
/// <param name="address">The target address.</param>
|
||||
/// <param name="code">The machine code bytes to write.</param>
|
||||
/// <returns>The address the code was written to (same as <paramref name="address"/>).</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="code"/> is empty.</exception>
|
||||
/// <exception cref="Win32Exception">Write fails.</exception>
|
||||
public static IntPtr InjectAtAddress(MemoryBase memory, IntPtr address, byte[] code)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(memory);
|
||||
ArgumentNullException.ThrowIfNull(code);
|
||||
|
||||
if (code.Length == 0)
|
||||
throw new ArgumentException("Code cannot be empty.", nameof(code));
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
throw new ArgumentException("Address cannot be zero.", nameof(address));
|
||||
|
||||
// Write the code to the target address
|
||||
int written = memory.WriteBytes(address, code);
|
||||
if (written != code.Length)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new Win32Exception(error,
|
||||
$"WriteProcessMemory failed at {address} (wrote {written} of {code.Length} bytes).");
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates executable memory and injects code into it.
|
||||
/// </summary>
|
||||
/// <param name="memory">The memory accessor.</param>
|
||||
/// <param name="code">The machine code bytes to inject.</param>
|
||||
/// <param name="protection">
|
||||
/// The memory protection. Defaults to <see cref="MemoryProtectionType.ExecuteReadWrite"/>.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The base address of the allocated memory containing the code.
|
||||
/// The caller is responsible for freeing this memory (e.g., via <see cref="AllocatedMemory.Dispose"/>).
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="code"/> is empty.</exception>
|
||||
/// <exception cref="Win32Exception">Allocation or write fails.</exception>
|
||||
public static AllocatedMemory Inject(
|
||||
MemoryBase memory,
|
||||
byte[] code,
|
||||
MemoryProtectionType protection = MemoryProtectionType.ExecuteReadWrite)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(memory);
|
||||
ArgumentNullException.ThrowIfNull(code);
|
||||
|
||||
if (code.Length == 0)
|
||||
throw new ArgumentException("Code cannot be empty.", nameof(code));
|
||||
|
||||
// Allocate memory with the specified protection
|
||||
var allocated = new AllocatedMemory(memory, code.Length, protection);
|
||||
|
||||
// Write the code to the allocated memory
|
||||
int written = memory.WriteBytes(allocated.BaseAddress, code);
|
||||
if (written != code.Length)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
allocated.Dispose();
|
||||
throw new Win32Exception(error,
|
||||
$"WriteProcessMemory failed (wrote {written} of {code.Length} bytes).");
|
||||
}
|
||||
|
||||
return allocated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagic.Injection;
|
||||
|
||||
/// <summary>
|
||||
/// Injects DLLs into an open target process by creating a remote thread or by
|
||||
/// hijacking an existing thread.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The DLL path is sent to <c>LoadLibraryW</c>, so it is encoded as a null-terminated
|
||||
/// UTF-16 string in the target process.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Injection requires the target process to have the same bitness as the current
|
||||
/// process, because the emitted x86/x64 stubs and the captured thread context must
|
||||
/// match the target architecture.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class DllInjector
|
||||
{
|
||||
private readonly MemoryBase _memory;
|
||||
private readonly bool _currentIs64Bit;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="DllInjector"/> for the target represented by
|
||||
/// <paramref name="memory"/>.
|
||||
/// </summary>
|
||||
/// <param name="memory">A reader/writer for the target process.</param>
|
||||
public DllInjector(MemoryBase memory)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(memory);
|
||||
_memory = memory;
|
||||
_currentIs64Bit = Environment.Is64BitProcess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="MemoryBase"/> the injector is operating on.
|
||||
/// </summary>
|
||||
public MemoryBase Memory => _memory;
|
||||
|
||||
/// <summary>
|
||||
/// Injects a DLL into the target process by creating a remote thread that loads it.
|
||||
/// </summary>
|
||||
/// <param name="dllPath">The path to the DLL. The file must exist.</param>
|
||||
/// <returns>The base address of the loaded module in the target process.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="dllPath"/> is null or empty.</exception>
|
||||
/// <exception cref="FileNotFoundException"><paramref name="dllPath"/> does not exist.</exception>
|
||||
/// <exception cref="InvalidOperationException">The target bitness does not match the caller.</exception>
|
||||
/// <exception cref="InvalidOperationException">The remote load failed or timed out.</exception>
|
||||
public IntPtr InjectWithRemoteThread(string dllPath)
|
||||
{
|
||||
ValidateAndCheckBitness(dllPath);
|
||||
|
||||
IntPtr loadLibrary = ResolveLoadLibraryW();
|
||||
byte[] pathBytes = Encoding.Unicode.GetBytes(dllPath + '\0');
|
||||
|
||||
int pointerSize = _currentIs64Bit ? 8 : 4;
|
||||
int stubSize = _currentIs64Bit ? 39 : 18;
|
||||
int pathOffset = Align(stubSize, pointerSize);
|
||||
int resultOffset = Align(pathOffset + pathBytes.Length, pointerSize);
|
||||
int totalSize = resultOffset + pointerSize + 4096;
|
||||
|
||||
IntPtr remoteBase = NativeMethods.VirtualAllocEx(
|
||||
_memory.Handle,
|
||||
IntPtr.Zero,
|
||||
totalSize,
|
||||
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
|
||||
MemoryProtectionType.ExecuteReadWrite);
|
||||
|
||||
if (remoteBase == IntPtr.Zero)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"VirtualAllocEx failed (error {error}).");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IntPtr pathAddress = remoteBase + pathOffset;
|
||||
IntPtr resultAddress = remoteBase + resultOffset;
|
||||
|
||||
if (_memory.WriteBytes(pathAddress, pathBytes) != pathBytes.Length)
|
||||
throw new InvalidOperationException("Failed to write the DLL path into the target process.");
|
||||
|
||||
byte[] stub = _currentIs64Bit
|
||||
? BuildRemoteThreadStubX64(pathAddress, resultAddress, loadLibrary)
|
||||
: BuildRemoteThreadStubX86(pathAddress, resultAddress, loadLibrary);
|
||||
|
||||
if (_memory.WriteBytes(remoteBase, stub) != stub.Length)
|
||||
throw new InvalidOperationException("Failed to write the remote thread stub.");
|
||||
|
||||
using SafeMemoryHandle thread = NativeMethods.CreateRemoteThread(
|
||||
_memory.Handle,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
remoteBase,
|
||||
IntPtr.Zero,
|
||||
ThreadCreationFlags.RunImmediately,
|
||||
out _);
|
||||
|
||||
if (thread.IsInvalid)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"CreateRemoteThread failed (error {error}).");
|
||||
}
|
||||
|
||||
const uint timeoutMs = 30000;
|
||||
uint wait = NativeMethods.WaitForSingleObject(thread, timeoutMs);
|
||||
if (wait == 0xFFFFFFFF)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"WaitForSingleObject failed (error {error}).");
|
||||
}
|
||||
|
||||
if (wait == 0x00000102)
|
||||
throw new InvalidOperationException("Remote thread timed out while loading the DLL.");
|
||||
|
||||
IntPtr result = _memory.Read<IntPtr>(resultAddress);
|
||||
if (result == IntPtr.Zero)
|
||||
throw new InvalidOperationException("LoadLibrary returned zero; the DLL could not be loaded.");
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The DLL is already loaded; the temporary stub, path and result slot can be released.
|
||||
NativeMethods.VirtualFreeEx(_memory.Handle, remoteBase, 0, MemoryFreeType.Release);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Injects a DLL by hijacking an existing thread in the target process.
|
||||
/// </summary>
|
||||
/// <param name="threadId">The operating-system identifier of the thread to hijack.</param>
|
||||
/// <param name="dllPath">The path to the DLL. The file must exist.</param>
|
||||
/// <returns>The base address of the loaded module in the target process.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="threadId"/> is not a positive value or
|
||||
/// <paramref name="dllPath"/> is null or empty.</exception>
|
||||
/// <exception cref="FileNotFoundException"><paramref name="dllPath"/> does not exist.</exception>
|
||||
/// <exception cref="InvalidOperationException">The target bitness does not match the caller.</exception>
|
||||
/// <exception cref="InvalidOperationException">The hijack, load, or context restore failed.</exception>
|
||||
public IntPtr InjectWithThreadHijack(int threadId, string dllPath)
|
||||
{
|
||||
if (threadId <= 0)
|
||||
throw new ArgumentException("Thread ID must be a positive value.", nameof(threadId));
|
||||
|
||||
ValidateAndCheckBitness(dllPath);
|
||||
|
||||
IntPtr loadLibrary = ResolveLoadLibraryW();
|
||||
byte[] pathBytes = Encoding.Unicode.GetBytes(dllPath + '\0');
|
||||
|
||||
int pointerSize = _currentIs64Bit ? 8 : 4;
|
||||
int stubSize = _currentIs64Bit ? 40 : 19;
|
||||
int pathOffset = Align(stubSize, pointerSize);
|
||||
int resultOffset = Align(pathOffset + pathBytes.Length, pointerSize);
|
||||
int totalSize = resultOffset + pointerSize + 4096;
|
||||
|
||||
IntPtr remoteBase = NativeMethods.VirtualAllocEx(
|
||||
_memory.Handle,
|
||||
IntPtr.Zero,
|
||||
totalSize,
|
||||
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
|
||||
MemoryProtectionType.ExecuteReadWrite);
|
||||
|
||||
if (remoteBase == IntPtr.Zero)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"VirtualAllocEx failed (error {error}).");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IntPtr pathAddress = remoteBase + pathOffset;
|
||||
IntPtr resultAddress = remoteBase + resultOffset;
|
||||
|
||||
if (_memory.WriteBytes(pathAddress, pathBytes) != pathBytes.Length)
|
||||
throw new InvalidOperationException("Failed to write the DLL path into the target process.");
|
||||
|
||||
byte[] stub = _currentIs64Bit
|
||||
? BuildHijackStubX64(pathAddress, resultAddress, loadLibrary)
|
||||
: BuildHijackStubX86(pathAddress, resultAddress, loadLibrary);
|
||||
|
||||
if (_memory.WriteBytes(remoteBase, stub) != stub.Length)
|
||||
throw new InvalidOperationException("Failed to write the hijack stub.");
|
||||
|
||||
nint stackTop = (nint)(remoteBase + totalSize);
|
||||
stackTop = AlignDown(stackTop, pointerSize);
|
||||
if (_currentIs64Bit)
|
||||
stackTop = AlignDown(stackTop, 16);
|
||||
|
||||
using SafeMemoryHandle thread = NativeMethods.OpenThread(
|
||||
ThreadAccess.SuspendResume | ThreadAccess.GetContext | ThreadAccess.SetContext | ThreadAccess.QueryInformation,
|
||||
false,
|
||||
threadId);
|
||||
|
||||
if (thread.IsInvalid)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"OpenThread failed (error {error}).");
|
||||
}
|
||||
|
||||
if (NativeMethods.SuspendThread(thread) == 0xFFFFFFFF)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"SuspendThread failed (error {error}).");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IntPtr result;
|
||||
|
||||
if (_currentIs64Bit)
|
||||
{
|
||||
var originalContext = new Context64 { ContextFlags = ContextFlags.Amd64Full };
|
||||
if (!NativeMethods.GetThreadContext(thread, ref originalContext))
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"GetThreadContext failed (error {error}).");
|
||||
}
|
||||
|
||||
var redirectContext = originalContext;
|
||||
redirectContext.Rip = (ulong)(nint)remoteBase;
|
||||
redirectContext.Rsp = (ulong)stackTop;
|
||||
|
||||
if (!NativeMethods.SetThreadContext(thread, ref redirectContext))
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"SetThreadContext failed (error {error}).");
|
||||
}
|
||||
|
||||
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"ResumeThread failed (error {error}).");
|
||||
}
|
||||
|
||||
result = WaitForResult(resultAddress, TimeSpan.FromSeconds(5));
|
||||
|
||||
if (NativeMethods.SuspendThread(thread) == 0xFFFFFFFF)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"SuspendThread failed while capturing result (error {error}).");
|
||||
}
|
||||
|
||||
if (!NativeMethods.SetThreadContext(thread, ref originalContext))
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"SetThreadContext restore failed (error {error}).");
|
||||
}
|
||||
|
||||
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"ResumeThread restore failed (error {error}).");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var originalContext = new Context32 { ContextFlags = ContextFlags.X86Full };
|
||||
if (!NativeMethods.Wow64GetThreadContext(thread, ref originalContext))
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"Wow64GetThreadContext failed (error {error}).");
|
||||
}
|
||||
|
||||
var redirectContext = originalContext;
|
||||
redirectContext.Eip = (uint)(nint)remoteBase;
|
||||
redirectContext.Esp = (uint)(nint)stackTop;
|
||||
|
||||
if (!NativeMethods.Wow64SetThreadContext(thread, ref redirectContext))
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"Wow64SetThreadContext failed (error {error}).");
|
||||
}
|
||||
|
||||
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"ResumeThread failed (error {error}).");
|
||||
}
|
||||
|
||||
result = WaitForResult(resultAddress, TimeSpan.FromSeconds(5));
|
||||
|
||||
if (NativeMethods.SuspendThread(thread) == 0xFFFFFFFF)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"SuspendThread failed while capturing result (error {error}).");
|
||||
}
|
||||
|
||||
if (!NativeMethods.Wow64SetThreadContext(thread, ref originalContext))
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"Wow64SetThreadContext restore failed (error {error}).");
|
||||
}
|
||||
|
||||
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"ResumeThread restore failed (error {error}).");
|
||||
}
|
||||
}
|
||||
|
||||
if (result == IntPtr.Zero)
|
||||
throw new InvalidOperationException("LoadLibrary returned zero; the DLL could not be loaded.");
|
||||
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort: resume the thread if we left it suspended.
|
||||
_ = NativeMethods.ResumeThread(thread);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.VirtualFreeEx(_memory.Handle, remoteBase, 0, MemoryFreeType.Release);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateAndCheckBitness(string dllPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dllPath))
|
||||
throw new ArgumentException("DLL path cannot be null or empty.", nameof(dllPath));
|
||||
|
||||
if (!File.Exists(dllPath))
|
||||
throw new FileNotFoundException("The specified DLL was not found.", dllPath);
|
||||
|
||||
if (_memory.Is64Bit != _currentIs64Bit)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The target process bitness does not match the current process bitness.");
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr ResolveLoadLibraryW()
|
||||
{
|
||||
// kernel32.dll is loaded at the same base address in every process at a given
|
||||
// bitness, so resolving the export in the current process gives the correct
|
||||
// target address for the remote process.
|
||||
IntPtr kernel32 = NativeMethods.LoadLibrary("kernel32.dll");
|
||||
if (kernel32 == IntPtr.Zero)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"Unable to obtain a handle to kernel32.dll (error {error}).");
|
||||
}
|
||||
|
||||
IntPtr loadLibrary = NativeMethods.GetProcAddress(kernel32, "LoadLibraryW");
|
||||
if (loadLibrary == IntPtr.Zero)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException($"Unable to resolve LoadLibraryW (error {error}).");
|
||||
}
|
||||
|
||||
return loadLibrary;
|
||||
}
|
||||
|
||||
private IntPtr WaitForResult(IntPtr resultAddress, TimeSpan timeout)
|
||||
{
|
||||
Stopwatch watch = Stopwatch.StartNew();
|
||||
while (watch.Elapsed < timeout)
|
||||
{
|
||||
IntPtr value = _memory.Read<IntPtr>(resultAddress);
|
||||
if (value != IntPtr.Zero)
|
||||
return value;
|
||||
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private static byte[] BuildRemoteThreadStubX86(IntPtr pathAddress, IntPtr resultAddress, IntPtr loadLibrary)
|
||||
{
|
||||
var buffer = new List<byte>(18);
|
||||
|
||||
// push pathAddress
|
||||
buffer.Add(0x68);
|
||||
EmitU32(buffer, (uint)(nint)pathAddress);
|
||||
|
||||
// mov ecx, LoadLibraryW
|
||||
buffer.Add(0xB9);
|
||||
EmitU32(buffer, (uint)(nint)loadLibrary);
|
||||
|
||||
// call ecx
|
||||
buffer.Add(0xFF);
|
||||
buffer.Add(0xD1);
|
||||
|
||||
// mov [resultAddress], eax
|
||||
buffer.Add(0xA3);
|
||||
EmitU32(buffer, (uint)(nint)resultAddress);
|
||||
|
||||
// ret
|
||||
buffer.Add(0xC3);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] BuildRemoteThreadStubX64(IntPtr pathAddress, IntPtr resultAddress, IntPtr loadLibrary)
|
||||
{
|
||||
var buffer = new List<byte>(39);
|
||||
|
||||
// mov rcx, pathAddress
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0xB9);
|
||||
EmitU64(buffer, (ulong)(nint)pathAddress);
|
||||
|
||||
// mov rax, LoadLibraryW
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0xB8);
|
||||
EmitU64(buffer, (ulong)(nint)loadLibrary);
|
||||
|
||||
// call rax
|
||||
buffer.Add(0xFF);
|
||||
buffer.Add(0xD0);
|
||||
|
||||
// mov rdx, rax
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0x89);
|
||||
buffer.Add(0xC2);
|
||||
|
||||
// mov rax, resultAddress
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0xB8);
|
||||
EmitU64(buffer, (ulong)(nint)resultAddress);
|
||||
|
||||
// mov [rax], rdx
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0x89);
|
||||
buffer.Add(0x10);
|
||||
|
||||
// ret
|
||||
buffer.Add(0xC3);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] BuildHijackStubX86(IntPtr pathAddress, IntPtr resultAddress, IntPtr loadLibrary)
|
||||
{
|
||||
var buffer = new List<byte>(19);
|
||||
|
||||
// push pathAddress
|
||||
buffer.Add(0x68);
|
||||
EmitU32(buffer, (uint)(nint)pathAddress);
|
||||
|
||||
// mov ecx, LoadLibraryW
|
||||
buffer.Add(0xB9);
|
||||
EmitU32(buffer, (uint)(nint)loadLibrary);
|
||||
|
||||
// call ecx
|
||||
buffer.Add(0xFF);
|
||||
buffer.Add(0xD1);
|
||||
|
||||
// mov [resultAddress], eax
|
||||
buffer.Add(0xA3);
|
||||
EmitU32(buffer, (uint)(nint)resultAddress);
|
||||
|
||||
// jmp $ (infinite loop so the main injector can suspend and restore context)
|
||||
buffer.Add(0xEB);
|
||||
buffer.Add(0xFE);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] BuildHijackStubX64(IntPtr pathAddress, IntPtr resultAddress, IntPtr loadLibrary)
|
||||
{
|
||||
var buffer = new List<byte>(40);
|
||||
|
||||
// mov rcx, pathAddress
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0xB9);
|
||||
EmitU64(buffer, (ulong)(nint)pathAddress);
|
||||
|
||||
// mov rax, LoadLibraryW
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0xB8);
|
||||
EmitU64(buffer, (ulong)(nint)loadLibrary);
|
||||
|
||||
// call rax
|
||||
buffer.Add(0xFF);
|
||||
buffer.Add(0xD0);
|
||||
|
||||
// mov rdx, rax
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0x89);
|
||||
buffer.Add(0xC2);
|
||||
|
||||
// mov rax, resultAddress
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0xB8);
|
||||
EmitU64(buffer, (ulong)(nint)resultAddress);
|
||||
|
||||
// mov [rax], rdx
|
||||
buffer.Add(0x48);
|
||||
buffer.Add(0x89);
|
||||
buffer.Add(0x10);
|
||||
|
||||
// jmp $ (infinite loop)
|
||||
buffer.Add(0xEB);
|
||||
buffer.Add(0xFE);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static void EmitU32(List<byte> buffer, uint value)
|
||||
{
|
||||
buffer.Add((byte)value);
|
||||
buffer.Add((byte)(value >> 8));
|
||||
buffer.Add((byte)(value >> 16));
|
||||
buffer.Add((byte)(value >> 24));
|
||||
}
|
||||
|
||||
private static void EmitU64(List<byte> buffer, ulong value)
|
||||
{
|
||||
EmitU32(buffer, (uint)value);
|
||||
EmitU32(buffer, (uint)(value >> 32));
|
||||
}
|
||||
|
||||
private static int Align(int value, int alignment)
|
||||
{
|
||||
return (value + alignment - 1) / alignment * alignment;
|
||||
}
|
||||
|
||||
private static nint AlignDown(nint value, int alignment)
|
||||
{
|
||||
return (nint)((nuint)value & ~((nuint)alignment - 1));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user