예제 #1
0
        /// <summary>
        /// Assembles an absolute jump to a user specified address and returns
        /// the resultant bytes of the assembly process.
        /// </summary>
        /// <param name="functionAddress">The address to assemble the absolute jump to.</param>
        /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param>
        /// <param name="shortJump">Set to true to shorten the length of the JMP at the expense of a free register's content in ASLR Mode.</param>
        /// <param name="targetAbsoluteJumpAddress">[Optional] Target address within which 2GB absolute jump to be assembled.</param>
        /// <returns>A set of X64 assembler bytes to absolute jump to a specified address.</returns>
        public static byte[] X64AssembleAbsoluteJump(IntPtr functionAddress, X64ReloadedFunctionAttribute reloadedFunction, bool shortJump = false, long targetAbsoluteJumpAddress = 0)
        {
            // Get mnemonics to assemble.
            List <string> assemblyCode = new List <string> {
                "use64"
            };

            assemblyCode.AddRange(X64AssembleAbsoluteJumpMnemonics(functionAddress, reloadedFunction, shortJump, targetAbsoluteJumpAddress));

            // Assemble the individual bytes.
            return(Assemble(assemblyCode.ToArray()));
        }
예제 #2
0
        /// <summary>
        /// Returns a register to be used for calling of the target function.
        /// </summary>
        /// <param name="reloadedFunction">
        ///     Structure containing the details of the actual function in question.
        ///     The source registers (parameters) are considered to be a blacklist of registers that cannot be used.
        ///     The return register is considered by default as the starting candidate.
        /// </param>
        public static X64ReloadedFunctionAttribute.Register GetCallRegister(X64ReloadedFunctionAttribute reloadedFunction)
        {
            // X86-64 doesn't support 64bit immediates in most instructions, meaning that we must, unfortunately
            // make use of a register to call a game function in question.
            // Here we find an unused register and delegate it to calling the function.

            // Default value | nonvolatile register
            X64ReloadedFunctionAttribute.Register callRegister = reloadedFunction.ReturnRegister;

            // Use return register if it's not a parameter (safe).
            if (!reloadedFunction.SourceRegisters.Contains(callRegister))
            {
                return(callRegister);
            }

            // Use R11 (volatile) if it's not a parameter.
            if (!reloadedFunction.SourceRegisters.Contains(X64ReloadedFunctionAttribute.Register.r11))
            {
                return(X64ReloadedFunctionAttribute.Register.r11);
            }

            // Use R10 (volatile) if it's not a parameter.
            if (!reloadedFunction.SourceRegisters.Contains(X64ReloadedFunctionAttribute.Register.r10))
            {
                return(X64ReloadedFunctionAttribute.Register.r10);
            }

            // Otherwise brute force!
            foreach (X64ReloadedFunctionAttribute.Register foo in Enum.GetValues(
                         typeof(X64ReloadedFunctionAttribute.Register)))
            {
                // Don't use a parameter register.
                if (reloadedFunction.SourceRegisters.Contains(foo))
                {
                    continue;
                }

                return(foo);
            }

            return(callRegister);
        }
