Files
whitemagic/WhiteMagic.Examples/Example4_FunctionHooking.cs
T
kbe 6300bebe33 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.
2026-07-22 22:26:07 +02:00

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");
}
}