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>
102 lines
3.6 KiB
C#
102 lines
3.6 KiB
C#
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);
|
|
}
|
|
}
|