using System.Diagnostics; using Process = System.Diagnostics.Process; using System.Runtime.InteropServices; using WhiteMagic.Native; namespace WhiteMagic; /// /// Out-of-process memory reader that accesses the target's memory through /// and /// . /// public sealed class ExternalReader : MemoryBase { private readonly SafeMemoryHandle _handle; private readonly IntPtr _imageBase; private readonly bool _is64Bit; private readonly int _processId; private bool _disposed; /// /// The default access rights: enough to read, write, allocate, query, run a remote /// thread, and wait on it. This deliberately omits , /// which over-requests and makes OpenProcess fail on protected processes where /// these narrower rights would succeed. /// public const ProcessAccess DefaultAccess = ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation | ProcessAccess.CreateThread | ProcessAccess.Synchronize; /// /// Opens a process for external memory access. /// /// The target process. /// The access rights to request. Defaults to /// . public ExternalReader(System.Diagnostics.Process process, ProcessAccess desiredAccess = DefaultAccess) { _processId = process.Id; const ProcessAccess queryAccess = ProcessAccess.QueryInformation | ProcessAccess.QueryLimitedInformation; if ((desiredAccess & queryAccess) == 0) { throw new ArgumentException( "ExternalReader requires ProcessAccess.QueryInformation or ProcessAccess.QueryLimitedInformation to determine target bitness.", nameof(desiredAccess)); } _handle = NativeMethods.OpenProcess(desiredAccess, false, _processId); if (_handle.IsInvalid) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException( $"OpenProcess failed for PID {_processId}: error {error}"); } // Derive target bitness. A 64-bit host sees a 32-bit target as WOW64. // A 32-bit host can only open 32-bit targets. if (!NativeMethods.IsWow64Process(_handle, out bool wow64)) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException( $"IsWow64Process failed for PID {_processId}: error {error}."); } _is64Bit = Environment.Is64BitProcess && !wow64; // Process.MainModule throws Win32Exception for a bitness-mismatched or protected // target. A missing image base must not sink the whole reader — callers can still // use absolute addresses when ImageBase is unknown. try { _imageBase = process.MainModule?.BaseAddress ?? IntPtr.Zero; } catch (System.ComponentModel.Win32Exception) { _imageBase = IntPtr.Zero; } } /// public override IntPtr ImageBase => _imageBase; /// public override SafeMemoryHandle Handle => _handle; /// public override bool Is64Bit => _is64Bit; /// public override int ProcessId => _processId; /// public override byte[] ReadBytes(IntPtr address, int count, bool isRelative = false) { if (isRelative) address = GetAbsolute(address); return RpmHelper.ReadBytes(_handle, address, count); } /// public override int WriteBytes(IntPtr address, ReadOnlySpan bytes, bool isRelative = false) { if (isRelative) address = GetAbsolute(address); return RpmHelper.WriteBytes(_handle, address, bytes); } /// public override void Dispose() { if (!_disposed) { _disposed = true; base.Dispose(); } } }