Skip to content

Instantly share code, notes, and snippets.

@0xF6
Created September 10, 2022 09:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xF6/b8714191830dd604420e0c59c4da6e80 to your computer and use it in GitHub Desktop.
Save 0xF6/b8714191830dd604420e0c59c4da6e80 to your computer and use it in GitHub Desktop.
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