예제 #1
0
        /// <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);
        }
예제 #2
0
        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());
        }
예제 #3
0
        /// <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();
        }
예제 #4
0
        /// <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());
        }
예제 #5
0
        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();
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        /// <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);
        }
예제 #8
0
        /// <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);
            });
        }
예제 #9
0
        /// <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);
            });
        }