/// <summary> /// Creates the wrapper function for redirecting program flow to our C# function. /// </summary> /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param> /// <param name="functionAddress">The address of the function to create a wrapper for.</param> /// <returns></returns> private static IntPtr CreateWrapperFunction <TFunction>(IntPtr functionAddress, ReloadedFunctionAttribute reloadedFunction) { // Retrieve number of parameters. int numberOfParameters = FunctionCommon.GetNumberofParameters(typeof(TFunction)); int nonRegisterParameters = numberOfParameters - reloadedFunction.SourceRegisters.Length; // List of ASM Instructions to be Compiled List <string> assemblyCode = new List <string> { "use32" }; // Backup Stack Frame assemblyCode.Add("push ebp"); // Backup old call frame assemblyCode.Add("mov ebp, esp"); // Setup new call frame // Push registers for our C# method as necessary. assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, reloadedFunction.SourceRegisters)); // Call C# Function Pointer (cSharpFunctionPointer is address at which our C# function address is written) IntPtr cSharpFunctionPointer = MemoryBuffer.Add(functionAddress); assemblyCode.Add("call dword [0x" + cSharpFunctionPointer.ToString("X") + "]"); // Restore stack pointer + stack frame assemblyCode.Add($"add esp, {numberOfParameters * 4}"); // MOV our own return register, EAX into the register expected by the calling convention assemblyCode.Add($"mov {reloadedFunction.ReturnRegister}, eax"); // Restore Stack Frame and Return assemblyCode.Add("pop ebp"); // Caller/Callee Cleanup if (reloadedFunction.Cleanup == ReloadedFunctionAttribute.StackCleanup.Callee) { assemblyCode.Add($"ret {nonRegisterParameters * 4}"); } else { assemblyCode.Add("ret"); } // Assemble and return pointer to code byte[] assembledMnemonics = Assembler.Assembler.Assemble(assemblyCode.ToArray()); return(MemoryBuffer.Add(assembledMnemonics)); }
/// <summary> /// Creates a function hook for a function at a user specified address. /// This class provides Windows API (and general process) hooking functionality for standard cdecl, stdcall, as well as custom /// ReloadedFunction Attribute declared functions. For more details, see the description of the <see cref="FunctionHook{TDelegate}"/> class. /// </summary> /// <param name="gameFunctionAddress">The address of the game function to create the wrapper for.</param> /// <param name="functionDelegate"> /// A delegate instance of the supplied generic delegate type which calls/invokes /// the C# method that will be used to handle the hook. /// </param> /// <param name="hookLength"> /// Optional explicit length of the hook to perform used in the impossibly rare /// cases whereby auto-length checking overflows the default into a jmp/call. /// </param> /// <returns> /// An instance of <see cref="FunctionHook{TFunction}"/>, which may be used /// to call the original function. /// </returns> /// <remarks> /// Due to safety and depth concerns regarding the use of multiple hooks on a singular address, /// the class does not provide an implementation allowing you to unhook. Please instead implement /// a flag and just call + return value from the original method without changing any of the input /// parameters if you wish to achieve the same effect. /// </remarks> public static FunctionHook <TFunction> CreateFunctionHook(long gameFunctionAddress, TFunction functionDelegate, int hookLength) { /* * Retrieve C# function details. */ // Retrieve the function address from the supplied user delegate. // Our ReloadedFunction attribute. IntPtr cSharpFunctionAddress = Marshal.GetFunctionPointerForDelegate(functionDelegate); ReloadedFunctionAttribute reloadedFunction = GetReloadedFunctionAttribute <TFunction>(); /* * [Hook Part I] Create Custom => CDECL Wrapper and Assemble */ // Assemble the wrapper function. // Assemble a jump to our wrapper function. IntPtr wrapperFunctionAddress = CreateWrapperFunction <TFunction>(cSharpFunctionAddress, reloadedFunction); List <byte> jumpBytes = HookCommon.AssembleAbsoluteJump(wrapperFunctionAddress).ToList(); /* * [Hook Part II] Calculate Hook Length (Unless Explicit) */ // Retrieve hook length explicitly if (hookLength == -1) { hookLength = HookCommon.GetHookLength((IntPtr)gameFunctionAddress, jumpBytes.Count); } // Assemble JMP + NOPs for stolen/stray bytes. if (hookLength > jumpBytes.Count) { // Append NOPs after JMP to fill remaining bytes. int nopBytes = hookLength - jumpBytes.Count; for (int x = 0; x < nopBytes; x++) { jumpBytes.Add(0x90); } } /* * [Call Original Function Part I] Read stolen bytes and assemble function wrapper to call original function. */ // Backup game's hook bytes. // Check if stolen bytes contains a jmp as first (other hooks) // Calculate jump back address for original function. // Append absolute JMP instruction to return to original function for calling the original function in hook. List <byte> stolenBytes = Bindings.TargetProcess.ReadMemoryExternal((IntPtr)gameFunctionAddress, hookLength).ToList(); stolenBytes = HookCommon.ProcessStolenBytes(stolenBytes, (IntPtr)gameFunctionAddress); IntPtr jumpBackAddress = (IntPtr)(gameFunctionAddress + hookLength); stolenBytes.AddRange(HookCommon.AssembleAbsoluteJump(jumpBackAddress)); /* * [Call Original Function part II] Instantiate and return functionHook with the original game function address. */ // Assign original function. FunctionHook <TFunction> functionHook = new FunctionHook <TFunction>(); // Write original bytes and jump to memory, and return address. IntPtr gameFunctionWrapperAddress = MemoryBuffer.Add(stolenBytes.ToArray()); // Create wrapper for calling the original function. functionHook.OriginalFunction = FunctionWrapper.CreateWrapperFunction <TFunction>((long)gameFunctionWrapperAddress); // Store a copy of the original function. functionHook._originalDelegate = functionDelegate; /* * [Apply Hook] Write hook bytes. */ Bindings.TargetProcess.WriteMemoryExternal((IntPtr)gameFunctionAddress, jumpBytes.ToArray()); return(functionHook); }