Skip to content

Instantly share code, notes, and snippets.

@mipek
Created July 29, 2022 12:30
Show Gist options
  • Save mipek/64d6d1ad08dc51c89e9f7d5ae369b203 to your computer and use it in GitHub Desktop.
Save mipek/64d6d1ad08dc51c89e9f7d5ae369b203 to your computer and use it in GitHub Desktop.
Hook managed function
// MIT License
//
// Copyright (c) 2022 Michael Pekar
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ------------------------------------------------------------------------
// Originally released on unknowncheats.me by me in 2017:
// https://www.unknowncheats.me/forum/c-/213492-hook-managed-function.html#post1700496
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
class DumbHook
{
private const uint HOOK_SIZE_X64 = 12;
private const uint HOOK_SIZE_X86 = 7;
private byte[] original;
public MethodInfo OriginalMethod { get; private set; }
public MethodInfo HookMethod { get; private set; }
public DumbHook()
{
original = null;
OriginalMethod = HookMethod = null;
}
public DumbHook(MethodInfo orig, MethodInfo hook)
{
original = null;
Init(orig, hook);
}
public DumbHook(Type typeOrig, string nameOrig, Type typeHook, string nameHook)
{
original = null;
Init(Util.GetMethodByName(typeOrig, nameOrig), Util.GetMethodByName(typeHook, nameHook));
}
public void Init(MethodInfo orig, MethodInfo hook)
{
if (Util.IsNull(orig) || Util.IsNull(hook))
throw new ArgumentException("Both original and hook need to be valid methods");
RuntimeHelpers.PrepareMethod(orig.MethodHandle);
RuntimeHelpers.PrepareMethod(hook.MethodHandle);
OriginalMethod = orig;
HookMethod = hook;
}
public void Hook()
{
// Valid params?
if (Util.IsNull(OriginalMethod) || Util.IsNull(HookMethod))
throw new ArgumentException("Hook has to be properly Init'd before use");
// Check if function is already hooked
if (original != null)
return;
// Patch it
IntPtr funcFrom = OriginalMethod.MethodHandle.GetFunctionPointer();
IntPtr funcTo = HookMethod.MethodHandle.GetFunctionPointer();
uint oldProt;
if (IntPtr.Size == 8) //x86-64
{
original = new byte[HOOK_SIZE_X64];
Import.VirtualProtect(funcFrom, HOOK_SIZE_X64, 0x40, out oldProt);
unsafe
{
byte* ptr = (byte*)funcFrom;
for (int i = 0; i < HOOK_SIZE_X64; ++i)
{
original[i] = ptr[i];
}
// movabs rax, addy
// jmp rax
*(ptr) = 0x48;
*(ptr + 1) = 0xb8;
*(IntPtr*)(ptr + 2) = funcTo;
*(ptr + 10) = 0xff;
*(ptr + 11) = 0xe0;
}
Import.VirtualProtect(funcFrom, HOOK_SIZE_X64, oldProt, out oldProt);
}
else //assume x86
{
original = new byte[HOOK_SIZE_X86];
Import.VirtualProtect(funcFrom, HOOK_SIZE_X86, 0x40, out oldProt);
unsafe
{
byte* ptr = (byte*)funcFrom;
for (int i = 0; i < HOOK_SIZE_X86; ++i)
{
original[i] = ptr[i];
}
// mov eax, addy
// jmp eax
*(ptr) = 0xb8;
*(IntPtr*)(ptr + 1) = funcTo;
*(ptr + 5) = 0xff;
*(ptr + 6) = 0xe0;
}
Import.VirtualProtect(funcFrom, HOOK_SIZE_X86, oldProt, out oldProt);
}
}
public void Unhook()
{
// Check if function is hooked
if (original == null)
return;
// Restore original code
uint oldProt;
uint codeSize = (uint)original.Length;
IntPtr origAddr = OriginalMethod.MethodHandle.GetFunctionPointer();
Import.VirtualProtect(origAddr, codeSize, 0x40, out oldProt);
unsafe
{
byte* ptr = (byte*)origAddr;
for (var i = 0; i < codeSize; ++i)
{
ptr[i] = original[i];
}
}
Import.VirtualProtect(origAddr, codeSize, 0x40, out oldProt);
// The original function has been restored.
original = null;
}
internal class Import
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool VirtualProtect(IntPtr address, uint size, uint newProtect, out uint oldProtect);
}
}
/* Usage pseudo:
[MethodImpl(MethodImplOptions.NoInlining)]
public void HelloWorldHook(string hi)
{
// call original
hook.Unhook();
hook.OriginalMethod.Invoke(this, null);
hook.Hook();
}
public void GameAwake()
{
hook = new DumbHook(typeof(SomeClass), "HelloWorld", typeof(Program), "HelloWorldHook");
hook.Hook();
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment