Implement core diagnostic memory layer, execution helpers, and high-level facade slices
Implemented: - Core: UTF-16 ReadString boundary/alignment fix, target bitness and process id on MemoryBase - function interception: PatchManager, DetourManager, InstructionAnalyzer, MainThreadDispatcher - Execution: BackgroundTaskExecutor, InProcessInvoker - High-level: Magic facade, RemotePointer, async wrappers - Discovery/external code loading/Window groundwork (PEB/TEB, pattern scanning, raw allocations, DLL external code loading, window/input) Tests: 180 passing, 4 integration/interactive tests skipped.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using WhiteMagic;
|
||||
using WhiteMagic.Discovery;
|
||||
using WhiteMagicTest;
|
||||
|
||||
namespace WhiteMagicTest.Discovery;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="PatternScanner"/>.
|
||||
/// </summary>
|
||||
public class PatternScannerTests
|
||||
{
|
||||
private static InProcessReader CreateReader()
|
||||
{
|
||||
return new InProcessReader();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_exact_pattern_returns_correct_address()
|
||||
{
|
||||
using var reader = CreateReader();
|
||||
|
||||
// Create a buffer with known bytes
|
||||
byte[] buffer = new byte[256];
|
||||
buffer[10] = 0xDE;
|
||||
buffer[11] = 0xAD;
|
||||
buffer[12] = 0xBE;
|
||||
buffer[13] = 0xEF;
|
||||
|
||||
GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
IntPtr addr = pin.AddrOfPinnedObject();
|
||||
IntPtr end = addr + buffer.Length;
|
||||
|
||||
// Search for the exact pattern
|
||||
byte[] pattern = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
IntPtr found = PatternScanner.Find(reader, pattern, null, addr, end);
|
||||
|
||||
Assert.Equal(addr + 10, found);
|
||||
}
|
||||
finally
|
||||
{
|
||||
pin.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_with_wildcard_mask_ignores_wildcard_bytes()
|
||||
{
|
||||
using var reader = CreateReader();
|
||||
|
||||
// Create a buffer with known bytes
|
||||
byte[] buffer = new byte[256];
|
||||
buffer[20] = 0x12;
|
||||
buffer[21] = 0x34; // This byte is wildcard
|
||||
buffer[22] = 0x56;
|
||||
buffer[23] = 0x78;
|
||||
|
||||
GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
IntPtr addr = pin.AddrOfPinnedObject();
|
||||
IntPtr end = addr + buffer.Length;
|
||||
|
||||
// Search with wildcard mask (x = match, ? = wildcard)
|
||||
byte[] pattern = { 0x12, 0x00, 0x56, 0x78 };
|
||||
string mask = "x?xx"; // Second byte is wildcard
|
||||
IntPtr found = PatternScanner.Find(reader, pattern, mask, addr, end);
|
||||
|
||||
Assert.Equal(addr + 20, found);
|
||||
}
|
||||
finally
|
||||
{
|
||||
pin.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_pattern_not_found_returns_zero()
|
||||
{
|
||||
using var reader = CreateReader();
|
||||
|
||||
// Create a buffer without the target pattern
|
||||
byte[] buffer = new byte[256];
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
buffer[i] = 0xAA;
|
||||
|
||||
GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
IntPtr addr = pin.AddrOfPinnedObject();
|
||||
IntPtr end = addr + buffer.Length;
|
||||
|
||||
// Search for pattern that doesn't exist
|
||||
byte[] pattern = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
IntPtr found = PatternScanner.Find(reader, pattern, null, addr, end);
|
||||
|
||||
Assert.Equal(IntPtr.Zero, found);
|
||||
}
|
||||
finally
|
||||
{
|
||||
pin.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_empty_pattern_throws()
|
||||
{
|
||||
using var reader = CreateReader();
|
||||
|
||||
byte[] pattern = Array.Empty<byte>();
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
PatternScanner.Find(reader, pattern, null, IntPtr.Zero, (IntPtr)1000));
|
||||
|
||||
Assert.Contains("Pattern cannot be empty", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_mask_length_mismatch_throws()
|
||||
{
|
||||
using var reader = CreateReader();
|
||||
|
||||
byte[] pattern = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
string mask = "xxx"; // Wrong length
|
||||
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
PatternScanner.Find(reader, pattern, mask, IntPtr.Zero, (IntPtr)1000));
|
||||
|
||||
Assert.Contains("Mask length", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_invalid_mask_char_throws()
|
||||
{
|
||||
using var reader = CreateReader();
|
||||
|
||||
byte[] pattern = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
string mask = "axxx"; // 'a' is invalid
|
||||
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
PatternScanner.Find(reader, pattern, mask, IntPtr.Zero, (IntPtr)1000));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_null_mask_treats_all_as_exact()
|
||||
{
|
||||
using var reader = CreateReader();
|
||||
|
||||
byte[] buffer = new byte[256];
|
||||
buffer[50] = 0xAB;
|
||||
buffer[51] = 0xCD;
|
||||
|
||||
GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
IntPtr addr = pin.AddrOfPinnedObject();
|
||||
IntPtr end = addr + buffer.Length;
|
||||
|
||||
// Null mask should behave like "xx" (exact match)
|
||||
byte[] pattern = { 0xAB, 0xCD };
|
||||
IntPtr found = PatternScanner.Find(reader, pattern, null, addr, end);
|
||||
|
||||
Assert.Equal(addr + 50, found);
|
||||
}
|
||||
finally
|
||||
{
|
||||
pin.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindInModule_scans_current_process_module()
|
||||
{
|
||||
using var reader = CreateReader();
|
||||
|
||||
// Get the current process's main module
|
||||
var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
|
||||
var mainModule = currentProcess.MainModule;
|
||||
Assert.NotNull(mainModule);
|
||||
|
||||
// MZ header is always at the start of the main module
|
||||
byte[] pattern = { 0x4D, 0x5A };
|
||||
IntPtr found = PatternScanner.FindInModule(reader, pattern, null, mainModule);
|
||||
|
||||
Assert.Equal(mainModule.BaseAddress, found);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user