/// <summary> /// Creates a hook for a function at a given address. /// </summary> /// <param name="function">The function to detour the original function to.</param> /// <param name="functionAddress">The address of the function to hook.</param> /// <param name="minHookLength">Optional explicit length of hook. Use only in rare cases where auto-length check overflows a jmp/call opcode.</param> public Hook(TFunction function, long functionAddress, int minHookLength = -1) { _is64Bit = IntPtr.Size == 8; ReverseWrapper = CreateReverseWrapper(function); /* * === Hook Summary === * * A. Insert Absolute Jump to ReverseWrapper (Convention => CDECL Marshaller) * A1. Backup original bytes and patch between start and end of JMP for (B). * * B. Setup Wrapper to call original function (CDECL => Convention Marshaller) * B1. Take bytes backed up from A, and create stub function with those * bytes and JMP to end of hook. * B2. Assign OriginalFunction to that function stub. * * Note: For X64 the same principles apply, just replace CDECL with Microsoft calling convention. */ Mutex.MakeHookMutex.WaitOne(); /* Create Convention => CDECL Wrapper. */ List <byte> jumpOpcodes = Utilities.AssembleAbsoluteJump(ReverseWrapper.WrapperPointer, _is64Bit).ToList(); /* Calculate Hook Length (Unless Explicit) */ if (minHookLength == -1) { minHookLength = Utilities.GetHookLength((IntPtr)functionAddress, jumpOpcodes.Count, _is64Bit); } // Sometimes our hook can be larger than the amount of bytes taken by the jmp opcode. // We need to fill the remaining bytes with NOPs. Utilities.FillArrayUntilSize <byte>(jumpOpcodes, 0x90, minHookLength); /* Get bytes from original function prologue and patch them. */ CurrentProcess.SafeReadRaw((IntPtr)functionAddress, out byte[] originalFunction, minHookLength); var functionPatcher = new FunctionPatcher(_is64Bit); var functionPatch = functionPatcher.Patch(originalFunction.ToList(), (IntPtr)functionAddress); IntPtr hookEndAddress = (IntPtr)(functionAddress + minHookLength); functionPatch.NewFunction.AddRange(Utilities.AssembleAbsoluteJump(hookEndAddress, _is64Bit)); /* Second wave of patching. */ var icedPatcher = new IcedPatcher(_is64Bit, functionPatch.NewFunction.ToArray(), (IntPtr)functionAddress); /* Create Hook instance. */ OriginalFunctionAddress = icedPatcher.ToMemoryBuffer(); OriginalFunction = CreateWrapper((long)icedPatcher.ToMemoryBuffer(), out IntPtr originalFunctionWrapperAddress); OriginalFunctionWrapperAddress = originalFunctionWrapperAddress; _otherHookPatches = functionPatch.Patches; _hookPatch = new Patch((IntPtr)functionAddress, jumpOpcodes.ToArray()); Mutex.MakeHookMutex.ReleaseMutex(); }
/// <summary> /// Creates a hook for a function at a given address. /// </summary> /// <param name="functionAddress">The address of the function to hook.</param> /// <param name="minHookLength">Optional explicit length of hook. Use only in rare cases where auto-length check overflows a jmp/call opcode.</param> /// <param name="options">Options which control the hook generation procedure.</param> private void CreateHook(nuint functionAddress, int minHookLength = -1, FunctionHookOptions options = null) { // Set options if not passed in. if (options == null) { Misc.TryGetAttribute <TFunction, FunctionHookOptions>(out options); options ??= new FunctionHookOptions(); } /* * === Hook Summary === * * A. Insert Absolute Jump to ReverseWrapper (Convention => CDECL Marshaller) * A1. Backup original bytes and patch between start and end of JMP for (B). * * B. Setup Wrapper to call original function (CDECL => Convention Marshaller) * B1. Take bytes backed up from A, and create stub function with those * bytes and JMP to end of hook. * B2. Assign OriginalFunction to that function stub. * * Note: For X64 the same principles apply, just replace CDECL with Microsoft calling convention. */ /* Create Target Convention => TFunction Wrapper. */ var jumpOpcodes = options.PreferRelativeJump ? Utilities.TryAssembleRelativeJump(functionAddress, ReverseWrapper.WrapperPointer.ToUnsigned(), _is64Bit, out _) : Utilities.AssembleAbsoluteJump(ReverseWrapper.WrapperPointer.ToUnsigned(), _is64Bit).ToList(); /* Calculate Hook Length (Unless Explicit) */ if (minHookLength == -1) { minHookLength = Utilities.GetHookLength(functionAddress, jumpOpcodes.Count, _is64Bit); } // Sometimes our hook can be larger than the amount of bytes taken by the jmp opcode. // We need to fill the remaining bytes with NOPs. Utilities.FillArrayUntilSize <byte>(jumpOpcodes, 0x90, minHookLength); /* Get bytes from original function prologue and patch them. */ CurrentProcess.SafeReadRaw(functionAddress, out byte[] originalFunction, minHookLength); var functionPatcher = new FunctionPatcher(_is64Bit, options); var functionPatch = functionPatcher.Patch(originalFunction.ToList(), functionAddress); nuint hookEndAddress = functionAddress + (nuint)minHookLength; /* Second wave of patching. */ var icedPatcher = new IcedPatcher(_is64Bit, functionPatch.NewFunction.ToArray(), functionAddress); /* Create Hook instance. */ OriginalFunctionAddress = icedPatcher.ToMemoryBuffer(hookEndAddress).ToSigned(); OriginalFunction = CreateWrapper(icedPatcher.ToMemoryBuffer(null), out nuint originalFunctionWrapperAddress); OriginalFunctionWrapperAddress = originalFunctionWrapperAddress.ToSigned(); _otherHookPatches = functionPatch.Patches; _hookPatch = new Patch(functionAddress, jumpOpcodes.ToArray()); }