Created
September 10, 2022 09:29
-
-
Save 0xF6/b8714191830dd604420e0c59c4da6e80 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class HookFx<T> where T : Delegate | |
{ | |
private readonly nint _original; | |
private readonly T _target; | |
private byte[] _originalByteCode = new byte[0]; | |
private bool isInstalled; | |
public HookFx(nint original, T target) | |
{ | |
_original = original; | |
_target = target; | |
} | |
public void Install() | |
{ | |
if (isInstalled) throw new Exception("Already installed"); | |
_originalByteCode = PatchJMP(_original, Marshal.GetFunctionPointerForDelegate<T>(_target)); | |
isInstalled = true; | |
} | |
public void Uninstall() | |
{ | |
if (!isInstalled) throw new Exception("Not installed"); | |
UnhookJMP(_original, _originalByteCode); | |
isInstalled = false; | |
} | |
private static unsafe byte[] PatchJMP(nint originalSite, nint replacementSite) | |
{ | |
var is64 = nint.Size == 8; | |
//instruction opcodes are 13 bytes on 64-bit, 7 bytes on 32-bit | |
uint offset = (is64 ? 13u : 6u); | |
//we store the original opcodes for restoration later | |
byte[] originalOpcodes = new byte[offset]; | |
unsafe | |
{ | |
//segfault protection | |
var oldProtecton = VirtualProtect(originalSite, (uint)originalOpcodes.Length, | |
NativeApi.Protection.PAGE_EXECUTE_READWRITE); | |
//get unmanaged function pointer to address of original site | |
byte* originalSitePointer = (byte*)originalSite.ToPointer(); | |
//copy the original opcodes | |
for (int k = 0; k < offset; k++) | |
originalOpcodes[k] = *(originalSitePointer + k); | |
if (is64) | |
{ | |
//mov r11, replacementSite | |
*originalSitePointer = 0x49; | |
*(originalSitePointer + 1) = 0xBB; | |
*((ulong*)(originalSitePointer + 2)) = (ulong)replacementSite.ToInt64(); //sets 8 bytes | |
//jmp r11 | |
*(originalSitePointer + 10) = 0x41; | |
*(originalSitePointer + 11) = 0xFF; | |
*(originalSitePointer + 12) = 0xE3; | |
} | |
else | |
{ | |
//push replacementSite | |
*originalSitePointer = 0x68; | |
*((uint*)(originalSitePointer + 1)) = (uint)replacementSite.ToInt32(); //sets 4 bytes | |
//ret | |
*(originalSitePointer + 5) = 0xC3; | |
} | |
//flush insutruction cache to make sure our new code executes | |
FlushInstructionCache(originalSite, (uint)originalOpcodes.Length); | |
//done | |
VirtualProtect(originalSite, (uint)originalOpcodes.Length, oldProtecton); | |
} | |
//return original opcodes | |
return originalOpcodes; | |
} | |
//architecture agnostic JMP patch removal | |
private static unsafe void UnhookJMP(nint originalSite, byte[] originalOpcodes) | |
{ | |
unsafe | |
{ | |
//segfault protection | |
NativeApi.VirtualProtect(originalSite, (uint)originalOpcodes.Length, | |
NativeApi.Protection.PAGE_EXECUTE_READWRITE, out var oldProtecton); | |
//get unmanaged function pointer to address of original site | |
byte* originalSitePointer = (byte*)originalSite.ToPointer(); | |
//put the original bytes back where they belong | |
for (int k = 0; k < originalOpcodes.Length; k++) | |
{ | |
//restore current opcode to former value | |
*(originalSitePointer + k) = originalOpcodes[k]; | |
} | |
//flush insutruction cache to make sure our new code executes | |
FlushInstructionCache(originalSite, (uint)originalOpcodes.Length); | |
//done | |
VirtualProtect(originalSite, (uint)originalOpcodes.Length, oldProtecton); | |
} | |
} | |
//Wrapper around native method VirtualProtect. Changes the protection on a region of memory and returns the old protection level. | |
private static NativeApi.Protection VirtualProtect(nint address, uint size, NativeApi.Protection protectionFlags) | |
{ | |
NativeApi.Protection oldProtection; | |
//call VirtualProtect on the region of memory, | |
if (!NativeApi.VirtualProtect(address, size, protectionFlags, out oldProtection)) | |
throw new Win32Exception(); | |
//return old protection level | |
return oldProtection; | |
} | |
//flushes instrction cache. Required to prevent old code from being executed after we hook a function | |
private static void FlushInstructionCache(nint address, uint size) | |
{ | |
//Throw an error if FlushInstructionCache fails. This is unrecoverable. | |
if (!NativeApi.FlushInstructionCache(Process.GetCurrentProcess().Handle, address, size)) | |
throw new Win32Exception(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment