using WhiteMagic.Discovery; using Process = System.Diagnostics.Process; using ProcessModule = System.Diagnostics.ProcessModule; namespace WhiteMagic; /// /// A module (loaded DLL/EXE image) in the target process, obtained by indexing the /// facade with a module name (e.g. magic["user32"]). Exposes the module's base /// address and resolves exported functions by name. /// public sealed class RemoteModule { private readonly Magic _magic; /// The module's file name as reported by the OS (e.g. user32.dll). public string Name { get; } /// The module's load address in the target process. 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; } /// /// Resolves an exported function by name and returns a /// bound to its address. Export forwarders are followed. /// public RemoteFunction this[string functionName] { get { IntPtr address = GetExportAddress(functionName); return new RemoteFunction(_magic, functionName, address); } } /// Resolves the absolute address of an exported function by name. 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}."); } /// /// Resolves a module's base address by name within a target process, returning /// if it is not loaded. Used by export-forwarder resolution. /// 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; } /// /// Matches a loaded module's file name against a requested name, tolerating a missing /// or present .dll extension and ignoring case (e.g. KERNEL32 matches /// kernel32.dll). /// 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); } }