using System; using System.Diagnostics; using WhiteMagic; using WhiteMagic.Hooking; namespace WhiteMagic.Examples; /// /// Example 4: Function Hooking /// Demonstrates inline detours and byte patches /// public class FunctionHooking { /// /// Target process for examples /// 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; } /// /// Example 4.1: Simple Inline Detour /// Note: Detours only work in-process (requires injection) /// 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"); } /// /// Example 4.2: Detour with Parameter Modification /// 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"); } /// /// Example 4.3: Detour with Return Value Modification /// 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)"); } /// /// Example 4.4: Multiple Detours (Chain Hooking) /// 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"); } /// /// Example 4.5: Byte Patching /// 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}"); } } /// /// Example 4.6: NOP Patching (Removing Instructions) /// 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}"); } } /// /// Example 4.7: Conditional Patching /// 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("```"); } /// /// Example 4.8: Detour Safety and Prologue Validation /// 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("```"); } /// /// Example 4.9: Automatic Restoration on Disposal /// 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"); } /// /// Run all function hooking examples /// 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"); } }