113 lines
4.0 KiB
C#
113 lines
4.0 KiB
C#
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));
|
|
}
|
|
}
|