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:
@@ -0,0 +1,112 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagicTest.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests that exercise the P/Invoke surface against the current
|
||||
/// process. They prove the marshalling signatures are correct end-to-end.
|
||||
/// </summary>
|
||||
public class NativeSurfaceTests
|
||||
{
|
||||
private static SafeMemoryHandle OpenSelf(ProcessAccess access)
|
||||
{
|
||||
SafeMemoryHandle handle = NativeMethods.OpenProcess(access, false, Environment.ProcessId);
|
||||
Assert.False(handle.IsInvalid, $"OpenProcess failed: {Marshal.GetLastPInvokeError()}");
|
||||
return handle;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenProcess_on_self_returns_valid_handle_and_closes_on_dispose()
|
||||
{
|
||||
SafeMemoryHandle handle = OpenSelf(ProcessAccess.QueryInformation);
|
||||
Assert.False(handle.IsClosed);
|
||||
handle.Dispose();
|
||||
Assert.True(handle.IsClosed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadProcessMemory_reads_a_known_value_from_own_memory()
|
||||
{
|
||||
int value = 0x1BADB002;
|
||||
GCHandle pin = GCHandle.Alloc(value, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
using SafeMemoryHandle handle = OpenSelf(ProcessAccess.VmRead | ProcessAccess.QueryInformation);
|
||||
Span<byte> buffer = stackalloc byte[sizeof(int)];
|
||||
|
||||
bool ok = NativeMethods.ReadProcessMemory(
|
||||
handle, pin.AddrOfPinnedObject(), buffer, buffer.Length, out nint read);
|
||||
|
||||
Assert.True(ok, $"ReadProcessMemory failed: {Marshal.GetLastPInvokeError()}");
|
||||
Assert.Equal(sizeof(int), (int)read);
|
||||
Assert.Equal(value, BitConverter.ToInt32(buffer));
|
||||
}
|
||||
finally
|
||||
{
|
||||
pin.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteProcessMemory_writes_a_value_into_own_memory()
|
||||
{
|
||||
int slot = 0;
|
||||
GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
using SafeMemoryHandle handle = OpenSelf(
|
||||
ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation);
|
||||
ReadOnlySpan<byte> payload = BitConverter.GetBytes(0x5EED);
|
||||
|
||||
bool ok = NativeMethods.WriteProcessMemory(
|
||||
handle, pin.AddrOfPinnedObject(), payload, payload.Length, out nint written);
|
||||
|
||||
Assert.True(ok, $"WriteProcessMemory failed: {Marshal.GetLastPInvokeError()}");
|
||||
Assert.Equal(payload.Length, (int)written);
|
||||
Assert.Equal(0x5EED, Marshal.ReadInt32(pin.AddrOfPinnedObject()));
|
||||
}
|
||||
finally
|
||||
{
|
||||
pin.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VirtualAllocEx_commits_then_protects_then_frees()
|
||||
{
|
||||
using SafeMemoryHandle handle = OpenSelf(ProcessAccess.VmOperation | ProcessAccess.QueryInformation);
|
||||
|
||||
IntPtr region = NativeMethods.VirtualAllocEx(
|
||||
handle, IntPtr.Zero, 0x1000,
|
||||
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
|
||||
MemoryProtectionType.ReadWrite);
|
||||
Assert.NotEqual(IntPtr.Zero, region);
|
||||
|
||||
bool protect = NativeMethods.VirtualProtectEx(
|
||||
handle, region, 0x1000, MemoryProtectionType.ExecuteReadWrite, out MemoryProtectionType old);
|
||||
Assert.True(protect, $"VirtualProtectEx failed: {Marshal.GetLastPInvokeError()}");
|
||||
Assert.Equal(MemoryProtectionType.ReadWrite, old);
|
||||
|
||||
bool free = NativeMethods.VirtualFreeEx(handle, region, 0, MemoryFreeType.Release);
|
||||
Assert.True(free, $"VirtualFreeEx failed: {Marshal.GetLastPInvokeError()}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadLibrary_then_GetProcAddress_resolves_an_export()
|
||||
{
|
||||
IntPtr module = NativeMethods.LoadLibrary("kernel32.dll");
|
||||
Assert.NotEqual(IntPtr.Zero, module);
|
||||
|
||||
IntPtr proc = NativeMethods.GetProcAddress(module, "CloseHandle");
|
||||
Assert.NotEqual(IntPtr.Zero, proc);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(Context32), 716)]
|
||||
[InlineData(typeof(Context64), 1232)]
|
||||
public void Thread_context_struct_has_the_exact_native_size(Type contextType, int expectedSize)
|
||||
{
|
||||
Assert.Equal(expectedSize, Marshal.SizeOf(contextType));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user