/// <summary> /// Patches all jumps pointing to originalJmpTarget to point to newJmpTarget. /// </summary> /// <param name="searchRange">Range of addresses where to patch jumps.</param> /// <param name="originalJmpTarget">Address range of JMP targets to patch with newJmpTarget.</param> /// <param name="newJmpTarget">The new address instructions should jmp to.</param> internal List <Patch> PatchJumpTargets(AddressRange searchRange, AddressRange originalJmpTarget, long newJmpTarget) { var patches = new List <Patch>(); int length = (int)(searchRange.EndPointer - searchRange.StartPointer); CurrentProcess.SafeReadRaw((IntPtr)searchRange.StartPointer, out byte[] memory, length); Disassembler disassembler = new Disassembler(memory, _architecture, (ulong)searchRange.StartPointer, true); Instruction[] instructions = disassembler.Disassemble().ToArray(); for (int x = 0; x < instructions.Length; x++) { Instruction instruction = instructions[x]; Instruction nextInstruction = (x + 1 < instructions.Length) ? instructions[x + 1] : null; if (IsRelativeJump(instruction)) { PatchRelativeJump(instruction, ref originalJmpTarget, newJmpTarget, patches); } if (IsRIPRelativeJump(instruction)) { PatchRIPRelativeJump(instruction, ref originalJmpTarget, newJmpTarget, patches); } if (IsPushReturn(instruction, nextInstruction)) { PatchPushReturn(instruction, ref originalJmpTarget, newJmpTarget, patches); } } // Return all the addresses to patch!. return(patches); }
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. */ /* 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)); /* Commit the original modified function to memory. */ byte[] patchedFunction = functionPatch.NewFunction.ToArray(); var buffer = Utilities.FindOrCreateBufferInRange(patchedFunction.Length); var patchedFunctionAddress = buffer.Add(patchedFunction); /* Create Hook instance. */ hook.OriginalFunctionAddress = patchedFunctionAddress; hook.OriginalFunction = Wrapper.Create <TFunction>((long)patchedFunctionAddress); hook.ReverseWrapper = reverseWrapper; hook._otherHookPatches = functionPatch.Patches; hook._hookPatch = new Patch((IntPtr)functionAddress, jumpOpcodes.ToArray()); }
/// <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 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)); /* Commit the original modified function to memory. */ byte[] patchedFunction = functionPatch.NewFunction.ToArray(); var buffer = Utilities.FindOrCreateBufferInRange(patchedFunction.Length, 1, long.MaxValue); var patchedFunctionAddress = buffer.Add(patchedFunction); /* Create Hook instance. */ hook.OriginalFunctionAddress = patchedFunctionAddress; hook.OriginalFunction = Wrapper.Create <TFunction>((long)patchedFunctionAddress); hook.ReverseWrapper = reverseWrapper; hook._otherHookPatches = functionPatch.Patches; hook._hookPatch = new Patch((IntPtr)functionAddress, jumpOpcodes.ToArray()); Mutex.MakeHookMutex.ReleaseMutex(); }
/// <summary> /// Attempts to read out a number of bytes from unmanaged memory. /// </summary> /// <param name="address">Address to read from.</param> /// <param name="size">The size of memory.</param> private byte[] TryReadFromMemory(IntPtr address, int size) { byte[] memory; try { CurrentProcess.ReadRaw(address, out memory, size); } catch (Exception) { /* Ignore exception, and only take 2nd one. */ CurrentProcess.SafeReadRaw(address, out memory, size); } return(memory); }
/// <summary> /// Performs a one time activation of the hook, making the necessary memory writes to permanently commit the hook. /// </summary> /// <remarks> /// This function should be called after instantiation as soon as possible, /// preferably in the same line as instantiation. /// /// This class exists such that we don't run into concurrency issues on /// attaching to other processes whereby the following happens: /// /// A. Original process calls a function that was just hooked. /// B. Create function has not yet returned, and OriginalFunction is unassigned. /// C. Hook tried to call OriginalFunction. NullReferenceException. /// </remarks> public IHook <TFunction> Activate() { /* Create enable/disable patch. */ var disableOpCodes = Utilities.AssembleAbsoluteJump(OriginalFunctionAddress, false); CurrentProcess.SafeReadRaw(ReverseWrapper.WrapperPointer, out var originalOpcodes, disableOpCodes.Length); _disableHookPatch = new Patch(ReverseWrapper.WrapperPointer, disableOpCodes); _enableHookPatch = new Patch(ReverseWrapper.WrapperPointer, originalOpcodes); /* Activate the hook. */ _hookPatch.Apply(); foreach (var hookPatch in _otherHookPatches) { hookPatch.Apply(); } /* Set flags. */ IsHookEnabled = true; IsHookActivated = true; return(this); }
/// <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); }); }
/// <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); }); }