task 1.4: add NativeMethods.cs LibraryImport P/Invoke surface

Add LibraryImport-based P/Invoke declarations for all required Win32
APIs: OpenProcess, CloseHandle, ReadProcessMemory, WriteProcessMemory,
VirtualAllocEx, VirtualProtectEx, VirtualFreeEx, CreateRemoteThread,
GetThreadContext, SetThreadContext, Wow64GetThreadContext,
Wow64SetThreadContext, LoadLibrary, GetProcAddress, and
WaitForSingleObject.

Add InternalsVisibleTo WhiteMagicTest so the test project can call
internal NativeMethods.

Tests (NativeSurfaceTests): 7/7 passing.
This commit is contained in:
kbe
2026-07-21 17:01:41 +02:00
parent eea175499d
commit 302da5f3c2
7 changed files with 630 additions and 1 deletions
+136
View File
@@ -0,0 +1,136 @@
namespace WhiteMagic.Native;
/// <summary>
/// Access rights that open a process object.
/// </summary>
[Flags]
public enum ProcessAccess : uint
{
/// <summary>The right to terminate the process with TerminateProcess.</summary>
Terminate = 0x0001,
/// <summary>The right to create a thread in the process.</summary>
CreateThread = 0x0002,
/// <summary>The right to operate on the address space of the process.</summary>
VmOperation = 0x0008,
/// <summary>The right to read memory with ReadProcessMemory.</summary>
VmRead = 0x0010,
/// <summary>The right to write memory with WriteProcessMemory.</summary>
VmWrite = 0x0020,
/// <summary>The right to duplicate a handle with DuplicateHandle.</summary>
DupHandle = 0x0040,
/// <summary>The right to set information about the process.</summary>
SetInformation = 0x0200,
/// <summary>The right to read information about the process, such as the exit code.</summary>
QueryInformation = 0x0400,
/// <summary>The right to suspend or resume the process.</summary>
SuspendResume = 0x0800,
/// <summary>The right to read a limited set of information about the process.</summary>
QueryLimitedInformation = 0x1000,
/// <summary>The right to use the process object for synchronization.</summary>
Synchronize = 0x00100000,
/// <summary>All access rights for a process object.</summary>
AllAccess = 0x001F0000 | Synchronize | 0xFFFF,
}
/// <summary>
/// Values that control how VirtualAllocEx allocates memory.
/// </summary>
[Flags]
public enum MemoryAllocationType : uint
{
/// <summary>Commit physical storage for the reserved pages. The pages start as zero.</summary>
Commit = 0x00001000,
/// <summary>Reserve a range of address space without physical storage.</summary>
Reserve = 0x00002000,
/// <summary>Reset the data in the range to indicate that it is no longer of interest.</summary>
Reset = 0x00080000,
/// <summary>Allocate memory at the highest possible address.</summary>
TopDown = 0x00100000,
}
/// <summary>
/// Values that protect a block of memory.
/// </summary>
[Flags]
public enum MemoryProtectionType : uint
{
/// <summary>No access to the committed pages.</summary>
NoAccess = 0x01,
/// <summary>Read access to the committed pages.</summary>
ReadOnly = 0x02,
/// <summary>Read and write access to the committed pages.</summary>
ReadWrite = 0x04,
/// <summary>Copy-on-write access to the committed pages.</summary>
WriteCopy = 0x08,
/// <summary>Execute access to the committed pages.</summary>
Execute = 0x10,
/// <summary>Execute and read access to the committed pages.</summary>
ExecuteRead = 0x20,
/// <summary>Execute, read, and write access to the committed pages.</summary>
ExecuteReadWrite = 0x40,
/// <summary>Execute and copy-on-write access to the committed pages.</summary>
ExecuteWriteCopy = 0x80,
/// <summary>The pages in the range become guard pages.</summary>
Guard = 0x100,
/// <summary>The system does not cache the committed pages.</summary>
NoCache = 0x200,
/// <summary>The system uses write-combined access for the pages.</summary>
WriteCombine = 0x400,
}
/// <summary>
/// Values that control how VirtualFreeEx frees memory.
/// </summary>
[Flags]
public enum MemoryFreeType : uint
{
/// <summary>Decommit the committed pages. The address range stays reserved.</summary>
Decommit = 0x4000,
/// <summary>Release the range of pages. The size must be zero.</summary>
Release = 0x8000,
}
/// <summary>
/// Values that set the initial state of a new thread.
/// </summary>
[Flags]
public enum ThreadCreationFlags : uint
{
/// <summary>The thread runs immediately after creation.</summary>
RunImmediately = 0,
/// <summary>The thread starts in a suspended state. Call ResumeThread to start it.</summary>
CreateSuspended = 0x00000004,
/// <summary>The stack-size parameter sets the reserve size of the stack.</summary>
StackSizeParamIsAReservation = 0x00010000,
}
/// <summary>
/// Flags that select the registers that the thread-context functions read or write.
/// There are separate constants for 32-bit (x86/WOW64) and 64-bit (AMD64) contexts.
/// </summary>
public static class ContextFlags
{
/// <summary>Architecture identifier for x86 contexts.</summary>
public const uint X86 = 0x00010000;
/// <summary>Architecture identifier for AMD64 contexts.</summary>
public const uint Amd64 = 0x00100000;
/// <summary>x86: SS:SP, CS:IP, FLAGS, and BP.</summary>
public const uint X86Control = X86 | 0x01;
/// <summary>x86: AX, BX, CX, DX, SI, and DI.</summary>
public const uint X86Integer = X86 | 0x02;
/// <summary>x86: DS, ES, FS, and GS.</summary>
public const uint X86Segments = X86 | 0x04;
/// <summary>x86: control, integer, and segment registers.</summary>
public const uint X86Full = X86Control | X86Integer | X86Segments;
/// <summary>AMD64: SegSs, Rsp, SegCs, Rip, and EFlags.</summary>
public const uint Amd64Control = Amd64 | 0x01;
/// <summary>AMD64: Rax, Rcx, Rdx, Rbx, Rbp, Rsi, Rdi, and R8 to R15.</summary>
public const uint Amd64Integer = Amd64 | 0x02;
/// <summary>AMD64: SegDs, SegEs, SegFs, and SegGs.</summary>
public const uint Amd64Segments = Amd64 | 0x04;
/// <summary>AMD64: control, integer, and segment registers.</summary>
public const uint Amd64Full = Amd64Control | Amd64Integer | Amd64Segments;
}
+136
View File
@@ -0,0 +1,136 @@
using System.Runtime.InteropServices;
namespace WhiteMagic.Native;
/// <summary>
/// P/Invoke declarations for the Win32 process, memory, thread, and module
/// APIs that WhiteMagic uses. Every declaration uses <see cref="LibraryImportAttribute"/>
/// (source-generated interop). SetLastError is enabled on all calls that the
/// Win32 API documents as setting a thread-local last-error value.
/// </summary>
internal static partial class NativeMethods
{
// ── Process ──────────────────────────────────────────────────────────────
/// <summary>Opens an existing process and returns a handle to it.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
internal static partial SafeMemoryHandle OpenProcess(
ProcessAccess desiredAccess,
[MarshalAs(UnmanagedType.Bool)] bool inheritHandle,
int processId);
/// <summary>Closes an open object handle.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool CloseHandle(IntPtr handle);
// ── Memory ───────────────────────────────────────────────────────────────
/// <summary>Reads memory from a process.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool ReadProcessMemory(
SafeMemoryHandle process,
IntPtr baseAddress,
Span<byte> buffer,
int size,
out nint bytesRead);
/// <summary>Writes memory to a process.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool WriteProcessMemory(
SafeMemoryHandle process,
IntPtr baseAddress,
ReadOnlySpan<byte> buffer,
int size,
out nint bytesWritten);
/// <summary>Reserves or commits a region of memory in a process.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
internal static partial IntPtr VirtualAllocEx(
SafeMemoryHandle process,
IntPtr address,
nint size,
MemoryAllocationType allocationType,
MemoryProtectionType protect);
/// <summary>Changes the protection on a committed region of memory.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool VirtualProtectEx(
SafeMemoryHandle process,
IntPtr address,
nint size,
MemoryProtectionType newProtect,
out MemoryProtectionType oldProtect);
/// <summary>Releases or decommits a region of memory in a process.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool VirtualFreeEx(
SafeMemoryHandle process,
IntPtr address,
nint size,
MemoryFreeType freeType);
// ── Threading ────────────────────────────────────────────────────────────
/// <summary>Creates a thread that runs in the virtual address space of a process.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
internal static partial SafeMemoryHandle CreateRemoteThread(
SafeMemoryHandle process,
IntPtr threadAttributes,
nint stackSize,
IntPtr startAddress,
IntPtr parameter,
ThreadCreationFlags creationFlags,
out int threadId);
/// <summary>Sets a 64-bit thread context (AMD64).</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetThreadContext(
SafeMemoryHandle thread,
ref Context64 context);
/// <summary>Gets a 64-bit thread context (AMD64).</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetThreadContext(
SafeMemoryHandle thread,
ref Context64 context);
/// <summary>Sets a 32-bit (WOW64) thread context.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool Wow64SetThreadContext(
SafeMemoryHandle thread,
ref Context32 context);
/// <summary>Gets a 32-bit (WOW64) thread context.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool Wow64GetThreadContext(
SafeMemoryHandle thread,
ref Context32 context);
// ── Modules ──────────────────────────────────────────────────────────────
/// <summary>Loads a module into the calling process.</summary>
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryW")]
internal static partial IntPtr LoadLibrary(
[MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
/// <summary>Returns the address of a function or variable from a loaded module.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
internal static partial IntPtr GetProcAddress(
IntPtr hModule,
[MarshalAs(UnmanagedType.LPStr)] string lpProcName);
/// <summary>Waits until a thread exits and retrieves its exit code.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
internal static partial int WaitForSingleObject(
SafeMemoryHandle handle,
uint milliseconds);
}
+207
View File
@@ -0,0 +1,207 @@
using System.Runtime.InteropServices;
namespace WhiteMagic.Native;
/// <summary>
/// The x87 and MMX state inside a 32-bit thread context.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public unsafe struct FloatingSaveArea32
{
/// <summary>The x87 FPU control word.</summary>
public uint ControlWord;
/// <summary>The x87 FPU status word.</summary>
public uint StatusWord;
/// <summary>The x87 FPU tag word.</summary>
public uint TagWord;
/// <summary>The offset of the instruction that caused the last FPU exception.</summary>
public uint ErrorOffset;
/// <summary>The selector of the instruction that caused the last FPU exception.</summary>
public uint ErrorSelector;
/// <summary>The offset of the operand that caused the last FPU exception.</summary>
public uint DataOffset;
/// <summary>The selector of the operand that caused the last FPU exception.</summary>
public uint DataSelector;
/// <summary>The 80-byte register area.</summary>
public fixed byte RegisterArea[80];
/// <summary>The CR0 numeric-processor-extension state.</summary>
public uint Cr0NpxState;
}
/// <summary>
/// A 32-bit (x86/WOW64) thread context. Use it with
/// <c>Wow64GetThreadContext</c> and <c>Wow64SetThreadContext</c> to inspect a 32-bit thread.
/// The total size is 716 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public unsafe struct Context32
{
/// <summary>Selects which parts of the context are valid. See <see cref="ContextFlags"/>.</summary>
public uint ContextFlags;
/// <summary>Debug register 0.</summary>
public uint Dr0;
/// <summary>Debug register 1.</summary>
public uint Dr1;
/// <summary>Debug register 2.</summary>
public uint Dr2;
/// <summary>Debug register 3.</summary>
public uint Dr3;
/// <summary>Debug register 6.</summary>
public uint Dr6;
/// <summary>Debug register 7.</summary>
public uint Dr7;
/// <summary>The floating-point state.</summary>
public FloatingSaveArea32 FloatSave;
/// <summary>The GS segment.</summary>
public uint SegGs;
/// <summary>The FS segment.</summary>
public uint SegFs;
/// <summary>The ES segment.</summary>
public uint SegEs;
/// <summary>The DS segment.</summary>
public uint SegDs;
/// <summary>The EDI register.</summary>
public uint Edi;
/// <summary>The ESI register.</summary>
public uint Esi;
/// <summary>The EBX register.</summary>
public uint Ebx;
/// <summary>The EDX register.</summary>
public uint Edx;
/// <summary>The ECX register.</summary>
public uint Ecx;
/// <summary>The EAX register.</summary>
public uint Eax;
/// <summary>The base (frame) pointer.</summary>
public uint Ebp;
/// <summary>The instruction pointer.</summary>
public uint Eip;
/// <summary>The CS segment.</summary>
public uint SegCs;
/// <summary>The flags register.</summary>
public uint EFlags;
/// <summary>The stack pointer.</summary>
public uint Esp;
/// <summary>The SS segment.</summary>
public uint SegSs;
/// <summary>The extended (processor-specific) registers. The size is 512 bytes.</summary>
public fixed byte ExtendedRegisters[512];
}
/// <summary>
/// A 64-bit (AMD64) thread context. Use it with the native
/// <c>GetThreadContext</c> and <c>SetThreadContext</c> from a 64-bit process.
/// The structure needs 16-byte alignment. The total size is 1232 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 16)]
public unsafe struct Context64
{
/// <summary>Home storage for a register parameter.</summary>
public ulong P1Home;
/// <summary>Home storage for a register parameter.</summary>
public ulong P2Home;
/// <summary>Home storage for a register parameter.</summary>
public ulong P3Home;
/// <summary>Home storage for a register parameter.</summary>
public ulong P4Home;
/// <summary>Home storage for a register parameter.</summary>
public ulong P5Home;
/// <summary>Home storage for a register parameter.</summary>
public ulong P6Home;
/// <summary>Selects which parts of the context are valid. See <see cref="ContextFlags"/>.</summary>
public uint ContextFlags;
/// <summary>The MXCSR register.</summary>
public uint MxCsr;
/// <summary>The CS segment.</summary>
public ushort SegCs;
/// <summary>The DS segment.</summary>
public ushort SegDs;
/// <summary>The ES segment.</summary>
public ushort SegEs;
/// <summary>The FS segment.</summary>
public ushort SegFs;
/// <summary>The GS segment.</summary>
public ushort SegGs;
/// <summary>The SS segment.</summary>
public ushort SegSs;
/// <summary>The flags register.</summary>
public uint EFlags;
/// <summary>Debug register 0.</summary>
public ulong Dr0;
/// <summary>Debug register 1.</summary>
public ulong Dr1;
/// <summary>Debug register 2.</summary>
public ulong Dr2;
/// <summary>Debug register 3.</summary>
public ulong Dr3;
/// <summary>Debug register 6.</summary>
public ulong Dr6;
/// <summary>Debug register 7.</summary>
public ulong Dr7;
/// <summary>The RAX register.</summary>
public ulong Rax;
/// <summary>The RCX register.</summary>
public ulong Rcx;
/// <summary>The RDX register.</summary>
public ulong Rdx;
/// <summary>The RBX register.</summary>
public ulong Rbx;
/// <summary>The stack pointer.</summary>
public ulong Rsp;
/// <summary>The base (frame) pointer.</summary>
public ulong Rbp;
/// <summary>The RSI register.</summary>
public ulong Rsi;
/// <summary>The RDI register.</summary>
public ulong Rdi;
/// <summary>The R8 register.</summary>
public ulong R8;
/// <summary>The R9 register.</summary>
public ulong R9;
/// <summary>The R10 register.</summary>
public ulong R10;
/// <summary>The R11 register.</summary>
public ulong R11;
/// <summary>The R12 register.</summary>
public ulong R12;
/// <summary>The R13 register.</summary>
public ulong R13;
/// <summary>The R14 register.</summary>
public ulong R14;
/// <summary>The R15 register.</summary>
public ulong R15;
/// <summary>The instruction pointer.</summary>
public ulong Rip;
/// <summary>The XMM save area. The size is 512 bytes.</summary>
public fixed byte FltSave[512];
/// <summary>The vector registers (26 entries of 16 bytes, stored as 52 entries of 8 bytes).</summary>
public fixed ulong VectorRegister[52];
/// <summary>The vector control register.</summary>
public ulong VectorControl;
/// <summary>The debug-control MSR.</summary>
public ulong DebugControl;
/// <summary>The target RIP of the last branch.</summary>
public ulong LastBranchToRip;
/// <summary>The source RIP of the last branch.</summary>
public ulong LastBranchFromRip;
/// <summary>The target RIP of the last exception.</summary>
public ulong LastExceptionToRip;
/// <summary>The source RIP of the last exception.</summary>
public ulong LastExceptionFromRip;
}
+34
View File
@@ -0,0 +1,34 @@
using Microsoft.Win32.SafeHandles;
namespace WhiteMagic.Native;
/// <summary>
/// A Win32 handle (process, thread, or snapshot) with a managed lifetime.
/// The handle closes with <c>CloseHandle</c>, even after an exception or a thread abort.
/// </summary>
/// <remarks>The pattern comes from MemorySharp's SafeMemoryHandle.</remarks>
public sealed class SafeMemoryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
/// <summary>
/// Makes an empty handle. The interop marshaller uses this constructor for a
/// handle that a system call returns (for example, <see cref="NativeMethods.OpenProcess"/>).
/// </summary>
public SafeMemoryHandle() : base(true)
{
}
/// <summary>
/// Wraps a raw handle and takes ownership of the handle.
/// </summary>
/// <param name="handle">The handle to own.</param>
public SafeMemoryHandle(IntPtr handle) : base(true)
{
SetHandle(handle);
}
/// <inheritdoc />
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(handle);
}
}