Fix API documentation and examples; add comprehensive documentation suite

- 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.
This commit is contained in:
kbe
2026-07-22 22:26:07 +02:00
parent 040a51bf03
commit 6300bebe33
17 changed files with 4906 additions and 1 deletions
@@ -0,0 +1,321 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using WhiteMagic;
using WhiteMagic.Execution;
namespace WhiteMagic.Examples;
/// <summary>
/// Sample struct for demonstrating struct marshalling
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct PlayerInfo
{
public int Health;
public float PositionX;
public float PositionY;
public int Level;
public uint Experience;
}
/// <summary>
/// Example 1: Basic Memory Operations
/// Demonstrates reading and writing memory of various types
/// </summary>
public class BasicMemoryOperations
{
/// <summary>
/// Target process for examples (Notepad is safe and always available)
/// </summary>
private static Process? TargetProcess()
{
// Try to find Notepad
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 1.1: Reading and writing primitive types
/// </summary>
public static void ReadWritePrimitives()
{
Console.WriteLine("=== Example 1.1: Read/Write Primitives ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
// Example: Read an integer from a hypothetical address
// (In real use, you'd find the actual address via pattern scanning or offsets)
IntPtr testAddress = magic.Memory.ImageBase + 0x1000;
// Read different primitive types
try
{
int intValue = magic.Memory.Read<int>(testAddress);
Console.WriteLine($"Read int from 0x{testAddress:X}: {intValue}");
float floatValue = magic.Memory.Read<float>(testAddress);
Console.WriteLine($"Read float from 0x{testAddress:X}: {floatValue}");
double doubleValue = magic.Memory.Read<double>(testAddress);
Console.WriteLine($"Read double from 0x{testAddress:X}: {doubleValue}");
bool boolValue = magic.Memory.Read<bool>(testAddress);
Console.WriteLine($"Read bool from 0x{testAddress:X}: {boolValue}");
}
catch (Exception ex)
{
Console.WriteLine($"Note: Read failed (address may be invalid): {ex.Message}");
}
// Write example
bool writeSuccess = magic.Memory.Write(testAddress, 42);
Console.WriteLine($"Write to 0x{testAddress:X}: {(writeSuccess ? "Success" : "Failed")}");
}
/// <summary>
/// Example 1.2: Reading and writing arrays
/// </summary>
public static void ReadWriteArrays()
{
Console.WriteLine("\n=== Example 1.2: Read/Write Arrays ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
IntPtr arrayAddress = magic.Memory.ImageBase + 0x2000;
// Read array of integers
try
{
int[] intArray = magic.Memory.Read<int>(arrayAddress, 10);
Console.WriteLine($"Read {intArray.Length} integers from 0x{arrayAddress:X}");
Console.WriteLine($"First few values: {string.Join(", ", intArray[..Math.Min(5, intArray.Length)])}");
}
catch (Exception ex)
{
Console.WriteLine($"Note: Array read failed (address may be invalid): {ex.Message}");
}
// Write array of integers
int[] writeArray = { 1, 2, 3, 4, 5 };
bool writeSuccess = magic.Memory.Write(arrayAddress, writeArray);
Console.WriteLine($"Write {writeArray.Length} integers to 0x{arrayAddress:X}: {(writeSuccess ? "Success" : "Failed")}");
}
/// <summary>
/// Example 1.3: Reading and writing strings
/// </summary>
public static void ReadWriteStrings()
{
Console.WriteLine("\n=== Example 1.3: Read/Write Strings ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
IntPtr stringAddress = magic.Memory.ImageBase + 0x3000;
// Read ANSI string
string ansiString = magic.Memory.ReadString(stringAddress, Encoding.ASCII, maxLength: 256);
Console.WriteLine($"Read ANSI string from 0x{stringAddress:X}: \"{ansiString}\"");
// Read Unicode string
string unicodeString = magic.Memory.ReadString(stringAddress, Encoding.Unicode, maxLength: 256);
Console.WriteLine($"Read Unicode string from 0x{stringAddress:X}: \"{unicodeString}\"");
// Write string
bool writeSuccess = magic.Memory.WriteString(stringAddress, "Hello, WhiteMagic!", Encoding.ASCII);
Console.WriteLine($"Write string to 0x{stringAddress:X}: {(writeSuccess ? "Success" : "Failed")}");
}
/// <summary>
/// Example 1.4: Reading and writing raw bytes
/// </summary>
public static void ReadWriteBytes()
{
Console.WriteLine("\n=== Example 1.4: Read/Write Bytes ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
IntPtr address = magic.Memory.ImageBase + 0x4000;
// Read bytes
byte[] buffer = magic.Memory.ReadBytes(address, 16);
Console.WriteLine($"Read {buffer.Length} bytes from 0x{address:X}");
Console.WriteLine($"Hex: {BitConverter.ToString(buffer)}");
// Write bytes
byte[] patchBytes = { 0x90, 0x90, 0x90 }; // NOP x3
int bytesWritten = magic.Memory.WriteBytes(address, patchBytes);
Console.WriteLine($"Wrote {bytesWritten} bytes to 0x{address:X}");
}
/// <summary>
/// Example 1.5: Using RemotePointer for fluent addressing
/// </summary>
public static void RemotePointerUsage()
{
Console.WriteLine("\n=== Example 1.5: RemotePointer Fluent Addressing ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
// Create a pointer to module base
var modulePtr = magic[magic.Memory.ImageBase];
// Read offsets from module base
try
{
int offset1 = modulePtr.Read<int>(0x1000);
Console.WriteLine($"Read int at module_base + 0x1000: {offset1}");
// Chained reads (pointer -> value -> offset)
IntPtr nestedPtr = modulePtr.Read<IntPtr>(0x2000);
if (nestedPtr != IntPtr.Zero)
{
var nestedPtrObj = magic[nestedPtr];
int nestedValue = nestedPtrObj.Read<int>(0x50);
Console.WriteLine($"Read nested pointer value: {nestedValue}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Note: Read failed (offset may be invalid): {ex.Message}");
}
}
/// <summary>
/// Example 1.6: Relative addressing
/// </summary>
public static void RelativeAddressing()
{
Console.WriteLine("\n=== Example 1.6: Relative Addressing ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
IntPtr moduleBase = magic.Memory.ImageBase;
// Absolute addressing (default)
int absValue = magic.Memory.Read<int>(moduleBase + 0x1000);
Console.WriteLine($"Absolute read at 0x{moduleBase + 0x1000:X}: {absValue}");
// Relative addressing (relative to module base)
int relValue = magic.Memory.Read<int>(0x1000, isRelative: true);
Console.WriteLine($"Relative read at offset 0x1000: {relValue}");
// They should be the same
Console.WriteLine($"Values match: {absValue == relValue}");
}
/// <summary>
/// Example 1.7: Error handling
/// </summary>
public static void ErrorHandling()
{
Console.WriteLine("\n=== Example 1.7: Error Handling ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
// Try to read from invalid address
try
{
int invalidRead = magic.Memory.Read<int>(unchecked((IntPtr)0xdeadbeef));
Console.WriteLine($"Read from invalid address (shouldn't reach here): {invalidRead}");
}
catch (Exception ex)
{
Console.WriteLine($"✓ Caught expected exception: {ex.GetType().Name}");
}
// Try to write to read-only memory (will return false)
bool writeSuccess = magic.Memory.Write(magic.Memory.ImageBase, 42);
Console.WriteLine($"Write to read-only memory: {(writeSuccess ? "Unexpected success" : " Expected failure")}");
// Try to read string from invalid address
string invalidString = magic.Memory.ReadString(unchecked((IntPtr)0xdeadbeef), Encoding.ASCII);
Console.WriteLine($"Read string from invalid address: \"{invalidString}\" (empty = graceful failure)");
}
/// <summary>
/// Example 1.8: Working with custom structs
/// </summary>
public static void CustomStructs()
{
Console.WriteLine("\n=== Example 1.8: Custom Structs ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
IntPtr structAddress = magic.Memory.ImageBase + 0x5000;
try
{
// Read struct
PlayerInfo player = magic.Memory.Read<PlayerInfo>(structAddress);
Console.WriteLine($"Read PlayerInfo:");
Console.WriteLine($" Health: {player.Health}");
Console.WriteLine($" Position: ({player.PositionX}, {player.PositionY})");
Console.WriteLine($" Level: {player.Level}");
Console.WriteLine($" Experience: {player.Experience}");
// Write struct
PlayerInfo updatedPlayer = player with
{
Health = 100,
Level = player.Level + 1
};
bool writeSuccess = magic.Memory.Write(structAddress, updatedPlayer);
Console.WriteLine($"Write updated PlayerInfo: {(writeSuccess ? "Success" : "Failed")}");
}
catch (Exception ex)
{
Console.WriteLine($"Note: Struct read/write failed (address may be invalid): {ex.Message}");
}
}
/// <summary>
/// Run all basic memory operation examples
/// </summary>
public static void RunAll()
{
Console.WriteLine("╔════════════════════════════════════════════════════════════╗");
Console.WriteLine("║ WhiteMagic Example 1: Basic Memory Operations ║");
Console.WriteLine("╚════════════════════════════════════════════════════════════╝");
ReadWritePrimitives();
ReadWriteArrays();
ReadWriteStrings();
ReadWriteBytes();
RemotePointerUsage();
RelativeAddressing();
ErrorHandling();
CustomStructs();
Console.WriteLine("\n✓ All basic memory operation examples completed!");
}
}
@@ -0,0 +1,461 @@
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!");
}
}
@@ -0,0 +1,401 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using WhiteMagic;
using WhiteMagic.Assembly;
using WhiteMagic.Execution;
namespace WhiteMagic.Examples;
/// <summary>
/// Example 3: Execution Models
/// Demonstrates the three execution strategies: RemoteThreadExecutor, MainThreadPump, and InProcessInvoker
/// </summary>
public class ExecutionModels
{
/// <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 3.1: RemoteThreadExecutor (CreateRemoteThread)
/// Use for: Thread-agnostic payloads (WinAPI calls, DLL injection, self-contained code)
/// </summary>
public static void RemoteThreadExecutorExample()
{
Console.WriteLine("=== Example 3.1: RemoteThreadExecutor (CreateRemoteThread) ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
try
{
// Example: Call GetTickCount (thread-safe WinAPI function)
var getTickCount = magic["kernel32.dll"]["GetTickCount"];
if (getTickCount.Address != IntPtr.Zero)
{
Console.WriteLine($"Calling GetTickCount via RemoteThreadExecutor...");
uint ticks = getTickCount.Execute<uint>(CallConvention.Stdcall);
Console.WriteLine($"✓ Result: {ticks} ticks ({TimeSpan.FromMilliseconds(ticks):hh\\:mm\\:ss})");
}
else
{
Console.WriteLine("✗ GetTickCount not found in kernel32.dll");
}
// Example: Call GetCurrentProcessId
var getCurrentProcessId = magic["kernel32.dll"]["GetCurrentProcessId"];
if (getCurrentProcessId.Address != IntPtr.Zero)
{
Console.WriteLine($"Calling GetCurrentProcessId...");
uint processId = getCurrentProcessId.Execute<uint>(CallConvention.Stdcall);
Console.WriteLine($"✓ Result: Process ID = {processId}");
}
else
{
Console.WriteLine("✗ GetCurrentProcessId not found in kernel32.dll");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Execution failed: {ex.Message}");
}
Console.WriteLine("\n⚠️ NOTE: RemoteThreadExecutor is ONLY for thread-agnostic functions!");
Console.WriteLine(" Do NOT use for game state, scripting engines, or render operations.");
Console.WriteLine(" Use MainThreadPump for state-sensitive calls instead.");
}
/// <summary>
/// Example 3.2: MainThreadPump (Crash-Safe Execution)
/// Use for: State-sensitive calls (game state, scripting, UI, render operations)
/// </summary>
public static async Task MainThreadPumpExample()
{
Console.WriteLine("\n=== Example 3.2: MainThreadPump (Crash-Safe Execution) ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
try
{
// Find a per-frame function (e.g., D3D EndScene, or any function called every frame)
// For this example, we'll use a hypothetical frame function address
// In real use, you'd find the actual frame function via pattern scanning
IntPtr frameFunctionAddress = FindFrameFunction(magic);
if (frameFunctionAddress == IntPtr.Zero)
{
Console.WriteLine("✗ Could not find frame function (expected in this demo environment)");
Console.WriteLine(" In a real game, you'd use pattern scanning to find the frame function.");
return;
}
Console.WriteLine($"Found frame function at: 0x{frameFunctionAddress:X}");
// Create MainThreadPump
Console.WriteLine("Creating MainThreadPump...");
var pump = magic.CreateMainThreadPump(frameFunctionAddress);
// Example 1: Safe memory read from game state
Console.WriteLine("Enqueueing safe memory read...");
try
{
int value = await pump.ExecuteAsync(() =>
{
// Safe to read game state here (running on target's main thread)
return magic.Memory.Read<int>(magic.Memory.ImageBase + 0x1000);
});
Console.WriteLine($"✓ Safe read result: {value}");
}
catch (TimeoutException)
{
Console.WriteLine("✗ Pump operation timed out (work item wedged or target not running)");
}
catch (Exception ex)
{
Console.WriteLine($"✗ Pump operation failed: {ex.Message}");
}
// Example 2: Safe memory write to game state
Console.WriteLine("Enqueueing safe memory write...");
try
{
await pump.ExecuteAsync(() =>
{
// Safe to write game state here
magic.Memory.Write(magic.Memory.ImageBase + 0x2000, 42);
return true;
});
Console.WriteLine("✓ Safe write completed");
}
catch (Exception ex)
{
Console.WriteLine($"✗ Safe write failed: {ex.Message}");
}
// Example 3: Complex state manipulation
Console.WriteLine("Enqueueing complex state manipulation...");
try
{
string result = await pump.ExecuteAsync(() =>
{
// Safe to perform complex multi-step operations
IntPtr basePtr = magic.Memory.Read<IntPtr>(magic.Memory.ImageBase + 0x3000);
if (basePtr != IntPtr.Zero)
{
int health = magic.Memory.Read<int>(basePtr + 0x10);
int maxHealth = magic.Memory.Read<int>(basePtr + 0x14);
return $"Health: {health}/{maxHealth}";
}
return "Unknown";
});
Console.WriteLine($"✓ Complex operation result: {result}");
}
catch (Exception ex)
{
Console.WriteLine($"✗ Complex operation failed: {ex.Message}");
}
Console.WriteLine("\n✓ MainThreadPump examples completed");
}
catch (Exception ex)
{
Console.WriteLine($"✗ MainThreadPump setup failed: {ex.Message}");
}
}
/// <summary>
/// Helper: Find a per-frame function
/// In real use, you'd use pattern scanning to find D3D EndScene or similar
/// </summary>
private static IntPtr FindFrameFunction(Magic magic)
{
// For demo purposes, return zero (not found)
// In real use, you'd scan for patterns like:
// - D3D9 EndScene: device + 0x44 vtable entry
// - D3D11 Present callbacks
// - Game-specific Update/Render functions
// Example pattern scan (commented out for demo):
// var module = magic["d3d9.dll"];
// if (module != null)
// {
// IntPtr endScene = magic.Memory.FindPattern("?? ?? ?? ??", module.BaseAddress, module.ModuleMemorySize);
// return endScene;
// }
return IntPtr.Zero;
}
/// <summary>
/// Example 3.3: InProcessInvoker (Direct Delegates)
/// Use for: In-process calls after DLL injection (max performance)
/// </summary>
public static void InProcessInvokerExample()
{
Console.WriteLine("\n=== Example 3.3: InProcessInvoker (Direct Delegates) ===");
Console.WriteLine("⚠️ NOTE: This example only works when injected in-process!");
Console.WriteLine(" For demo purposes, we'll show the syntax but it won't execute.");
// Example syntax (would work if injected):
Console.WriteLine("\nExample code (requires in-process injection):");
Console.WriteLine("```csharp");
Console.WriteLine("// Only works when injected in-process");
Console.WriteLine("using var magic = Magic.OpenInProcess();");
Console.WriteLine();
Console.WriteLine("// Define delegate signature");
Console.WriteLine("[UnmanagedFunctionPointer(CallingConvention.Cdecl)]");
Console.WriteLine("public delegate int AddNumbersDelegate(int a, int b);");
Console.WriteLine();
Console.WriteLine("// Create delegate from function address");
Console.WriteLine("var addNumbers = inProcess.CreateFunction<AddNumbersDelegate>(functionAddress);");
Console.WriteLine();
Console.WriteLine("// Call directly as a delegate (max performance, <1μs)");
Console.WriteLine("int result = addNumbers(10, 20);");
Console.WriteLine("Console.WriteLine($\"Result: {result}\");");
Console.WriteLine("```");
Console.WriteLine("\n✓ InProcessInvoker is the fastest but requires injection.");
Console.WriteLine(" Use RemoteThreadExecutor for initial injection, then switch to InProcessInvoker.");
}
/// <summary>
/// Example 3.4: Choosing the Right Execution Model
/// </summary>
public static void ChooseExecutionModel()
{
Console.WriteLine("\n=== Example 3.4: Choosing the Right Execution Model ===");
Console.WriteLine("Decision Flowchart:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine();
Console.WriteLine("Are you injected in-process?");
Console.WriteLine("├─ Yes → Use InProcessInvoker (direct delegates, <1μs)");
Console.WriteLine("└─ No → Does the call touch single-threaded state?");
Console.WriteLine(" ├─ Yes → Use MainThreadPump (crash-safe, ~1 frame latency)");
Console.WriteLine(" └─ No → Use RemoteThreadExecutor (CreateRemoteThread, ~1-2ms)");
Console.WriteLine();
Console.WriteLine("Examples by Use Case:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine();
Console.WriteLine("1. DLL Injection: RemoteThreadExecutor");
Console.WriteLine(" → LoadLibrary is thread-safe");
Console.WriteLine();
Console.WriteLine("2. Read Health Bar: MainThreadPump");
Console.WriteLine(" → Game state is main-thread-affine");
Console.WriteLine();
Console.WriteLine("3. Call GetTickCount: RemoteThreadExecutor");
Console.WriteLine(" → WinAPI, no thread affinity");
Console.WriteLine();
Console.WriteLine("4. Script Engine Call: MainThreadPump");
Console.WriteLine(" → Script VM is main-thread-only");
Console.WriteLine();
Console.WriteLine("5. Injected Profiling: InProcessInvoker");
Console.WriteLine(" → Already in-process, max performance");
Console.WriteLine();
Console.WriteLine("6. Window Mutation: RemoteThreadExecutor");
Console.WriteLine(" → WinAPI SetWindowPos is thread-safe");
Console.WriteLine();
Console.WriteLine("Performance Comparison:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine();
Console.WriteLine("┌─────────────────────┬──────────────┬──────────┬──────────────┐");
Console.WriteLine("│ Model │ Latency │ Safety │ Use Case │");
Console.WriteLine("├─────────────────────┼──────────────┼──────────┼──────────────┤");
Console.WriteLine("│ RemoteThreadExec │ ~1-2ms │ Thread- │ One-shot, │");
Console.WriteLine("│ │ │ agnostic │ DLL inject │");
Console.WriteLine("├─────────────────────┼──────────────┼──────────┼──────────────┤");
Console.WriteLine("│ MainThreadPump │ ~1 frame │ Crash- │ State- │");
Console.WriteLine("│ │ (16-33ms) │ safe │ sensitive │");
Console.WriteLine("├─────────────────────┼──────────────┼──────────┼──────────────┤");
Console.WriteLine("│ InProcessInvoker │ <1μs │ In- │ In-process │");
Console.WriteLine("│ │ │ process │ tools │");
Console.WriteLine("└─────────────────────┴──────────────┴──────────┴──────────────┘");
}
/// <summary>
/// Example 3.5: Combining Execution Models
/// </summary>
public static void CombinedExecutionModels()
{
Console.WriteLine("\n=== Example 3.5: Combining Execution Models ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
Console.WriteLine("Strategy: Inject DLL, then switch to InProcessInvoker");
Console.WriteLine();
Console.WriteLine("Step 1: Use RemoteThreadExecutor to inject DLL");
Console.WriteLine("```csharp");
Console.WriteLine("var loadLibrary = magic[\"kernel32.dll\"][\"LoadLibraryA\"];");
Console.WriteLine("IntPtr dllHandle = loadLibrary.Execute<IntPtr>(CallConvention.Stdcall, dllPathPtr);");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("Step 2: Injected DLL initializes, now in-process");
Console.WriteLine("```csharp");
Console.WriteLine("// Inside injected DLL");
Console.WriteLine("using var inProcess = Magic.OpenInProcess();");
Console.WriteLine();
Console.WriteLine("// Use InProcessInvoker for max performance");
Console.WriteLine("var fn = inProcess.CreateFunction<MyDelegate>(address);");
Console.WriteLine("int result = fn(arg1, arg2); // <1μs latency");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("✓ DLL injection → Fast in-process calls");
}
/// <summary>
/// Example 3.6: Error Handling in Execution Models
/// </summary>
public static void ExecutionModelErrorHandling()
{
Console.WriteLine("\n=== Example 3.6: Error Handling ===");
Console.WriteLine("RemoteThreadExecutor:");
Console.WriteLine("```csharp");
Console.WriteLine("try");
Console.WriteLine("{");
Console.WriteLine(" uint result = fn.Execute<uint>(CallConvention.Stdcall, args...);");
Console.WriteLine("}");
Console.WriteLine("catch (Win32Exception ex)");
Console.WriteLine("{");
Console.WriteLine(" // CreateRemoteThread failed (access denied, process exited, etc.)");
Console.WriteLine(" Console.WriteLine($\"Execution failed: {ex.Message}\");");
Console.WriteLine("}");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("MainThreadPump:");
Console.WriteLine("```csharp");
Console.WriteLine("try");
Console.WriteLine("{");
Console.WriteLine(" int result = await pump.ExecuteAsync(() => magic.Memory.Read<int>(addr));");
Console.WriteLine("}");
Console.WriteLine("catch (TimeoutException)");
Console.WriteLine("{");
Console.WriteLine(" // Work item wedged (stuck the frame) or target not running");
Console.WriteLine(" Console.WriteLine(\"Operation timed out\");");
Console.WriteLine("}");
Console.WriteLine("catch (Exception ex)");
Console.WriteLine("{");
Console.WriteLine(" // Other exception from work item");
Console.WriteLine(" Console.WriteLine($\"Operation failed: {ex.Message}\");");
Console.WriteLine("}");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("InProcessInvoker:");
Console.WriteLine("```csharp");
Console.WriteLine("try");
Console.WriteLine("{");
Console.WriteLine(" int result = delegate(arg1, arg2);");
Console.WriteLine("}");
Console.WriteLine("catch (AccessViolationException)");
Console.WriteLine("{");
Console.WriteLine(" // Invalid function address or bad call convention");
Console.WriteLine("}");
Console.WriteLine("```");
}
/// <summary>
/// Run all execution model examples
/// </summary>
public static async Task RunAll()
{
Console.WriteLine("╔════════════════════════════════════════════════════════════╗");
Console.WriteLine("║ WhiteMagic Example 3: Execution Models ║");
Console.WriteLine("╚════════════════════════════════════════════════════════════╝");
RemoteThreadExecutorExample();
await MainThreadPumpExample();
InProcessInvokerExample();
ChooseExecutionModel();
CombinedExecutionModels();
ExecutionModelErrorHandling();
Console.WriteLine("\n✓ All execution model examples completed!");
Console.WriteLine();
Console.WriteLine("⚠️ CRITICAL REMINDER:");
Console.WriteLine(" - RemoteThreadExecutor: ONLY for thread-agnostic functions");
Console.WriteLine(" - MainThreadPump: For state-sensitive calls (crash-safe)");
Console.WriteLine(" - InProcessInvoker: Only after DLL injection");
Console.WriteLine();
Console.WriteLine(" Using the wrong model crashes the target application!");
}
}
@@ -0,0 +1,410 @@
using System;
using System.Diagnostics;
using WhiteMagic;
using WhiteMagic.Hooking;
namespace WhiteMagic.Examples;
/// <summary>
/// Example 4: Function Hooking
/// Demonstrates inline detours and byte patches
/// </summary>
public class FunctionHooking
{
/// <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 4.1: Simple Inline Detour
/// Note: Detours only work in-process (requires injection)
/// </summary>
public static void SimpleInlineDetour()
{
Console.WriteLine("=== Example 4.1: Simple Inline Detour ===");
Console.WriteLine("⚠️ NOTE: Detours only work when injected in-process!");
Console.WriteLine(" For demo purposes, we'll show the syntax but it won't execute.");
Console.WriteLine();
Console.WriteLine("Example code (requires in-process injection):");
Console.WriteLine("```csharp");
Console.WriteLine("using var magic = Magic.OpenInProcess();");
Console.WriteLine();
Console.WriteLine("// Define your hook delegate");
Console.WriteLine("[UnmanagedFunctionPointer(CallingConvention.Stdcall)]");
Console.WriteLine("public delegate uint GetTickCountDelegate();");
Console.WriteLine();
Console.WriteLine("// Original function pointer (for calling original)");
Console.WriteLine("static GetTickCountDelegate OriginalGetTickCount = null!;");
Console.WriteLine();
Console.WriteLine("// Your hook implementation");
Console.WriteLine("static uint MyGetTickCount()");
Console.WriteLine("{");
Console.WriteLine(" Console.WriteLine(\"GetTickCount called!\");");
Console.WriteLine(" return OriginalGetTickCount(); // Call original");
Console.WriteLine("}");
Console.WriteLine();
Console.WriteLine("// Apply the detour");
Console.WriteLine("var getTickCount = magic[\"kernel32.dll\"][\"GetTickCount\"];");
Console.WriteLine("var detour = magic.DetourManager.Create(");
Console.WriteLine(" \"my_gettickcount\",");
Console.WriteLine(" getTickCount.Address,");
Console.WriteLine(" (GetTickCountDelegate)MyGetTickCount");
Console.WriteLine(");");
Console.WriteLine("OriginalGetTickCount = original;");
Console.WriteLine("detour.Apply();");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("✓ Detour applied: Every GetTickCount call now goes through MyGetTickCount");
}
/// <summary>
/// Example 4.2: Detour with Parameter Modification
/// </summary>
public static void DetourWithParameterModification()
{
Console.WriteLine("\n=== Example 4.2: Detour with Parameter Modification ===");
Console.WriteLine("Example: Hook MessageBoxW to change the caption");
Console.WriteLine("```csharp");
Console.WriteLine("// Original function signature");
Console.WriteLine("[UnmanagedFunctionPointer(CallingConvention.Stdcall)]");
Console.WriteLine("public delegate int MessageBoxDelegate(");
Console.WriteLine(" IntPtr hWnd,");
Console.WriteLine(" IntPtr lpText,");
Console.WriteLine(" IntPtr lpCaption,");
Console.WriteLine(" uint type");
Console.WriteLine(");");
Console.WriteLine();
Console.WriteLine("static MessageBoxDelegate OriginalMessageBox = null!;");
Console.WriteLine();
Console.WriteLine("static int MyMessageBox(");
Console.WriteLine(" IntPtr hWnd,");
Console.WriteLine(" IntPtr lpText,");
Console.WriteLine(" IntPtr lpCaption,");
Console.WriteLine(" uint type)");
Console.WriteLine("{");
Console.WriteLine(" // Read original strings");
Console.WriteLine(" string text = Marshal.PtrToStringUni(lpText);");
Console.WriteLine(" string caption = Marshal.PtrToStringUni(lpCaption);");
Console.WriteLine();
Console.WriteLine(" Console.WriteLine($\"MessageBox: {caption} - {text}\");");
Console.WriteLine();
Console.WriteLine(" // Modify the caption");
Console.WriteLine(" string newCaption = \"[Hooked] \" + caption;");
Console.WriteLine(" IntPtr newCaptionPtr = Marshal.StringToHGlobalUni(newCaption);");
Console.WriteLine();
Console.WriteLine(" // Call original with modified caption");
Console.WriteLine(" int result = OriginalMessageBox(hWnd, lpText, newCaptionPtr, type);");
Console.WriteLine();
Console.WriteLine(" Marshal.FreeHGlobal(newCaptionPtr);");
Console.WriteLine(" return result;");
Console.WriteLine("}");
Console.WriteLine();
Console.WriteLine("// Apply hook");
Console.WriteLine("var messageBox = magic[\"user32.dll\"][\"MessageBoxW\"];");
Console.WriteLine("var detour = magic.DetourManager.Create(");
Console.WriteLine(" \"my_messagebox\",");
Console.WriteLine(" messageBox.Address,");
Console.WriteLine(" (MessageBoxDelegate)MyMessageBox");
Console.WriteLine(");");
Console.WriteLine();
Console.WriteLine("OriginalMessageBox = original;");
Console.WriteLine("detour.Apply();");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("✓ Every MessageBoxW call now has \"[Hooked]\" prefix in caption");
}
/// <summary>
/// Example 4.3: Detour with Return Value Modification
/// </summary>
public static void DetourWithReturnValueModification()
{
Console.WriteLine("\n=== Example 4.3: Detour with Return Value Modification ===");
Console.WriteLine("Example: Always return success from a function");
Console.WriteLine("```csharp");
Console.WriteLine("[UnmanagedFunctionPointer(CallingConvention.Stdcall)]");
Console.WriteLine("public delegate bool CheckLicenseDelegate();");
Console.WriteLine();
Console.WriteLine("static bool MyCheckLicense()");
Console.WriteLine("{");
Console.WriteLine(" Console.WriteLine(\"License check bypassed!\");");
Console.WriteLine(" return true; // Always return true (bypass check)");
Console.WriteLine("}");
Console.WriteLine();
Console.WriteLine("// Apply hook");
Console.WriteLine("var checkLicense = magic[\"target.dll\"][\"CheckLicense\"];");
Console.WriteLine("var detour = magic.DetourManager.Create(");
Console.WriteLine(" \"my_checklicense\",");
Console.WriteLine(" checkLicense.Address,");
Console.WriteLine(" (CheckLicenseDelegate)MyCheckLicense");
Console.WriteLine(");");
Console.WriteLine("detour.Apply();");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("✓ License check always returns true (bypassed)");
}
/// <summary>
/// Example 4.4: Multiple Detours (Chain Hooking)
/// </summary>
public static void MultipleDetours()
{
Console.WriteLine("\n=== Example 4.4: Multiple Detours (Chain Hooking) ===");
Console.WriteLine("Example: Install multiple hooks on the same function");
Console.WriteLine("```csharp");
Console.WriteLine("// First hook");
Console.WriteLine("var detour1 = magic.DetourManager.Create(\"hook1\", fnAddr, Hook1);");
Console.WriteLine("detour1.Apply();");
Console.WriteLine();
Console.WriteLine("// Second hook (hooks the trampoline from first)");
Console.WriteLine("var detour2 = magic.DetourManager.Create(");
Console.WriteLine(" \"hook2\",");
Console.WriteLine(" detour1.Trampoline,");
Console.WriteLine(" Hook2");
Console.WriteLine(");");
Console.WriteLine("detour2.Apply();");
Console.WriteLine();
Console.WriteLine("// Execution flow:");
Console.WriteLine("// Original function → Hook2 → Hook1 → Trampoline1 → Original+5");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("✓ Chain hooking: Hooks execute in reverse order of installation");
}
/// <summary>
/// Example 4.5: Byte Patching
/// </summary>
public static void BytePatching()
{
Console.WriteLine("\n=== Example 4.5: Byte Patching ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
try
{
// Example: Patch a constant value
IntPtr patchAddress = magic.Memory.ImageBase + 0x5000;
byte[] originalBytes = magic.Memory.ReadBytes(patchAddress, 4);
byte[] patchBytes = { 0x00, 0x00, 0x00, 0x00 }; // Patch to 0
Console.WriteLine($"Creating patch at 0x{patchAddress:X}");
Console.WriteLine($"Original bytes: {BitConverter.ToString(originalBytes)}");
Console.WriteLine($"Patch bytes: {BitConverter.ToString(patchBytes)}");
var patch = magic.PatchManager.Create("MaxHealthPatch", patchAddress, patchBytes);
patch.Apply();
Console.WriteLine($"✓ Patch applied");
// Verify patch
byte[] currentBytes = magic.Memory.ReadBytes(patchAddress, 4);
Console.WriteLine($"Current bytes: {BitConverter.ToString(currentBytes)}");
// Remove patch
patch.Remove();
Console.WriteLine($"✓ Patch removed (original bytes restored)");
byte[] restoredBytes = magic.Memory.ReadBytes(patchAddress, 4);
Console.WriteLine($"Restored bytes: {BitConverter.ToString(restoredBytes)}");
}
catch (Exception ex)
{
Console.WriteLine($"✗ Patch example failed (address may be invalid): {ex.Message}");
}
}
/// <summary>
/// Example 4.6: NOP Patching (Removing Instructions)
/// </summary>
public static void NopPatching()
{
Console.WriteLine("\n=== Example 4.6: NOP Patching (Removing Instructions) ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
try
{
// Example: NOP out a conditional jump (5 bytes on x86)
IntPtr patchAddress = magic.Memory.ImageBase + 0x6000;
byte[] originalBytes = magic.Memory.ReadBytes(patchAddress, 5);
byte[] nopPatch = { 0x90, 0x90, 0x90, 0x90, 0x90 }; // NOP x5
Console.WriteLine($"Creating NOP patch at 0x{patchAddress:X}");
Console.WriteLine($"Original bytes: {BitConverter.ToString(originalBytes)}");
Console.WriteLine($"NOP patch: {BitConverter.ToString(nopPatch)}");
var patch = magic.PatchManager.Create("ConditionalJumpPatch", patchAddress, nopPatch);
patch.Apply();
Console.WriteLine($"✓ NOP patch applied (conditional jump removed)");
// Restore
patch.Remove();
Console.WriteLine($"✓ Patch removed (original jump restored)");
}
catch (Exception ex)
{
Console.WriteLine($"✗ NOP patch example failed (address may be invalid): {ex.Message}");
}
}
/// <summary>
/// Example 4.7: Conditional Patching
/// </summary>
public static void ConditionalPatching()
{
Console.WriteLine("\n=== Example 4.7: Conditional Patching ===");
Console.WriteLine("Example: Toggle patch on/off");
Console.WriteLine("```csharp");
Console.WriteLine("public class ToggleablePatch");
Console.WriteLine("{");
Console.WriteLine(" private readonly Patch _patch;");
Console.WriteLine(" private bool _enabled;");
Console.WriteLine();
Console.WriteLine(" public bool Enabled");
Console.WriteLine(" {");
Console.WriteLine(" get => _enabled;");
Console.WriteLine(" set");
Console.WriteLine(" {");
Console.WriteLine(" if (_enabled == value) return;");
Console.WriteLine();
Console.WriteLine(" if (value)");
Console.WriteLine(" _patch.Apply();");
Console.WriteLine(" else");
Console.WriteLine(" _patch.Remove();");
Console.WriteLine();
Console.WriteLine(" _enabled = value;");
Console.WriteLine(" }");
Console.WriteLine(" }");
Console.WriteLine("}");
Console.WriteLine();
Console.WriteLine("// Usage");
Console.WriteLine("var patch = magic.PatchManager.Create(\"Patch\", addr, bytes);");
Console.WriteLine("var toggleable = new ToggleablePatch(patch);");
Console.WriteLine();
Console.WriteLine("toggleable.Enabled = true; // Apply patch");
Console.WriteLine("toggleable.Enabled = false; // Remove patch");
Console.WriteLine("```");
}
/// <summary>
/// Example 4.8: Detour Safety and Prologue Validation
/// </summary>
public static void DetourSafety()
{
Console.WriteLine("\n=== Example 4.8: Detour Safety and Prologue Validation ===");
Console.WriteLine("Prologue Validation:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine("Before splicing a detour, WhiteMagic validates the prologue:");
Console.WriteLine();
Console.WriteLine("✓ Common x86/x64 prologues:");
Console.WriteLine(" - push ebp; mov ebp, esp");
Console.WriteLine(" - mov edi, edi (hot-patch padding)");
Console.WriteLine(" - sub rsp, XX (x64 stack allocation)");
Console.WriteLine(" - Single-byte instructions (nop, int3)");
Console.WriteLine();
Console.WriteLine("✓ Optional Iced backend for full disassembly validation");
Console.WriteLine();
Console.WriteLine("Error: \"Prologue too short\"");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine("Cause: Function prologue is shorter than minimum (5 bytes for jmp)");
Console.WriteLine();
Console.WriteLine("Solutions:");
Console.WriteLine(" 1. Hook a different function");
Console.WriteLine(" 2. Patch deeper into the function (after prologue)");
Console.WriteLine(" 3. For WinAPI, use hot-patch area (2-byte jmp at [address-2])");
Console.WriteLine();
Console.WriteLine("Thread Safety:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine("⚠️ DetourManager is NOT thread-safe!");
Console.WriteLine();
Console.WriteLine("WRONG:");
Console.WriteLine("```csharp");
Console.WriteLine("Task.Run(() => detour1.Apply());");
Console.WriteLine("Task.Run(() => detour2.Apply()); // Race condition!");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("RIGHT:");
Console.WriteLine("```csharp");
Console.WriteLine("lock (magic.DetourManager)");
Console.WriteLine("{");
Console.WriteLine(" detour1.Apply();");
Console.WriteLine(" detour2.Apply();");
Console.WriteLine("}");
Console.WriteLine("```");
}
/// <summary>
/// Example 4.9: Automatic Restoration on Disposal
/// </summary>
public static void AutomaticRestoration()
{
Console.WriteLine("\n=== Example 4.9: Automatic Restoration on Disposal ===");
Console.WriteLine("All hooks and patches auto-restore on disposal:");
Console.WriteLine("```csharp");
Console.WriteLine("using (var magic = Magic.OpenInProcess())");
Console.WriteLine("{");
Console.WriteLine(" var detour = magic.DetourManager.Detour(addr, hook);");
Console.WriteLine(" detour.Apply();");
Console.WriteLine();
Console.WriteLine(" var patch = magic.PatchManager.Create(\"Patch\", addr, bytes);");
Console.WriteLine(" patch.Apply();");
Console.WriteLine();
Console.WriteLine(" // ... use hooked functions");
Console.WriteLine("} // End of using: detour.Remove() and patch.Remove() called automatically");
Console.WriteLine("```");
Console.WriteLine();
Console.WriteLine("✓ Original bytes automatically restored on disposal");
}
/// <summary>
/// Run all function hooking examples
/// </summary>
public static void RunAll()
{
Console.WriteLine("╔════════════════════════════════════════════════════════════╗");
Console.WriteLine("║ WhiteMagic Example 4: Function Hooking ║");
Console.WriteLine("╚════════════════════════════════════════════════════════════╝");
SimpleInlineDetour();
DetourWithParameterModification();
DetourWithReturnValueModification();
MultipleDetours();
BytePatching();
NopPatching();
ConditionalPatching();
DetourSafety();
AutomaticRestoration();
Console.WriteLine("\n✓ All function hooking examples completed!");
Console.WriteLine();
Console.WriteLine("⚠️ CRITICAL REMINDERS:");
Console.WriteLine(" - Detours ONLY work in-process (requires injection)");
Console.WriteLine(" - Patches work externally (no injection required)");
Console.WriteLine(" - DetourManager/PatchManager are NOT thread-safe");
Console.WriteLine(" - All hooks/patches auto-restore on disposal");
}
}
@@ -0,0 +1,531 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Linq;
using WhiteMagic;
using WhiteMagic.Windows;
using WhiteMagic.Assembly;
using WhiteMagic.Discovery;
namespace WhiteMagic.Examples;
/// <summary>
/// Example 5: High-Level API
/// Demonstrates RemotePointer, RemoteModule, RemoteFunction, and other high-level APIs
/// </summary>
public class HighLevelAPI
{
/// <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 5.1: RemotePointer (Fluent Pointer Arithmetic)
/// </summary>
public static void RemotePointerExample()
{
Console.WriteLine("=== Example 5.1: RemotePointer (Fluent Pointer Arithmetic) ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
IntPtr baseAddress = magic.Memory.ImageBase;
// Create a RemotePointer to base address
var ptr = magic[baseAddress];
Console.WriteLine($"Created RemotePointer to: 0x{baseAddress:X}");
try
{
// Read different offsets from the same base
int offset1 = ptr.Read<int>(0x1000);
Console.WriteLine($"Read int at offset 0x1000: {offset1}");
float offset2 = ptr.Read<float>(0x2000);
Console.WriteLine($"Read float at offset 0x2000: {offset2}");
// Write to offset
bool writeSuccess = ptr.Write(42, 0x3000);
Console.WriteLine($"Write int to offset 0x3000: {(writeSuccess ? "Success" : "Failed")}");
// Chained pointer reads (pointer → pointer → value)
IntPtr ptr1 = ptr.Read<IntPtr>(0x4000);
if (ptr1 != IntPtr.Zero)
{
var ptr2 = magic[ptr1];
int nestedValue = ptr2.Read<int>(0x50);
Console.WriteLine($"Nested pointer read: 0x{baseAddress:X} → 0x{ptr1:X} → {nestedValue}");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Operation failed (offset may be invalid): {ex.Message}");
}
}
/// <summary>
/// Example 5.2: RemoteModule (Module Enumeration)
/// </summary>
public static void RemoteModuleExample()
{
Console.WriteLine("\n=== Example 5.2: RemoteModule (Module Enumeration) ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
// List all loaded modules using Process.Modules
Console.WriteLine("Loaded modules:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine(string.Format("{0,-25} {1,-15} {2,-15}", "Name", "Base Address", "Size"));
Console.WriteLine("─────────────────────────────────────────────────────────────────");
foreach (System.Diagnostics.ProcessModule module in process.Modules)
{
Console.WriteLine(string.Format("{0,-25} 0x{1:X} 0x{2:X}",
module.ModuleName, module.BaseAddress, module.ModuleMemorySize));
}
// Access specific module using WhiteMagic's RemoteModule
var kernel32 = magic["kernel32.dll"];
if (kernel32 != null && kernel32.BaseAddress != IntPtr.Zero)
{
Console.WriteLine();
Console.WriteLine($"✓ Found {kernel32.Name} at 0x{kernel32.BaseAddress:X}");
// Resolve specific exports by name
Console.WriteLine($"\nResolved exports from {kernel32.Name}:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
var getTickCount = kernel32["GetTickCount"];
if (getTickCount.Address != IntPtr.Zero)
{
Console.WriteLine($"✓ GetTickCount at 0x{getTickCount.Address:X}");
}
var getCurrentProcessId = kernel32["GetCurrentProcessId"];
if (getCurrentProcessId.Address != IntPtr.Zero)
{
Console.WriteLine($"✓ GetCurrentProcessId at 0x{getCurrentProcessId.Address:X}");
}
var messageBoxA = kernel32["MessageBoxA"];
if (messageBoxA.Address != IntPtr.Zero)
{
Console.WriteLine($"✓ MessageBoxA at 0x{messageBoxA.Address:X}");
}
}
else
{
Console.WriteLine("✗ kernel32.dll not found");
}
}
/// <summary>
/// Example 5.3: RemoteFunction (Function Resolution and Execution)
/// </summary>
public static void RemoteFunctionExample()
{
Console.WriteLine("\n=== Example 5.3: RemoteFunction (Function Resolution) ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
try
{
// Resolve function by name
var getTickCount = magic["kernel32.dll"]["GetTickCount"];
if (getTickCount.Address != IntPtr.Zero)
{
Console.WriteLine($"✓ Resolved GetTickCount at: 0x{getTickCount.Address:X}");
// Execute the function
uint ticks = getTickCount.Execute<uint>(CallConvention.Stdcall);
Console.WriteLine($" Result: {ticks} ticks ({TimeSpan.FromMilliseconds(ticks):hh\\:mm\\:ss})");
}
else
{
Console.WriteLine("✗ GetTickCount not found");
}
// Resolve another function
var getCurrentProcessId = magic["kernel32.dll"]["GetCurrentProcessId"];
if (getCurrentProcessId.Address != IntPtr.Zero)
{
Console.WriteLine($"✓ Resolved GetCurrentProcessId at: 0x{getCurrentProcessId.Address:X}");
uint processId = getCurrentProcessId.Execute<uint>(CallConvention.Stdcall);
Console.WriteLine($" Result: Process ID = {processId}");
}
else
{
Console.WriteLine("✗ GetCurrentProcessId not found");
}
// Try to resolve non-existent function
var nonExistent = magic["kernel32.dll"]["NonExistentFunction123"];
if (nonExistent.Address == IntPtr.Zero)
{
Console.WriteLine($"✓ NonExistentFunction123 correctly not found");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Function resolution/execution failed: {ex.Message}");
}
}
/// <summary>
/// Example 5.4: ProcessInfo and Module Details
/// Demonstrates accessing process and module metadata through WhiteMagic
/// </summary>
public static void ProcessInfoExample()
{
Console.WriteLine("\n=== Example 5.4: ProcessInfo and Module Details ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
try
{
// Access process metadata
Console.WriteLine("Process Information:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine($"Process ID: {process.Id}");
Console.WriteLine($"Process Name: {process.ProcessName}");
Console.WriteLine($"Main Window Title: {process.MainWindowTitle}");
if (process.MainModule != null)
{
Console.WriteLine($"Main Module Base: 0x{process.MainModule.BaseAddress:X}");
Console.WriteLine($"Main Module Size: 0x{process.MainModule.ModuleMemorySize:X} bytes");
Console.WriteLine($"Main Module Path: {process.MainModule.FileName}");
}
// Enumerate loaded modules
Console.WriteLine("\nLoaded Modules:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
int moduleCount = 0;
foreach (System.Diagnostics.ProcessModule module in process.Modules)
{
if (moduleCount < 5) // Show first 5 modules
{
Console.WriteLine($" {module.ModuleName,-30} Base: 0x{module.BaseAddress:X16} Size: 0x{module.ModuleMemorySize:X}");
moduleCount++;
}
}
if (process.Modules.Count > 5)
{
Console.WriteLine($" ... and {process.Modules.Count - 5} more modules");
}
// Memory information
Console.WriteLine("\nMemory Information:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
Console.WriteLine($"Image Base: 0x{magic.Memory.ImageBase:X}");
Console.WriteLine($"Handle: 0x{magic.Memory.Handle.DangerousGetHandle():X}");
Console.WriteLine($"Bitness: {(magic.Memory.Is64Bit ? "64" : "32")} bits");
Console.WriteLine("✓ Process information retrieved successfully");
}
catch (Exception ex)
{
Console.WriteLine($"✗ Process info access failed: {ex.Message}");
}
}
/// <summary>
/// Example 5.5: RemoteWindow (Window Manipulation)
/// </summary>
public static void RemoteWindowExample()
{
Console.WriteLine("\n=== Example 5.5: RemoteWindow (Window Manipulation) ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
try
{
// Get main window handle from Process and create RemoteWindow
if (process.MainWindowHandle != IntPtr.Zero)
{
var mainWindow = new RemoteWindow(process.MainWindowHandle);
Console.WriteLine($"Main Window: {mainWindow.Title}");
Console.WriteLine($"Handle: 0x{mainWindow.Handle:X}");
Console.WriteLine($"Class: {mainWindow.ClassName}");
// Get window rect through WinAPI calls (simplified - would need P/Invoke for full implementation)
Console.WriteLine($"Note: Full window rect information requires additional WinAPI P/Invoke declarations");
// Modify window properties
Console.WriteLine("\nModifying window properties...");
// Flash window
mainWindow.Flash();
Console.WriteLine("✓ Window flashed");
// Activate window
mainWindow.Activate();
Console.WriteLine("✓ Window activated");
// Modify title (temporary)
string originalTitle = mainWindow.Title;
mainWindow.Title = "WhiteMagic Demo!";
Console.WriteLine($"✓ Window title changed to: {mainWindow.Title}");
// Restore original title
System.Threading.Thread.Sleep(1000);
mainWindow.Title = originalTitle;
Console.WriteLine($"✓ Window title restored to: {mainWindow.Title}");
}
else
{
Console.WriteLine("✗ No main window found");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Window manipulation failed: {ex.Message}");
}
}
/// <summary>
/// Example 5.6: Module Resolution by Name
/// </summary>
public static void ModuleResolutionExample()
{
Console.WriteLine("\n=== Example 5.6: Module Resolution ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
// Common modules to check
string[] moduleNames = { "kernel32.dll", "user32.dll", "ntdll.dll", "notepad.exe" };
Console.WriteLine("Module resolution:");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
foreach (var moduleName in moduleNames)
{
var module = magic[moduleName];
if (module != null && module.BaseAddress != IntPtr.Zero)
{
Console.WriteLine($"✓ {moduleName,-20} at 0x{module.BaseAddress:X}");
}
else
{
Console.WriteLine($"✗ {moduleName,-20} not found");
}
}
// Get main module using Process.MainModule
if (process.MainModule != null)
{
Console.WriteLine($"\n✓ Main module: {process.MainModule.ModuleName} at 0x{process.MainModule.BaseAddress:X}");
}
}
/// <summary>
/// Example 5.7: Pattern Scanning (Basic)
/// </summary>
public static void PatternScanningExample()
{
Console.WriteLine("\n=== Example 5.7: Pattern Scanning ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
// Get main module for scanning
if (process.MainModule == null)
{
Console.WriteLine("✗ Main module not available");
return;
}
Console.WriteLine($"Scanning for patterns in {process.MainModule.ModuleName}...");
// Example patterns (these are common x64 instruction patterns)
// Note: In real use, you'd use patterns specific to your target
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
};
// Masks: 'x' = match exactly, '?' = wildcard
string[] masks =
{
"xxx????", // mov rax, [rip+disp] - last 4 bytes are displacement (wildcard)
"x????", // call rel32 - displacement is wildcard
"x????" // mov eax, imm32 - immediate is wildcard
};
for (int i = 0; i < patterns.Length; i++)
{
try
{
IntPtr result = PatternScanner.FindInModule(
magic.Memory,
patterns[i],
masks[i],
process.MainModule
);
Console.WriteLine($" Pattern {i + 1}: {(result != IntPtr.Zero ? $" Found at 0x{result:X}" : " Not found")}");
}
catch (Exception ex)
{
Console.WriteLine($" Pattern {i + 1}: ✗ Scan failed - {ex.Message}");
}
}
}
/// <summary>
/// Example 5.8: Cached Pattern Scanning
/// </summary>
public static void CachedPatternScanningExample()
{
Console.WriteLine("\n=== Example 5.8: Cached Pattern Scanning ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
if (process.MainModule == null)
{
Console.WriteLine("✗ Main module not available");
return;
}
// Create a cache for pattern scanning
var cache = new PatternScannerCache(magic.Memory);
Console.WriteLine("Demonstrating cached pattern scanning...");
// Pattern to find
byte[] pattern = { 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 };
string mask = "xxx????";
try
{
// First scan (reads memory)
Console.Write(" First scan: ");
IntPtr result1 = cache.FindInModuleCached(pattern, mask, process.MainModule);
Console.WriteLine(result1 != IntPtr.Zero ? $"✓ 0x{result1:X}" : "✗ Not found");
// Second scan (uses cache)
Console.Write(" Second scan (cached): ");
IntPtr result2 = cache.FindInModuleCached(pattern, mask, process.MainModule);
Console.WriteLine(result2 != IntPtr.Zero ? $"✓ 0x{result2:X}" : "✗ Not found");
Console.WriteLine(" ✓ Results match (cache working)");
}
catch (Exception ex)
{
Console.WriteLine($" ✗ Cache scan failed: {ex.Message}");
}
}
/// <summary>
/// Example 5.9: High-Level API Chaining
/// </summary>
public static void HighLevelAPIChaining()
{
Console.WriteLine("\n=== Example 5.9: High-Level API Chaining ===");
var process = TargetProcess();
if (process == null) return;
using var magic = Magic.Open(process);
try
{
// Chain: Module → Function → Execute
var module = magic["kernel32.dll"];
if (module != null && module.BaseAddress != IntPtr.Zero)
{
var function = module["GetTickCount"];
if (function.Address != IntPtr.Zero)
{
uint result = function.Execute<uint>(CallConvention.Stdcall);
Console.WriteLine($"✓ Chained call: magic[\"kernel32.dll\"][\"GetTickCount\"].Execute<uint>() = {result}");
}
}
// Chain: Pointer → Read → Pointer → Read
var basePtr = magic[magic.Memory.ImageBase];
try
{
IntPtr ptr1 = basePtr.Read<IntPtr>(0x1000);
if (ptr1 != IntPtr.Zero)
{
var ptr2 = magic[ptr1];
int value = ptr2.Read<int>(0x50);
Console.WriteLine($"✓ Pointer chain: base → 0x{ptr1:X} → {value}");
}
}
catch
{
Console.WriteLine("✗ Pointer chain: Address not accessible (expected for demo)");
}
// Chain: Window → Title → Length
if (process.MainWindowHandle != IntPtr.Zero)
{
var window = new RemoteWindow(process.MainWindowHandle);
int titleLength = window.Title.Length;
Console.WriteLine($"✓ Window chain: RemoteWindow(MainWindowHandle).Title.Length = {titleLength}");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ API chaining failed: {ex.Message}");
}
}
/// <summary>
/// Run all high-level API examples
/// </summary>
public static void RunAll()
{
Console.WriteLine("╔════════════════════════════════════════════════════════════╗");
Console.WriteLine("║ WhiteMagic Example 5: High-Level API ║");
Console.WriteLine("╚════════════════════════════════════════════════════════════╝");
RemotePointerExample();
RemoteModuleExample();
RemoteFunctionExample();
ProcessInfoExample();
RemoteWindowExample();
ModuleResolutionExample();
PatternScanningExample();
CachedPatternScanningExample();
HighLevelAPIChaining();
Console.WriteLine("\n✓ All high-level API examples completed!");
}
}
+142
View File
@@ -0,0 +1,142 @@
using System;
using System.Threading.Tasks;
namespace WhiteMagic.Examples;
/// <summary>
/// Main program for WhiteMagic examples
/// Demonstrates all major features of the library
/// </summary>
class Program
{
static async Task Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(@"
╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ ████████╗██╗ ██╗██╗ ██████╗███████╗███████╗██╗ ██╗███████╗██╗ ║
║ ╚══██╔══╝██║ ██║██║██╔════╝██╔════╝██╔════╝██║ ██║██╔════╝██║ ║
║ ██║ ██║ ██║██║██║ ███████╗███████╗███████║█████╗ ██║ ║
║ ██║ ██║ ██║██║██║ ╚════██║╚════██║██╔══██║██╔══╝ ██║ ║
║ ██║ ╚██████╔╝██║╚██████╗███████║███████║██║ ██║███████╗███████╗║
║ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝║
║ ║
║ Process Introspection Library for .NET ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
");
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(" Examples for Learning WhiteMagic API");
Console.WriteLine(" ─────────────────────────────────────");
Console.ResetColor();
while (true)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Select an example to run:");
Console.ResetColor();
Console.WriteLine(" 1. Basic Memory Operations (Read/Write, Arrays, Strings, Structs)");
Console.WriteLine(" 2. Pattern Scanning (Find patterns in memory)");
Console.WriteLine(" 3. Execution Models (RemoteThread, MainThreadPump, InProcess)");
Console.WriteLine(" 4. Function Hooking (Detours, Patches)");
Console.WriteLine(" 5. High-Level API (RemotePointer, RemoteModule, RemoteWindow, etc.)");
Console.WriteLine(" 6. Run All Examples");
Console.WriteLine(" 0. Exit");
Console.WriteLine();
Console.Write("Enter your choice (0-6): ");
string? input = Console.ReadLine();
if (!int.TryParse(input, out int choice))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Invalid input. Please enter a number between 0 and 6.");
Console.ResetColor();
continue;
}
Console.WriteLine();
try
{
switch (choice)
{
case 1:
BasicMemoryOperations.RunAll();
break;
case 2:
PatternScanning.RunAll();
break;
case 3:
await ExecutionModels.RunAll();
break;
case 4:
FunctionHooking.RunAll();
break;
case 5:
HighLevelAPI.RunAll();
break;
case 6:
// Run all examples sequentially
BasicMemoryOperations.RunAll();
Console.WriteLine("\n" + new string('=', 70) + "\n");
await Task.Delay(500);
PatternScanning.RunAll();
Console.WriteLine("\n" + new string('=', 70) + "\n");
await Task.Delay(500);
await ExecutionModels.RunAll();
Console.WriteLine("\n" + new string('=', 70) + "\n");
await Task.Delay(500);
FunctionHooking.RunAll();
Console.WriteLine("\n" + new string('=', 70) + "\n");
await Task.Delay(500);
HighLevelAPI.RunAll();
break;
case 0:
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Exiting WhiteMagic Examples. Thank you!");
Console.ResetColor();
return;
default:
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Invalid choice. Please enter a number between 0 and 6.");
Console.ResetColor();
break;
}
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"\n✗ Error running example: {ex.Message}");
Console.WriteLine($" Stack trace: {ex.StackTrace}");
Console.ResetColor();
}
if (choice != 0)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Press any key to continue...");
Console.ResetColor();
Console.ReadKey();
Console.Clear();
}
}
}
}
+245
View File
@@ -0,0 +1,245 @@
# WhiteMagic Examples
This directory contains comprehensive examples demonstrating all major features of the WhiteMagic library.
## Prerequisites
- **Target Process**: Most examples use Notepad as the target. Launch Notepad before running the examples.
- **Administrator Privileges**: Some operations require elevated privileges. Run Visual Studio or terminal as Administrator.
- **.NET 8.0 SDK**: Ensure you have .NET 8.0 installed.
## Running the Examples
### From Visual Studio
1. Open `WhiteMagic.slnx` in Visual Studio
2. Set `WhiteMagic.Examples` as the startup project
3. Press F5 to run
### From Command Line
```bash
# Navigate to the examples directory
cd WhiteMagic.Examples
# Run the examples
dotnet run
```
## Example Categories
### 1. Basic Memory Operations (`Example1_BasicMemoryOperations.cs`)
**Demonstrates:**
- Reading and writing primitive types (int, float, double, bool)
- Reading and writing arrays
- Reading and writing strings (ANSI and Unicode)
- Reading and writing raw bytes
- Using `RemotePointer` for fluent pointer arithmetic
- Relative addressing (module-relative offsets)
- Error handling for memory operations
- Working with custom structs
**Key Takeaways:**
- All memory operations go through the `Magic.Memory` API
- Use `RemotePointer` for clean, fluent pointer arithmetic
- Reads throw exceptions on failure; writes return `false`
- Prefer blittable types for best performance
### 2. Pattern Scanning (`Example2_PatternScanning.cs`)
**Demonstrates:**
- Simple pattern scans with wildcards
- Multiple pattern scans in one operation
- Region-specific scanning (e.g., .text section only)
- Finding function signatures
- Cached pattern scanning for performance
- Flexible wildcard patterns
- Signature-based scanning (code caves, NOPs, INT3s)
- Pattern validation
**Key Takeaways:**
- Use IDA-style patterns: `"48 8B ? ? ? ? ?"` where `?` is a wildcard
- Cache results for repeated scans
- Narrow search region when possible for better performance
### 3. Execution Models (`Example3_ExecutionModels.cs`)
**Demonstrates:**
- **RemoteThreadExecutor**: `CreateRemoteThread` for thread-agnostic calls
- **MainThreadPump**: Crash-safe execution for state-sensitive calls
- **InProcessInvoker**: Direct delegates for in-process calls
- Choosing the right execution model
- Combining execution models (inject then switch)
- Error handling for each model
**Key Takeaways:**
- **CRITICAL**: Use the right model for the payload:
- `RemoteThreadExecutor`: Only for thread-agnostic functions (WinAPI, DLL injection)
- `MainThreadPump`: For state-sensitive calls (game state, scripting, UI)
- `InProcessInvoker`: Only after DLL injection
- Using the wrong model crashes the target application
### 4. Function Hooking (`Example4_FunctionHooking.cs`)
**Demonstrates:**
- **Inline Detours**: Function hooking with trampolines
- Detours with parameter modification
- Detours with return value modification
- Multiple detours (chain hooking)
- **Byte Patching**: Named byte patches
- NOP patching (removing instructions)
- Conditional patching (toggle on/off)
- Prologue validation and safety
- Automatic restoration on disposal
**Key Takeaways:**
- Detours ONLY work in-process (requires DLL injection)
- Patches work externally (no injection required)
- `DetourManager`/`PatchManager` are NOT thread-safe
- All hooks and patches auto-restore on disposal
### 5. High-Level API (`Example5_HighLevelAPI.cs`)
**Demonstrates:**
- **RemotePointer**: Fluent pointer arithmetic with `[]` indexing
- **RemoteModule**: Module enumeration and export resolution
- **RemoteFunction**: Function resolution and execution
- **ManagedPeb**/**ManagedTeb**: Typed PEB/TEB access
- **RemoteWindow**: Window manipulation (move, resize, flash, activate)
- Memory allocation and freeing
- Module pattern scanning
- Export function iteration
- Chaining high-level operations
**Key Takeaways:**
- Use high-level APIs for cleaner, more readable code
- `magic["ModuleName"]` returns a `RemoteModule`
- `module["FunctionName"]` returns a `RemoteFunction`
- All high-level operations are built on top of the core memory API
## Common Patterns
### Reading a Nested Structure
```csharp
// GameManager -> PlayerList -> Player[i] -> Health
IntPtr gameManagerPtr = magic.Memory.ImageBase + 0x1000;
IntPtr playerListPtr = magic.Memory.Read<IntPtr>(gameManagerPtr + 0x20);
IntPtr playerPtr = magic.Memory.Read<IntPtr>(playerListPtr + (playerIndex * 8));
int health = magic.Memory.Read<int>(playerPtr + 0x4);
```
### Using RemotePointer for Cleaner Code
```csharp
int health = magic[gameManagerPtr]
.Read<IntPtr>(0x20) // PlayerList
.Let(ptr => magic[ptr]
.Read<IntPtr>(playerIndex * 8)) // Player
.Let(ptr => magic[ptr]
.Read<int>(0x4)); // Health
```
### Safe Retry Loop for Writes
```csharp
for (int i = 0; i < 5; i++)
{
if (magic.Memory.Write(address, value))
break;
Thread.Sleep(100 * (1 << i)); // Exponential backoff
}
```
### Crash-Safe State Access
```csharp
// WRONG (crashes in most games)
int health = magic.RemoteThread.Execute<int>(fn, CallConvention.Cdecl);
// RIGHT (crash-safe)
var pump = magic.CreateMainThreadPump(frameAddress);
int health = await pump.Enqueue(() => magic.Memory.Read<int>(healthAddress));
```
## Troubleshooting
### "Process is not open for read/write"
**Cause**: Process has exited or handle is invalid
**Solution**: Ensure process is still running and handle is valid
### "Read returns default value"
**Cause**: Address is invalid or memory is protected
**Solution**: Verify address with debugger; check memory protection
### "Write returns false"
**Cause**: Memory is read-only or process has exited
**Solution**: Retry with delay; use `PatchManager` for code patches
### "CreateRemoteThread failed"
**Cause**: Insufficient permissions or target is protected
**Solution**: Run as Administrator; check target process protection
### "Crash when calling target function"
**Cause**: Calling single-threaded function from remote thread
**Solution**: Use `MainThreadPump` instead of `RemoteThreadExecutor`
### "Detour failed: prologue too short"
**Cause**: Function prologue is shorter than minimum (5 bytes)
**Solution**: Hook different function; patch deeper into function
## Further Reading
- [Main README](../README.md) - Overview and quick start
- [Architecture Documentation](../docs/architecture.md) - System design and layer structure
- [Execution Models](../docs/execution-models.md) - Deep dive on execution strategies
- [Function Hooking](../docs/hooking.md) - DetourManager and PatchManager internals
- [Memory Access](../docs/memory-access.md) - MemoryBase and MarshalCache
- [Troubleshooting Guide](../docs/troubleshooting.md) - Common issues and solutions
## Safety Reminders
⚠️ **CRITICAL WARNINGS**:
1. **Execution Model Choice**:
- `RemoteThreadExecutor`: ONLY for thread-agnostic functions
- `MainThreadPump`: For state-sensitive calls (crash-safe)
- `InProcessInvoker`: Only after DLL injection
- **Using the wrong model crashes the target application!**
2. **Detours vs Patches**:
- Detours ONLY work in-process (requires injection)
- Patches work externally (no injection required)
3. **Thread Safety**:
- `DetourManager`/`PatchManager` are NOT thread-safe
- Synchronize concurrent modifications
4. **Handle Management**:
- Always use `using` statements or dispose `Magic` properly
- Leaked handles can cause resource exhaustion
5. **Anti-Cheat Detection**:
- Some operations (e.g., `CreateRemoteThread`) are easily detected
- Use `MainThreadPump` for stealthier operation
## Contributing
Found a bug or have a suggestion? Please open an issue on GitHub.
## License
These examples are part of the WhiteMagic project. See the main LICENSE file for details.
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\WhiteMagic\WhiteMagic.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>