예제 #3
0
        /// <summary>
        /// Disassembles the provided array of stolen bytes and replaces instances of relative jumps to
        /// absolute jumps towards a specific location.
        /// </summary>
        /// <param name="stolenBytes">Bytes which are going to be overwritten by our own jump bytes with jmp calls to replace.</param>
        /// <param name="baseAddress">The original address of the start of the individual bytes.</param>
        /// <param name="architectureMode">Defines the architecture as X86 or X64 to use for disassembly.</param>
        /// <param name="reloadedFunctionX64">[Only for X64] Contains the register blacklist (source registers) and a default register for assembling absolute jumps in ASLR mode.</param>
        /// <returns></returns>
        public static (List <byte>, List <(IntPtr addressToPatch, byte[] newBytes)>) ProcessStolenBytes(List <byte> stolenBytes, IntPtr baseAddress, ArchitectureMode architectureMode, X64ReloadedFunctionAttribute reloadedFunctionX64 = null)
        {
            // List of addressses to patch.
            List <(IntPtr addressToPatch, byte[] newBytes)> addressesToPatch = new List <(IntPtr addressToPatch, byte[] newBytes)>();

            // Address of end of Reloaded's hook
            long reloadedHookEndAddress = stolenBytes.Count + (long)baseAddress;

            // Define the disassembler and isassemble function header and find shortest amount of bytes.
            Disassembler disassembler = new Disassembler(stolenBytes.ToArray(), architectureMode, (ulong)baseAddress, true);

            Instruction[] instructions = disassembler.Disassemble().ToArray();

            // New bytes to replace the stolen bytes.
            List <byte> newStolenBytes = new List <byte>();

            // Iterate over all instructions.
            foreach (Instruction instruction in instructions)
            {
                // Check if the opcode is a JMP, this is what we want to patch.
                if (instruction.Mnemonic == ud_mnemonic_code.UD_Ijmp && instruction.Length <= 5)
                {
                    // Relative offset from the program counter (i.e. instruction offset + jmp length)
                    int relativeOffset = instruction.Operands[0].LvalSDWord;

                    // Calculate final destination, this is where the existing hook's jmp leads to.
                    long finalJumpDestination = (long)instruction.PC + relativeOffset;

                    // Backup other hooks' stray bytes
                    byte[] otherStrayBytes = Bindings.TargetProcess.ReadMemoryExternal((IntPtr)instruction.PC, (int)(reloadedHookEndAddress - (long)instruction.PC));

                    // Assemble mini-wrapper which preserves stray bytes and jumps back to our final destination.
                    IntPtr strayByteWrapper;

                    if (architectureMode == ArchitectureMode.x86_32)
                    {
                        strayByteWrapper = X86AssembleMiniWrapper(otherStrayBytes, reloadedHookEndAddress);
                    }
                    else
                    {
                        strayByteWrapper = X64AssembleMiniWrapper(otherStrayBytes, reloadedHookEndAddress, reloadedFunctionX64, finalJumpDestination);
                    }

                    // Patch any relative calls at the final jump.
                    addressesToPatch.AddRange(GetPatchAddresses(finalJumpDestination, (long)instruction.PC, (long)strayByteWrapper, architectureMode));

                    // Assemble absolute JMP
                    if (architectureMode == ArchitectureMode.x86_32)
                    {
                        newStolenBytes.AddRange(X86AssembleAbsoluteJump((IntPtr)finalJumpDestination));
                    }
                    else
                    {
                        newStolenBytes.AddRange(X64AssembleAbsoluteJump((IntPtr)finalJumpDestination, reloadedFunctionX64));
                    }
                }
                // X64: Convert Relative Address Pointers to Absolute Jumps.
                else if (instruction.Mnemonic == ud_mnemonic_code.UD_Ijmp &&
                         architectureMode == ArchitectureMode.x86_64 &&
                         instruction.Operands.Length >= 1 &&
                         instruction.Operands[0].Base == ud_type.UD_R_RIP)
                {
                    // Resolve the target address of the rip relative addressing absolute jump.
                    IntPtr targetAddress = Bindings.TargetProcess.ReadMemoryExternal <IntPtr>((IntPtr)(instruction.PC + (ulong)instruction.Operands[0].LvalSDWord));

                    // Replace with our own copy of an assembled absolute jump.
                    newStolenBytes.AddRange(X64AssembleAbsoluteJump((IntPtr)targetAddress, reloadedFunctionX64));
                }

                // If not jump, add next instruction.
                else
                {
                    newStolenBytes.AddRange(instruction.Bytes);
                }
            }

            return(newStolenBytes, addressesToPatch);
        }
예제 #4
0
        /// <summary>
        /// Assembles an absolute jump to a user specified address and returns
        /// the resultant bytes of the assembly process.
        /// </summary>
        /// <param name="functionAddress">The address to assemble the absolute call to.</param>
        /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param>
        /// <returns>A set of X64 assembler bytes to absolute jump to a specified address.</returns>
        public static List <string> X64AssembleAbsoluteCallMnemonics(IntPtr functionAddress, X64ReloadedFunctionAttribute reloadedFunction)
        {
            // List of ASM Instructions to be Compiled
            List <string> assemblyCode = new List <string>();

            // Assemble the call to the game function in question.
            // With the rewrite of MemoryBuffer, this piece of code has been greatly simplified.
            IntPtr gameFunctionPointer;

            if ((gameFunctionPointer = MemoryBufferManager.Add(functionAddress, IntPtr.Zero)) != IntPtr.Zero)
            {
                // Jump to Game Function Pointer (gameFunctionPointer is address at which our function address is written)
                assemblyCode.Add("call qword [qword 0x" + gameFunctionPointer.ToString("X") + "]");
            }
            // Cannot get buffer in 2GB range.
            else
            {
                // Get register to delegate function calling to.
                X64ReloadedFunctionAttribute.Register jmpRegister = FunctionCommon.GetCallRegister(reloadedFunction);

                // Call Game Function Pointer (gameFunctionPointer is address at which our function address is written)
                assemblyCode.Add($"mov {jmpRegister}, 0x{functionAddress.ToString("X")}");
                assemblyCode.Add($"call {jmpRegister}");
            }

            // Assemble the individual bytes.
            return(assemblyCode);
        }
예제 #5
0
        /// <summary>
        /// Assembles a wrapper for other function hooks' stray bytes that will be overwritten by
        /// Reloaded's own hooking mechanism. Gives back the old functions their stray bytes and jumps back
        /// immediately to the end of Reloaded's hook.
        /// </summary>
        /// <param name="strayBytes">Bytes from another hook that will be replaced by Reloaded's hooking mechanism.</param>
        /// <param name="reloadedHookEndAddress">The target address for the mini wrapper to jump back to.</param>
        /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param>
        /// <param name="wrapperAddress">[Optional] Target address within of which the wrapper should be placed in 2GB range.</param>
        /// <returns>The address of a new "mini-wrapper" class for </returns>
        public static IntPtr X64AssembleMiniWrapper(byte[] strayBytes, long reloadedHookEndAddress, X64ReloadedFunctionAttribute reloadedFunction, long wrapperAddress = 0)
        {
            // Get our stray bytes.
            List <byte> newBytes = strayBytes.ToList();

            // Append our absolute jump to the mix.
            newBytes.AddRange(X64AssembleAbsoluteJump((IntPtr)reloadedHookEndAddress, reloadedFunction));

            // Write to memory buffer and return
            return(MemoryBufferManager.Add(newBytes.ToArray(), (IntPtr)wrapperAddress));
        }