Initial commit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
using System.Diagnostics;
|
||||
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 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(Process process, ProcessAccess desiredAccess = DefaultAccess)
|
||||
{
|
||||
_handle = NativeMethods.OpenProcess(desiredAccess, false, process.Id);
|
||||
if (_handle.IsInvalid)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException(
|
||||
$"OpenProcess failed for PID {process.Id}: error {error}");
|
||||
}
|
||||
|
||||
// Process.MainModule throws Win32Exception for a bitness-mismatched or protected
|
||||
// target; a missing image base must not sink the whole reader.
|
||||
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 byte[] ReadBytes(IntPtr address, int count, bool isRelative = false)
|
||||
{
|
||||
if (isRelative)
|
||||
address = GetAbsolute(address);
|
||||
|
||||
byte[] buffer = new byte[count];
|
||||
if (!NativeMethods.ReadProcessMemory(_handle, address, buffer, count, out nint bytesRead))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if ((int)bytesRead != count)
|
||||
{
|
||||
Array.Resize(ref buffer, (int)bytesRead);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int WriteBytes(IntPtr address, ReadOnlySpan<byte> bytes, bool isRelative = false)
|
||||
{
|
||||
if (isRelative)
|
||||
address = GetAbsolute(address);
|
||||
|
||||
if (!NativeMethods.WriteProcessMemory(_handle, address, bytes, bytes.Length, out nint written))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)written;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
_handle.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user