/// <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())); }
/// <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); }
/// <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); }
/// <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); }
/// <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)); }