# Function Hooking in WhiteMagic WhiteMagic provides two types of runtime code modification: **inline detours** (function hooking) and **byte patches**. Both are reversible and automatically restored on disposal. ## Overview | Feature | DetourManager | PatchManager | |---------|---------------|--------------| | **Scope** | In-process only | External + In-process | | **Mechanism** | Inline `jmp` over prologue | Named byte patch | | **Reversible** | ✅ Yes | ✅ Yes | | **Auto-restore** | ✅ Yes | ✅ Yes | | **Original call** | ✅ Via trampoline | ❌ No | ## DetourManager (Inline Function Hooking) ### What is a Detour? An inline detour overwrites the first bytes of a function's prologue with a jump instruction (`jmp` or `push/ret`) that redirects execution to your hook delegate. The original bytes are saved in a **trampoline** that can call the original function. ### How It Works ``` Original function: │ push ebp │ mov ebp, esp │ sub esp, 0x10 │ ... rest of function After detour: │ jmp [hook_address] ← Overwrites prologue Trampoline: │ push ebp ← Saved original bytes │ mov ebp, esp │ jmp [original+5] ← Jumps back to original function ``` ### Use Cases - **API interception:** Hook WinAPI functions (e.g., `CreateFileW` to monitor file access) - **Function replacement:** Replace a game function with your own logic - **Behavior modification:** Change parameters or return values - **Profiling/instrumentation:** Count calls, measure execution time ### Usage Example ```csharp using WhiteMagic; using WhiteMagic.Hooking; // In-process only using var magic = Magic.OpenInProcess(); // Define your hook delegate [DllImport("kernel32.dll")] public delegate void SleepDelegate(uint dwMilliseconds); public static void MySleep(uint ms) { Console.WriteLine($"Sleep called with {ms}ms"); // Optionally call original // originalSleep(ms); } // Apply the detour IntPtr sleepAddr = magic["kernel32"]["Sleep"].Address; var detour = magic.DetourManager.Detour( sleepAddr, (SleepDelegate)MySleep ); detour.Apply(); // ... use the hook // Remove and restore original detour.Remove(); ``` ### CallOriginal (Trampoline) To call the original function from your hook: ```csharp static MyDetourDelegate Original = null!; static void MyHook(int arg1, float arg2) { // Do something before Console.WriteLine($"Before: {arg1}, {arg2}"); // Call original int result = Original(arg1, arg2); // Do something after Console.WriteLine($"After: {result}"); return result; } // Create detour with original var detour = magic.DetourManager.Detour( targetAddress, (MyDetourDelegate)MyHook, out var original ); Original = original; detour.Apply(); ``` ### Prologue Safety Before splicing a detour, WhiteMagic validates the prologue: **Default (StubAssembler):** - Covers common x86/x64 prologue shapes: - `push ebp; mov ebp, esp` - `mov edi, edi` (hot-patch padding) - `sub rsp, XX` (x64 stack allocation) - Single-byte instructions (`nop`, `int3`) **Optional (IcedAssembler):** - Full disassembly validation via Iced - Handles arbitrary prologues - Ensures splice lands on instruction boundaries ### Safety Considerations **Risks:** - **Mid-instruction splice:** Crashes if prologue validation fails - **Race conditions:** Patching while code is running can crash - **Anti-cheat:** Detours are easily detected **Mitigations:** - Validate prologue before patching - Pause threads during application (use `DetourManager.Apply(options)`) - Use `MainThreadPump` for crash-safe execution - Auto-restore on `Dispose` ### Thread Safety **DetourManager is NOT thread-safe.** ```csharp // WRONG: Concurrent modifications Task.Run(() => detour1.Apply()); Task.Run(() => detour2.Apply()); // RIGHT: Synchronize lock (magic.DetourManager) { detour1.Apply(); detour2.Apply(); } ``` ### Performance Impact - **Hook overhead:** ~5-10 CPU cycles (single jmp) - **Trampoline call:** ~20-30 cycles (jmp + trampoline execution) - **Best practice:** Keep hook delegates short --- ## PatchManager (Named Byte Patches) ### What is a Patch? A patch is a named byte buffer that overwrites a region of memory. Unlike detours, patches can be any bytes and don't include a trampoline mechanism. ### Use Cases - **Patching constants:** Change a hardcoded value (e.g., max HP cap) - **NOP-ing code:** Remove instructions (e.g., bypass a check) - **Restoring bytes:** Undo anti-cheat modifications - **Hot-patching:** Patch the hot-patch region (common in WinAPI) ### Usage Example ```csharp using WhiteMagic; using WhiteMagic.Hooking; using var magic = Magic.Open(targetProcess); // Create a patch to NOP 5 bytes var patch = magic.PatchManager.Create( "MaxHealthCapPatch", address, new byte[] { 0x90, 0x90, 0x90, 0x90, 0x90 } // NOP x5 ); patch.Apply(); // Check status if (patch.IsApplied) { Console.WriteLine("Patch applied"); } // Remove and restore patch.Remove(); ``` ### VirtualProtect Dance When applying a patch, `PatchManager` automatically: 1. Calls `VirtualProtectEx` to make region writable 2. Writes the patch bytes 3. Calls `VirtualProtectEx` to restore original protection ### Relative Addressing Patches support relative addressing: ```csharp // Apply relative to module base var patch = magic.PatchManager.Create( "DataPatch", address, // Interpreted as relative if isRelative=true patchBytes, isRelative: true ); ``` ### Thread Safety **PatchManager is NOT thread-safe.** Synchronize concurrent modifications. --- ## Lifecycle Management Both managers auto-restore on disposal: ```csharp // All applied detours and patches auto-restore using (var magic = Magic.OpenInProcess()) { var detour = magic.DetourManager.Detour(addr, hook); detour.Apply(); var patch = magic.PatchManager.Create("Patch", addr, bytes); patch.Apply(); // End of using block: detour.Remove() and patch.Remove() called automatically } ``` ### Explicit Disposal ```csharp var detour = magic.DetourManager.Detour(addr, hook); detour.Apply(); // Later detour.Remove(); // Restores original bytes ``` --- ## Advanced: Memory Protection ### Handling Read-Only Memory If the target region is protected (e.g., `.text` section), both managers use: ```csharp VirtualProtectEx(handle, address, size, PAGE_EXECUTE_READWRITE, out oldProt); // Write bytes VirtualProtectEx(handle, address, size, oldProt, out _); ``` ### Risks - **Anti-cheat:** May detect protection changes - **Race conditions:** Other threads might execute during patch window - **Crashes:** If code executes during the brief writable window --- ## Advanced: Conditional Patching ```csharp public class ConditionalPatch { private readonly Patch _patch; private bool _enabled; public bool Enabled { get => _enabled; set { if (_enabled == value) return; if (value) _patch.Apply(); else _patch.Remove(); _enabled = value; } } } ``` --- ## Advanced: Chain Hooking Multiple hooks on the same function: ```csharp // First hook var detour1 = magic.DetourManager.Detour(addr, Hook1); detour1.Apply(out var trampoline1); // Second hook (trampoline from first) var detour2 = magic.DetourManager.Detour( trampoline1.TrampolineAddress, Hook2 ); detour2.Apply(); // Execution flow: Original → Hook2 → Hook1 → Trampoline1 → Original+5 ``` --- ## Comparison to Other Libraries | Library | Detours | Patches | Auto-restore | In-process | |---------|---------|---------|---------------|------------| | **WhiteMagic** | ✅ | ✅ | ✅ | ✅ Required | | BlackMagic | ❌ | ❌ | ❌ | N/A | | MemorySharp | ❌ (planned) | ❌ (planned) | N/A | N/A | | GreyMagic | ✅ | ✅ | ✅ | ✅ Required | --- ## Troubleshooting ### "Detour failed: prologue too short" **Cause:** Function prologue is shorter than minimum required (5 bytes for x86 `jmp`, 14 bytes for x64 `push/ret`). **Solution:** - Use a different function - Patch deeper into the function (after prologue) - For x86, use hot-patch area (`mov edi, edi` padding) ### "Patch failed: access violation" **Cause:** Memory region is protected or invalid. **Solution:** - Verify address is valid (use `Magic.Memory.CanRead`) - Ensure process has `PROCESS_VM_OPERATION` access - Check if anti-cheat is blocking writes ### "Crash after detour" **Cause:** Prologue validation failed or mid-instruction splice. **Solution:** - Enable Iced backend for full validation - Pause threads during application - Check if target is using code obfuscation ### "Hook not called" **Cause:** Wrong function address or hook installed after target already called it. **Solution:** - Verify address with debugger - Install hook early (before target uses function) - Check if target is using a different implementation (e.g., forwarded export) --- ## Further Reading - [Architecture](./architecture.md) — Hooking layer design - [Execution Models](./execution-models.md) — Safe hook execution - [Memory Access](./memory-access.md) — Reading/writing memory