- Example5: Fix format string bugs (alignment specifier placement, SafeMemoryHandle formatting) - Example4: Fix DetourManager.Detour() → Create() in all doc strings - Example3: Fix MemoryBase.CreateFunction() → InProcessInvoker.CreateFunction() in doc strings - Examples 1-5: Correct all runtime errors and API mismatches vs actual WhiteMagic API - README: Fix DetourManager.Detour() examples to use Create(); add missing code fence markers - Add WhiteMagic.Examples project with 5 comprehensive example files (40+ sub-examples) - Add docfx.json and toc.md for DocFX API reference generation - Add 5 conceptual guides: architecture, memory-access, execution-models, hooking, troubleshooting - Ensure zero errors, zero warnings across all projects (net8.0-windows) Doc strings now teach correct APIs; runtime format bugs eliminated; build succeeds.
462 lines
15 KiB
C#
462 lines
15 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using WhiteMagic;
|
|
using WhiteMagic.Discovery;
|
|
|
|
namespace WhiteMagic.Examples;
|
|
|
|
/// <summary>
|
|
/// Example 2: Pattern Scanning
|
|
/// Demonstrates finding patterns in target process memory
|
|
/// </summary>
|
|
public class PatternScanning
|
|
{
|
|
/// <summary>
|
|
/// Target process for examples
|
|
/// </summary>
|
|
private static Process? TargetProcess()
|
|
{
|
|
var processes = Process.GetProcessesByName("notepad");
|
|
if (processes.Length > 0)
|
|
return processes[0];
|
|
|
|
Console.WriteLine("No Notepad process found. Please launch Notepad first.");
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 2.1: Simple pattern scan
|
|
/// </summary>
|
|
public static void SimplePatternScan()
|
|
{
|
|
Console.WriteLine("=== Example 2.1: Simple Pattern Scan ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
if (process.MainModule == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Define a pattern to search for
|
|
// mov rax, [rip+disp] (common in x64)
|
|
byte[] pattern = { 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 };
|
|
string mask = "xxx????"; // 'x' = exact match, '?' = wildcard
|
|
|
|
Console.WriteLine($"Scanning for pattern in module: {process.MainModule.ModuleName}");
|
|
Console.WriteLine($"Pattern bytes: {BitConverter.ToString(pattern)}");
|
|
Console.WriteLine($"Mask: {mask}");
|
|
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
pattern,
|
|
mask,
|
|
process.MainModule
|
|
);
|
|
|
|
if (result != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ Pattern found at: 0x{result:X}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ Pattern not found");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 2.2: Pattern scan with multiple results
|
|
/// </summary>
|
|
public static void MultiplePatternScans()
|
|
{
|
|
Console.WriteLine("\n=== Example 2.2: Multiple Pattern Scans ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
if (process.MainModule == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Multiple patterns to scan
|
|
byte[][] patterns =
|
|
{
|
|
new byte[] { 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 }, // mov rax, [rip+disp]
|
|
new byte[] { 0xE8, 0x00, 0x00, 0x00, 0x00 }, // call rel32
|
|
new byte[] { 0xB8, 0x00, 0x00, 0x00, 0x00 } // mov eax, imm32
|
|
};
|
|
|
|
string[] masks =
|
|
{
|
|
"xxx????",
|
|
"x????",
|
|
"x????"
|
|
};
|
|
|
|
string[] descriptions =
|
|
{
|
|
"mov rax, [rip+disp]",
|
|
"call rel32",
|
|
"mov eax, imm32"
|
|
};
|
|
|
|
Console.WriteLine($"Scanning for {patterns.Length} patterns in {process.MainModule.ModuleName}...");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
|
|
for (int i = 0; i < patterns.Length; i++)
|
|
{
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
patterns[i],
|
|
masks[i],
|
|
process.MainModule
|
|
);
|
|
|
|
Console.WriteLine($"{descriptions[i],-25} {(result != IntPtr.Zero ? $"✓ 0x{result:X}" : "✗ Not found")}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"{descriptions[i],-25} ✗ Error: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 2.3: Pattern scanning in specific region
|
|
/// </summary>
|
|
public static void RegionSpecificScan()
|
|
{
|
|
Console.WriteLine("\n=== Example 2.3: Region-Specific Scan ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
if (process.MainModule == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Scan specific region: .text section (first 64KB of main module)
|
|
IntPtr startAddress = process.MainModule.BaseAddress;
|
|
IntPtr endAddress = startAddress + 0x10000; // 64KB
|
|
|
|
byte[] pattern = { 0x48, 0x8B };
|
|
string mask = "xx"; // Exact match for first 2 bytes
|
|
|
|
Console.WriteLine($"Scanning region: 0x{startAddress:X} - 0x{endAddress:X}");
|
|
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.Find(
|
|
magic.Memory,
|
|
pattern,
|
|
mask,
|
|
startAddress,
|
|
endAddress
|
|
);
|
|
|
|
if (result != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ Pattern found at: 0x{result:X}");
|
|
Console.WriteLine($" Offset from module base: 0x{(result - startAddress):X}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ Pattern not found in region");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Scan failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 2.4: Finding a function signature
|
|
/// </summary>
|
|
public static void FindFunctionSignature()
|
|
{
|
|
Console.WriteLine("\n=== Example 2.4: Finding Function Signature ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
if (process.MainModule == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Common function prologue patterns
|
|
byte[][] prologues =
|
|
{
|
|
// x64 prologue: push rbp; mov rbp, rsp
|
|
new byte[] { 0x55, 0x48, 0x89, 0xE5 },
|
|
|
|
// x64 prologue: push rbp
|
|
new byte[] { 0x55 },
|
|
|
|
// x64 prologue: sub rsp, XX (stack allocation)
|
|
new byte[] { 0x48, 0x83, 0xEC, 0x00 } // last byte varies
|
|
};
|
|
|
|
string[] prologueMasks =
|
|
{
|
|
"xxxx", // Exact match
|
|
"x", // Exact match
|
|
"xxx?" // Last byte wildcard
|
|
};
|
|
|
|
Console.WriteLine($"Scanning for function prologues in {process.MainModule.ModuleName}...");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
|
|
for (int i = 0; i < prologues.Length; i++)
|
|
{
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
prologues[i],
|
|
prologueMasks[i],
|
|
process.MainModule
|
|
);
|
|
|
|
Console.WriteLine($"Prologue {i + 1}: {(result != IntPtr.Zero ? $"✓ Found at 0x{result:X}" : "✗ Not found")}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Prologue {i + 1}: ✗ Error: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 2.5: Pattern scanning with caching
|
|
/// </summary>
|
|
public static void CachedPatternScan()
|
|
{
|
|
Console.WriteLine("\n=== Example 2.5: Cached Pattern Scanning ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
if (process.MainModule == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Create cache
|
|
var cache = new PatternScannerCache(magic.Memory);
|
|
|
|
byte[] pattern = { 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 };
|
|
string mask = "xxx????";
|
|
|
|
Console.WriteLine("Demonstrating cache performance...");
|
|
|
|
// First scan (uncached - reads memory)
|
|
Console.Write(" First scan (uncached): ");
|
|
var watch = System.Diagnostics.Stopwatch.StartNew();
|
|
IntPtr result1 = cache.FindInModuleCached(pattern, mask, process.MainModule);
|
|
watch.Stop();
|
|
Console.WriteLine($"{(result1 != IntPtr.Zero ? $"✓ 0x{result1:X}" : "✗ Not found")} ({watch.ElapsedMilliseconds}ms)");
|
|
|
|
// Second scan (cached - no memory read)
|
|
Console.Write(" Second scan (cached): ");
|
|
watch.Restart();
|
|
IntPtr result2 = cache.FindInModuleCached(pattern, mask, process.MainModule);
|
|
watch.Stop();
|
|
Console.WriteLine($"{(result2 != IntPtr.Zero ? $"✓ 0x{result2:X}" : "✗ Not found")} ({watch.ElapsedMilliseconds}ms)");
|
|
|
|
if (result1 == result2)
|
|
{
|
|
Console.WriteLine(" ✓ Results match and cache is working");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 2.6: Pattern scanning with wildcard flexibility
|
|
/// </summary>
|
|
public static void FlexibleWildcardPatterns()
|
|
{
|
|
Console.WriteLine("\n=== Example 2.6: Flexible Wildcard Patterns ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
if (process.MainModule == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Same pattern, different wildcard masks
|
|
byte[] pattern = { 0x48, 0x8B, 0x05, 0x12, 0x34, 0x56, 0x78 };
|
|
|
|
string[] masks =
|
|
{
|
|
"xxx????", // Last 4 bytes wildcard
|
|
"xxxx???", // Last 3 bytes wildcard
|
|
"xxxxxxx", // Exact match
|
|
"x?x?x?x" // Alternating wildcard
|
|
};
|
|
|
|
Console.WriteLine($"Testing same pattern with different masks...");
|
|
Console.WriteLine($"Pattern: {BitConverter.ToString(pattern)}");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
|
|
for (int i = 0; i < masks.Length; i++)
|
|
{
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
pattern,
|
|
masks[i],
|
|
process.MainModule
|
|
);
|
|
|
|
Console.WriteLine($"Mask \"{masks[i],-10}\" {(result != IntPtr.Zero ? $"✓ Found at 0x{result:X}" : "✗ Not found")}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Mask \"{masks[i],-10}\" ✗ Error: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 2.7: Combining pattern scan with validation
|
|
/// </summary>
|
|
public static void SignatureBasedScanning()
|
|
{
|
|
Console.WriteLine("\n=== Example 2.7: Pattern-Based Function Scanning ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
if (process.MainModule == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Find MessageBoxA pattern in user32.dll (if loaded)
|
|
var user32Module = process.Modules.Cast<System.Diagnostics.ProcessModule>()
|
|
.FirstOrDefault(m => m.ModuleName.Equals("user32.dll", StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (user32Module == null)
|
|
{
|
|
Console.WriteLine("✗ user32.dll not loaded in target process");
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine($"Scanning {user32Module.ModuleName} for function signatures...");
|
|
|
|
// Try to find common export patterns
|
|
byte[] testPattern = { 0x48, 0x8B };
|
|
string mask = "xx";
|
|
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
testPattern,
|
|
mask,
|
|
user32Module
|
|
);
|
|
|
|
if (result != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ Found pattern at 0x{result:X}");
|
|
Console.WriteLine($" Module base: 0x{user32Module.BaseAddress:X}");
|
|
Console.WriteLine($" Offset: 0x{(result - user32Module.BaseAddress):X}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ Pattern not found");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Scan failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 2.8: Pattern validation and error handling
|
|
/// </summary>
|
|
public static void PatternValidation()
|
|
{
|
|
Console.WriteLine("\n=== Example 2.8: Pattern Validation ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
if (process.MainModule == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Test various invalid/edge case patterns
|
|
Console.WriteLine("Testing edge cases and validation...");
|
|
|
|
// Empty pattern
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
Array.Empty<byte>(),
|
|
null,
|
|
process.MainModule
|
|
);
|
|
Console.WriteLine("✗ Empty pattern should throw exception");
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
Console.WriteLine("✓ Empty pattern correctly rejected");
|
|
}
|
|
|
|
// Mismatched pattern and mask length
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
new byte[] { 0x48, 0x8B },
|
|
"x", // Mask too short
|
|
process.MainModule
|
|
);
|
|
Console.WriteLine("✗ Mismatched mask length should throw exception");
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
Console.WriteLine("✓ Mismatched mask length correctly rejected");
|
|
}
|
|
|
|
// Invalid mask characters
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
new byte[] { 0x48, 0x8B },
|
|
"ab", // Invalid mask characters
|
|
process.MainModule
|
|
);
|
|
Console.WriteLine("✗ Invalid mask characters should throw exception");
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
Console.WriteLine("✓ Invalid mask characters correctly rejected");
|
|
}
|
|
|
|
Console.WriteLine("✓ All validation tests passed");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run all pattern scanning examples
|
|
/// </summary>
|
|
public static void RunAll()
|
|
{
|
|
Console.WriteLine("╔════════════════════════════════════════════════════════════╗");
|
|
Console.WriteLine("║ WhiteMagic Example 2: Pattern Scanning ║");
|
|
Console.WriteLine("╚════════════════════════════════════════════════════════════╝");
|
|
|
|
SimplePatternScan();
|
|
MultiplePatternScans();
|
|
RegionSpecificScan();
|
|
FindFunctionSignature();
|
|
CachedPatternScan();
|
|
FlexibleWildcardPatterns();
|
|
SignatureBasedScanning();
|
|
PatternValidation();
|
|
|
|
Console.WriteLine("\n✓ All pattern scanning examples completed!");
|
|
}
|
|
}
|