Implement RemoteModule/RemoteFunction and prove x64 ABI at runtime
Task 7.2: export resolution + module/function facade. - PeHeaderParser.GetExportAddress walks the PE32/PE32+ export directory and follows export forwarders (e.g. kernel32!HeapAlloc -> NTDLL.RtlAllocateHeap) into other loaded modules; ordinal and unresolvable API-set forwarders throw NotSupportedException. - RemoteModule resolves a module base via Process.Modules (name match tolerant of .dll/case); RemoteFunction executes via RemoteThreadExecutor by default, exposes Address for pump routing and CreateDelegate<T> for in-process. - Magic gains a string indexer: magic["user32"]["MessageBoxA"]. Task 3.8: add the missing live-execution ABI test - an SSE callee whose aligned movaps #GPs unless the stub delivers a 16-byte-aligned stack, combined with a 5th stack argument. Runtime-proves shadow space, alignment, and arg placement. Tests: 214 passing, 4 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using WhiteMagic.Discovery;
|
||||
using Process = System.Diagnostics.Process;
|
||||
using ProcessModule = System.Diagnostics.ProcessModule;
|
||||
|
||||
namespace WhiteMagic;
|
||||
|
||||
/// <summary>
|
||||
/// A module (loaded DLL/EXE image) in the target process, obtained by indexing the
|
||||
/// facade with a module name (e.g. <c>magic["user32"]</c>). Exposes the module's base
|
||||
/// address and resolves exported functions by name.
|
||||
/// </summary>
|
||||
public sealed class RemoteModule
|
||||
{
|
||||
private readonly Magic _magic;
|
||||
|
||||
/// <summary>The module's file name as reported by the OS (e.g. <c>user32.dll</c>).</summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>The module's load address in the target process.</summary>
|
||||
public IntPtr BaseAddress { get; }
|
||||
|
||||
internal RemoteModule(Magic magic, string moduleName)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(magic);
|
||||
ArgumentException.ThrowIfNullOrEmpty(moduleName);
|
||||
|
||||
_magic = magic;
|
||||
|
||||
(string name, IntPtr baseAddress) = FindModule(magic.Memory.ProcessId, moduleName);
|
||||
Name = name;
|
||||
BaseAddress = baseAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves an exported function by name and returns a <see cref="RemoteFunction"/>
|
||||
/// bound to its address. Export forwarders are followed.
|
||||
/// </summary>
|
||||
public RemoteFunction this[string functionName]
|
||||
{
|
||||
get
|
||||
{
|
||||
IntPtr address = GetExportAddress(functionName);
|
||||
return new RemoteFunction(_magic, functionName, address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Resolves the absolute address of an exported function by name.</summary>
|
||||
public IntPtr GetExportAddress(string functionName)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(functionName);
|
||||
var parser = new PeHeaderParser(_magic.Memory, BaseAddress);
|
||||
return parser.GetExportAddress(functionName);
|
||||
}
|
||||
|
||||
private static (string Name, IntPtr BaseAddress) FindModule(int processId, string moduleName)
|
||||
{
|
||||
using Process process = Process.GetProcessById(processId);
|
||||
foreach (ProcessModule module in process.Modules)
|
||||
{
|
||||
if (NameMatches(module.ModuleName, moduleName))
|
||||
return (module.ModuleName, module.BaseAddress);
|
||||
}
|
||||
|
||||
throw new DllNotFoundException(
|
||||
$"Module '{moduleName}' is not loaded in process {processId}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a module's base address by name within a target process, returning
|
||||
/// <see cref="IntPtr.Zero"/> if it is not loaded. Used by export-forwarder resolution.
|
||||
/// </summary>
|
||||
internal static IntPtr ResolveBase(int processId, string moduleName)
|
||||
{
|
||||
using Process process = Process.GetProcessById(processId);
|
||||
foreach (ProcessModule module in process.Modules)
|
||||
{
|
||||
if (NameMatches(module.ModuleName, moduleName))
|
||||
return module.BaseAddress;
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches a loaded module's file name against a requested name, tolerating a missing
|
||||
/// or present <c>.dll</c> extension and ignoring case (e.g. <c>KERNEL32</c> matches
|
||||
/// <c>kernel32.dll</c>).
|
||||
/// </summary>
|
||||
private static bool NameMatches(string actual, string requested)
|
||||
{
|
||||
if (string.Equals(actual, requested, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
string actualNoExt = Path.GetFileNameWithoutExtension(actual);
|
||||
string requestedNoExt = requested.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
|
||||
? requested[..^4]
|
||||
: requested;
|
||||
|
||||
return string.Equals(actualNoExt, requestedNoExt, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user