Because DllInjector enforces matching host/target bitness, a 32-bit caller always handles a 32-bit target. The correct API is native GetThreadContext/SetThreadContext with Context32; the Wow64 APIs are only for 64-bit processes inspecting WOW64 targets, which never happens here. - Collapse the 32-bit path to always use GetThreadContext/SetThreadContext. - Remove the now-unused Wow64GetThreadContext/Wow64SetThreadContext declarations. - Update Context32 doc comment to describe the x86 usage. - ExternalReader access guard now also accepts QueryLimitedInformation. Tests: 207 passing, 4 skipped.
120 lines
4.2 KiB
C#
120 lines
4.2 KiB
C#
using System.Diagnostics;
|
|
using Process = System.Diagnostics.Process;
|
|
using System.Runtime.InteropServices;
|
|
using WhiteMagic.Native;
|
|
|
|
namespace WhiteMagic;
|
|
|
|
/// <summary>
|
|
/// Out-of-process memory reader that accesses the target's memory through
|
|
/// <see cref="NativeMethods.ReadProcessMemory"/> and
|
|
/// <see cref="NativeMethods.WriteProcessMemory"/>.
|
|
/// </summary>
|
|
public sealed class ExternalReader : MemoryBase
|
|
{
|
|
private readonly SafeMemoryHandle _handle;
|
|
private readonly IntPtr _imageBase;
|
|
private readonly bool _is64Bit;
|
|
private readonly int _processId;
|
|
private bool _disposed;
|
|
|
|
/// <summary>
|
|
/// The default access rights: enough to read, write, allocate, query, run a remote
|
|
/// thread, and wait on it. This deliberately omits <see cref="ProcessAccess.AllAccess"/>,
|
|
/// which over-requests and makes <c>OpenProcess</c> fail on protected processes where
|
|
/// these narrower rights would succeed.
|
|
/// </summary>
|
|
public const ProcessAccess DefaultAccess =
|
|
ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation
|
|
| ProcessAccess.QueryInformation | ProcessAccess.CreateThread | ProcessAccess.Synchronize;
|
|
|
|
/// <summary>
|
|
/// Opens a process for external memory access.
|
|
/// </summary>
|
|
/// <param name="process">The target process.</param>
|
|
/// <param name="desiredAccess">The access rights to request. Defaults to
|
|
/// <see cref="DefaultAccess"/>.</param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override IntPtr ImageBase => _imageBase;
|
|
|
|
/// <inheritdoc />
|
|
public override SafeMemoryHandle Handle => _handle;
|
|
|
|
/// <inheritdoc />
|
|
public override bool Is64Bit => _is64Bit;
|
|
|
|
/// <inheritdoc />
|
|
public override int ProcessId => _processId;
|
|
|
|
/// <inheritdoc />
|
|
public override byte[] ReadBytes(IntPtr address, int count, bool isRelative = false)
|
|
{
|
|
if (isRelative)
|
|
address = GetAbsolute(address);
|
|
|
|
return RpmHelper.ReadBytes(_handle, address, count);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int WriteBytes(IntPtr address, ReadOnlySpan<byte> bytes, bool isRelative = false)
|
|
{
|
|
if (isRelative)
|
|
address = GetAbsolute(address);
|
|
|
|
return RpmHelper.WriteBytes(_handle, address, bytes);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dispose()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
_disposed = true;
|
|
base.Dispose();
|
|
}
|
|
}
|
|
}
|