private IntPtr MakeHookStub(MemoryBuffer buffer, IcedPatcher patcher, byte[] asmCode, byte[] originalCode, long jumpBackAddress, AsmHookBehaviour behaviour) { var bytes = new List <byte>(asmCode.Length + originalCode.Length); var jmpBackBytes = Utilities.AssembleAbsoluteJump((IntPtr)jumpBackAddress, _is64Bit); switch (behaviour) { case AsmHookBehaviour.ExecuteFirst: bytes.AddRange(asmCode); bytes.AddRange(patcher.EncodeForNewAddress(buffer.Properties.WritePointer + bytes.Count)); break; case AsmHookBehaviour.ExecuteAfter: bytes.AddRange(patcher.EncodeForNewAddress(buffer.Properties.WritePointer + bytes.Count)); bytes.AddRange(asmCode); break; case AsmHookBehaviour.DoNotExecuteOriginal: bytes.AddRange(asmCode); break; default: throw new ArgumentOutOfRangeException(nameof(behaviour), behaviour, null); } bytes.AddRange(jmpBackBytes); return(buffer.Add(bytes.ToArray(), 1)); // Buffer is pre-aligned }
private IntPtr MakeOriginalStub(MemoryBuffer buffer, IcedPatcher patcher, byte[] originalCode, long jumpBackAddress) { var bytes = new List <byte>(originalCode.Length); var jmpBackBytes = Utilities.AssembleAbsoluteJump((IntPtr)jumpBackAddress, _is64Bit); bytes.AddRange(patcher.EncodeForNewAddress(buffer.Properties.WritePointer)); bytes.AddRange(jmpBackBytes); return(buffer.Add(bytes.ToArray(), 1)); // Buffer is pre-aligned }
/// <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()); }
private nuint MakeOriginalStub(MemoryBuffer buffer, IcedPatcher patcher, byte[] originalCode, nuint jumpBackAddress) { var bytes = new List <byte>(originalCode.Length); bytes.AddRange(patcher.EncodeForNewAddress(buffer.Properties.WritePointer)); var jmpBackBytes = Utilities.AssembleRelativeJump(buffer.Properties.WritePointer + (nuint)bytes.Count, jumpBackAddress, _is64Bit); bytes.AddRange(jmpBackBytes); return(buffer.Add(bytes.ToArray(), 1)); // Buffer is pre-aligned }
private static void Create(Hook <TFunction> hook, ReverseWrapper <TFunction> reverseWrapper, long functionAddress, int minHookLength = -1) { Mutex.MakeHookMutex.WaitOne(); /* Create Convention => CDECL Wrapper. */ List <byte> jumpOpcodes = Utilities.AssembleAbsoluteJump(reverseWrapper.WrapperPointer, true).ToList(); /* Calculate Hook Length (Unless Explicit) */ if (minHookLength == -1) { minHookLength = Utilities.GetHookLength((IntPtr)functionAddress, jumpOpcodes.Count, ArchitectureMode.x86_64); } // 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. if (minHookLength > jumpOpcodes.Count) { int nopBytes = minHookLength - jumpOpcodes.Count; for (int x = 0; x < nopBytes; x++) { jumpOpcodes.Add(0x90); } } /* Get bytes from original function prologue and patch them. */ CurrentProcess.SafeReadRaw((IntPtr)functionAddress, out byte[] originalFunction, minHookLength); var functionPatcher = new FunctionPatcher(ArchitectureMode.x86_64); var functionPatch = functionPatcher.Patch(originalFunction.ToList(), (IntPtr)functionAddress); IntPtr hookEndAddress = (IntPtr)(functionAddress + minHookLength); functionPatch.NewFunction.AddRange(Utilities.AssembleAbsoluteJump(hookEndAddress, true)); /* Second wave of patching. */ var icedPatcher = new IcedPatcher(true, functionPatch.NewFunction.ToArray(), (IntPtr)functionAddress); /* Create Hook instance. */ hook.OriginalFunctionAddress = icedPatcher.GetFunctionAddress(); hook.OriginalFunction = Wrapper.Create <TFunction>((long)icedPatcher.GetFunctionAddress(), out IntPtr originalFunctionWrapperAddress); hook.OriginalFunctionWrapperAddress = originalFunctionWrapperAddress; hook.ReverseWrapper = reverseWrapper; hook._otherHookPatches = functionPatch.Patches; hook._hookPatch = new Patch((IntPtr)functionAddress, jumpOpcodes.ToArray()); Mutex.MakeHookMutex.ReleaseMutex(); }
/// <summary> /// Creates a cheat engine style hook, replacing instruction(s) with a JMP to a user provided set of ASM instructions (and optionally the original ones). /// </summary> /// <param name="asmCode">The assembly code to execute, precompiled.</param> /// <param name="functionAddress">The address of the function or mid-function to hook.</param> /// <param name="behaviour">Defines what should be done with the original code that was replaced with the JMP instruction.</param> /// <param name="hookLength">Optional explicit length of hook. Use only in rare cases where auto-length check overflows a jmp/call opcode.</param> public AsmHook(byte[] asmCode, long functionAddress, AsmHookBehaviour behaviour = AsmHookBehaviour.ExecuteFirst, int hookLength = -1) : this() { /* * === Hook Summary === * * A. Backup Original Code & Generate Function Disable Stub * A1. Find amount of bytes required to insert JMP opcode, backup original bytes. * B2. Function disable stub will contain original code and jump back to end of hook. * * B. Generate Function Stub. * B1. For stub, generate code combining asmCode and original instructions, depending on AsmHookBehaviour * B2. Add jmp back to end of original hook. * * Graph: * Original => Stub Entry (JMP To Hook or Original Copy) => Hook/Original Copy with JMP back. * * Notes: For hook disable, replace jmp address to function disable stub. * For hook re-enable, replace jmp address to function hook stub. */ if (hookLength == -1) { hookLength = Utilities.GetHookLength((IntPtr)functionAddress, MaxJmpSize, _is64Bit); } CurrentProcess.SafeReadRaw((IntPtr)functionAddress, out byte[] originalFunction, hookLength); long jumpBackAddress = functionAddress + hookLength; /* Size calculations for buffer, must have sufficient space. */ // Stubs: // Stub Entry => Stub Hook. // Stub Hook => Caller. // Disable.Original Stub => Caller. int codeAlignment = 4; // Alignment of code in memory. int numberOfStubs = 3; // Also number of allocations. int alignmentRequiredBytes = (codeAlignment * numberOfStubs); int stubEntrySize = MaxJmpSize; int stubHookSize = asmCode.Length + hookLength + MaxJmpSize; int stubOriginalSize = hookLength + MaxJmpSize; int requiredSizeOfBuffer = stubEntrySize + stubHookSize + stubOriginalSize + alignmentRequiredBytes; var buffer = Utilities.FindOrCreateBufferInRange(requiredSizeOfBuffer); buffer.ExecuteWithLock(() => { var patcher = new IcedPatcher(_is64Bit, originalFunction, (IntPtr)functionAddress); // Make Hook and Original Stub buffer.SetAlignment(codeAlignment); IntPtr hookStubAddr = MakeHookStub(buffer, patcher, asmCode, originalFunction, jumpBackAddress, behaviour); buffer.SetAlignment(codeAlignment); IntPtr originalStubAddr = MakeOriginalStub(buffer, patcher, originalFunction, jumpBackAddress); // Make Jump to Entry, Original Stub byte[] jmpToOriginal = Utilities.AssembleAbsoluteJump(originalStubAddr, _is64Bit); byte[] jmpToHook = Utilities.AssembleAbsoluteJump(hookStubAddr, _is64Bit); // Make Entry Stub IntPtr entryStubAddr = buffer.Add(jmpToHook, codeAlignment); // Make Disable/Enable _disableHookPatch = new Patch(entryStubAddr, jmpToOriginal); _enableHookPatch = new Patch(entryStubAddr, jmpToHook); // Make Hook Enabler var jumpOpcodes = Utilities.AssembleAbsoluteJump(entryStubAddr, _is64Bit).ToList(); Utilities.FillArrayUntilSize <byte>(jumpOpcodes, 0x90, hookLength); _activateHookPatch = new Patch((IntPtr)functionAddress, jumpOpcodes.ToArray()); return(true); }); }
private static void Create(Hook <TFunction> hook, ReverseWrapper <TFunction> reverseWrapper, long functionAddress, int minHookLength = -1) { /* * === 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. */ Mutex.MakeHookMutex.WaitOne(); /* Create Convention => CDECL Wrapper. */ List <byte> jumpOpcodes = Utilities.AssembleAbsoluteJump(reverseWrapper.WrapperPointer, false).ToList(); /* Calculate Hook Length (Unless Explicit) */ if (minHookLength == -1) { minHookLength = Utilities.GetHookLength((IntPtr)functionAddress, jumpOpcodes.Count, ArchitectureMode.x86_32); } // 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. if (minHookLength > jumpOpcodes.Count) { int nopBytes = minHookLength - jumpOpcodes.Count; for (int x = 0; x < nopBytes; x++) { jumpOpcodes.Add(0x90); } } /* Get bytes from original function prologue and patch them. */ CurrentProcess.SafeReadRaw((IntPtr)functionAddress, out byte[] originalFunction, minHookLength); var functionPatcher = new FunctionPatcher(ArchitectureMode.x86_32); var functionPatch = functionPatcher.Patch(originalFunction.ToList(), (IntPtr)functionAddress); IntPtr hookEndAddress = (IntPtr)(functionAddress + minHookLength); functionPatch.NewFunction.AddRange(Utilities.AssembleAbsoluteJump(hookEndAddress, false)); /* Second wave of patching. */ var icedPatcher = new IcedPatcher(false, functionPatch.NewFunction.ToArray(), (IntPtr)functionAddress); /* Create Hook instance. */ hook.OriginalFunctionAddress = icedPatcher.GetFunctionAddress(); hook.OriginalFunction = Wrapper.Create <TFunction>((long)icedPatcher.GetFunctionAddress(), out IntPtr originalFunctionWrapperAddress); hook.OriginalFunctionWrapperAddress = originalFunctionWrapperAddress; hook.ReverseWrapper = reverseWrapper; hook._otherHookPatches = functionPatch.Patches; hook._hookPatch = new Patch((IntPtr)functionAddress, jumpOpcodes.ToArray()); Mutex.MakeHookMutex.ReleaseMutex(); }
/// <summary> /// Creates a cheat engine style hook, replacing instruction(s) with a JMP to a user provided set of ASM instructions (and optionally the original ones). /// </summary> /// <param name="asmCode">The assembly code to execute, precompiled.</param> /// <param name="functionAddress">The address of the function or mid-function to hook.</param> /// <param name="options">The options used for creating the assembly hook.</param> public AsmHook(byte[] asmCode, nuint functionAddress, AsmHookOptions options = default) : this() { options ??= new AsmHookOptions(); /* * === Hook Summary === * * A. Backup Original Code & Generate Function Disable Stub * A1. Find amount of bytes required to insert JMP opcode, backup original bytes. * B2. Function disable stub will contain original code and jump back to end of hook. * * B. Generate Function Stub. * B1. For stub, generate code combining asmCode and original instructions, depending on AsmHookBehaviour * B2. Add jmp back to end of original hook. * * Graph: * Original => Stub Entry (JMP To Hook or Original Copy) => Hook/Original Copy with JMP back. * * Notes: For hook disable, replace jmp address to function disable stub. * For hook re-enable, replace jmp address to function hook stub. */ if (options.hookLength == -1) { options.hookLength = Utilities.GetHookLength(functionAddress, options.MaxOpcodeSize, _is64Bit); } CurrentProcess.SafeReadRaw(functionAddress, out byte[] originalFunction, options.hookLength); nuint jumpBackAddress = functionAddress + (nuint)options.hookLength; /* Size calculations for buffer, must have sufficient space. */ // Stubs: // Stub Entry => Stub Hook. // Stub Hook => Caller. // Disable.Original Stub => Caller. int codeAlignment = 16; // Alignment of code in memory. int numberOfStubs = 3; // Also number of allocations. int alignmentRequiredBytes = (codeAlignment * numberOfStubs); int pointerSize = (_is64Bit ? 8 : 4); int pointerRequiredBytes = pointerSize * 2; // 2 calls to AssembleAbsoluteJump int stubEntrySize = Constants.MaxAbsJmpSize; int stubHookSize = asmCode.Length + options.hookLength + Constants.MaxAbsJmpSize; int stubOriginalSize = options.hookLength + Constants.MaxAbsJmpSize + pointerSize; // 1 call to AssembleAbsoluteJump int requiredSizeOfBuffer = stubEntrySize + stubHookSize + stubOriginalSize + alignmentRequiredBytes + pointerRequiredBytes; var minMax = Utilities.GetRelativeJumpMinMax(functionAddress, Int32.MaxValue - requiredSizeOfBuffer); var buffer = Utilities.FindOrCreateBufferInRange(requiredSizeOfBuffer, minMax.min, minMax.max); buffer.ExecuteWithLock(() => { var patcher = new IcedPatcher(_is64Bit, originalFunction, functionAddress); // Make Hook and Original Stub buffer.SetAlignment(codeAlignment); nuint hookStubAddr = MakeHookStub(buffer, patcher, asmCode, originalFunction, jumpBackAddress, options.Behaviour); buffer.SetAlignment(codeAlignment); nuint originalStubAddr = MakeOriginalStub(buffer, patcher, originalFunction, jumpBackAddress); // Make Jump to Entry, Original Stub buffer.SetAlignment(codeAlignment); var currAddress = buffer.Properties.WritePointer; byte[] jmpToOriginal = Utilities.AssembleRelativeJump(currAddress, originalStubAddr, _is64Bit); byte[] jmpToHook = Utilities.AssembleRelativeJump(currAddress, hookStubAddr, _is64Bit); // Make Entry Stub nuint entryStubAddr = buffer.Add(jmpToHook, codeAlignment); // Make Disable/Enable _disableHookPatch = new Patch(entryStubAddr, jmpToOriginal); _enableHookPatch = new Patch(entryStubAddr, jmpToHook); // Make Hook Enabler var jumpOpcodes = options.PreferRelativeJump ? Utilities.AssembleRelativeJump(functionAddress, entryStubAddr, _is64Bit).ToList() : Utilities.AssembleAbsoluteJump(entryStubAddr, _is64Bit).ToList(); Utilities.FillArrayUntilSize <byte>(jumpOpcodes, 0x90, options.hookLength); _activateHookPatch = new Patch(functionAddress, jumpOpcodes.ToArray()); return(true); }); }