- 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.
411 lines
18 KiB
C#
411 lines
18 KiB
C#
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");
|
|
}
|
|
}
|