Ejemplo n.º 1
0
        /// <summary>
        /// Checks whether an instance of <see cref="X64ReloadedFunctionAttribute"/> has the same logical meaning
        /// as the current instance of <see cref="X64ReloadedFunctionAttribute"/>.
        /// </summary>
        /// <param name="obj">The <see cref="X64ReloadedFunctionAttribute"/> to compare to the current attribute.</param>
        /// <returns>True if both of the ReloadedFunctions are logically equivalent.</returns>
        public override bool Equals(Object obj)
        {
            // Check for type.
            X64ReloadedFunctionAttribute functionAttribute = obj as X64ReloadedFunctionAttribute;

            // Return false if null
            if (functionAttribute == null)
            {
                return(false);
            }

            // Check by value.
            return(functionAttribute.ShadowSpace == ShadowSpace &&
                   functionAttribute.ReturnRegister == ReturnRegister &&
                   functionAttribute.SourceRegisters.SequenceEqual(SourceRegisters));
        }
        /// <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>
        internal static IntPtr CreateX64WrapperFunctionInternal <TFunction>(IntPtr functionAddress, X64ReloadedFunctionAttribute reloadedFunction)
        {
            // Retrieve number of parameters.
            int numberOfParameters    = FunctionCommon.GetNumberofParametersWithoutFloats(typeof(TFunction));
            int nonRegisterParameters = numberOfParameters - reloadedFunction.SourceRegisters.Length;

            // List of ASM Instructions to be Compiled
            List <string> assemblyCode = new List <string> {
                "use64"
            };

            // If the attribute is Microsoft Call Convention, take fast path!
            if (reloadedFunction.Equals(new X64ReloadedFunctionAttribute(X64CallingConventions.Microsoft)))
            {
                // Backup old call frame
                assemblyCode.AddRange(HookCommon.X64AssembleAbsoluteJumpMnemonics(functionAddress, reloadedFunction));
            }
            else
            {
                // Backup Stack Frame
                assemblyCode.Add("push rbp");       // Backup old call frame
                assemblyCode.Add("mov rbp, rsp");   // Setup new call frame

                // We assume that we are stack aligned in usercall/custom calling conventions, if we are not, game over for now.
                // Our stack frame alignment is off by 8 here, we must also consider non-register parameters.
                // Note to self: In the case you forget, dummy. Your stack needs to be 16 byte aligned.

                // Calculate the bytes our wrapper parameters take on the stack in total.
                // The stack frame backup, push rbp and CALL negate themselves so it's down to this.
                // Then our misalignment to the stack.
                int stackBytesTotal   = (nonRegisterParameters * 8);
                int stackMisalignment = (stackBytesTotal % 16);

                // Prealign stack
                assemblyCode.Add($"sub rbp, {stackMisalignment}");   // Setup new call frame

                // Setup the registers for our C# method.
                assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, reloadedFunction, stackMisalignment));

                // And now we add the shadow space for C# method's Microsoft Call Convention.
                assemblyCode.Add("sub rsp, 32");   // Setup new call frame

                // Assemble the Call to C# Function Pointer.
                assemblyCode.AddRange(HookCommon.X64AssembleAbsoluteCallMnemonics(functionAddress, reloadedFunction));

                // MOV our own return register, EAX into the register expected by the calling convention
                assemblyCode.Add($"mov {reloadedFunction.ReturnRegister}, rax");

                // Restore stack pointer (this is always the same, our function is Microsoft Call Convention Compliant)
                assemblyCode.Add("add rsp, " + ((nonRegisterParameters * 8) + 32));

                // Restore stack alignment
                assemblyCode.Add($"add rbp, {stackMisalignment}");

                // Restore Stack Frame and Return
                assemblyCode.Add("pop rbp");
                assemblyCode.Add("ret");
            }

            // Assemble and return pointer to code
            byte[] assembledMnemonics = Assembler.Assembler.Assemble(assemblyCode.ToArray());
            return(MemoryBufferManager.Add(assembledMnemonics));
        }
        /// <summary>
        /// Generates the assembly code to assemble for the passing of the
        /// function parameters for to our own C# CDECL compliant function.
        /// </summary>
        /// <param name="parameterCount">The total amount of parameters that the target function accepts.</param>
        /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param>
        /// <param name="stackMisalignment">The amount of extra bytes that the stack pointer is decremented by/grown in order to 16-byte align the stack.</param>
        /// <returns>A string array of compatible x64 mnemonics to be assembled.</returns>
        internal static string[] AssembleFunctionParameters(int parameterCount, X64ReloadedFunctionAttribute reloadedFunction, int stackMisalignment)
        {
            // Store our JIT Assembly Code
            List <string> assemblyCode = new List <string>();

            // At the current moment in time, the base address of old call stack (EBP) is at [ebp + 0]
            // the return address of the calling function is at [ebp + 8], last parameter is therefore at [ebp + 16].
            // Reminder: The stack grows by DECREMENTING THE STACK POINTER.

            // There exists 32 bits of Shadow Space depending on the function, we must move it to a convention supporting shadow space (Microsoft).

            // The initial offset from EBP (Stack Base Pointer) for the rightmost parameter (right to left passing):
            int nonRegisterParameters  = parameterCount - reloadedFunction.SourceRegisters.Length;
            int currentBaseStackOffset = 0;

            // Set the base stack offset depending on whether the method has "Shadow Space" or not.
            if (reloadedFunction.ShadowSpace)
            {
                // Including parameter count will compensate for the "Shadow Space"
                currentBaseStackOffset = ((parameterCount + 1) * 8) + stackMisalignment;
            }
            else
            {
                // If there is no "Shadow Space", it's directly on the stack below.
                currentBaseStackOffset = ((nonRegisterParameters + 1) * 8) + stackMisalignment;
            }

            /*
             *   Re-push register parameters to be used for calling the method.
             */

            // Re-push our non-register parameters passed onto the method onto the stack.
            for (int x = 0; x < nonRegisterParameters; x++)
            {
                // Push parameter onto stack.
                assemblyCode.Add($"push qword [rbp + {currentBaseStackOffset}]");

                // Go to next parameter.
                currentBaseStackOffset -= 8;
            }

            // Push our register parameters onto the stack.
            // We reverse the order of the register parameters such that they are ultimately pushed in right to left order, matching
            // our individual parameter order as if they were pushed onto the stack in left to right order.
            X64ReloadedFunctionAttribute.Register[] newRegisters = reloadedFunction.SourceRegisters.Reverse().ToArray();
            foreach (X64ReloadedFunctionAttribute.Register registerParameter in newRegisters)
            {
                assemblyCode.Add($"push {registerParameter.ToString()}");
            }

            // Now we pop our individual register parameters from the stack back into registers expected by the Microsoft
            // X64 Calling Convention.

            // This looks stupid, I know.
            X64ReloadedFunctionAttribute microsoftX64CallingConvention = new X64ReloadedFunctionAttribute(X64CallingConventions.Microsoft);

            // Loop for all registers in Microsoft Convention
            for (int x = 0; x < microsoftX64CallingConvention.SourceRegisters.Length; x++)
            {
                // Reduce this until the end of our parameter list, in the case we have e.g. only 3 parameters
                // and the convention can take 4.
                if (x < parameterCount)
                {
                    assemblyCode.Add($"pop {microsoftX64CallingConvention.SourceRegisters[x].ToString()}");
                }
                else
                {
                    break;
                }
            }

            return(assemblyCode.ToArray());
        }
        /// <summary>
        /// Creates the wrapper function that wraps a native function with our own defined or custom calling convention,
        /// into a regular X64 Microsoft Calling Convention function that is natively supported by the C# programming language.
        ///
        /// This allows us to call non-standard "usercall" game functions, such as for example functions that take values
        /// in registers as parameters instead of using the stack, functions which take paremeters in a mixture of both stack
        /// and registers as well as functions which varying return parameters, either caller or callee cleaned up.
        /// </summary>
        /// <param name="functionAddress">The address of the function to create a wrapper for.</param>
        /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param>
        /// <returns>Pointer to the new X64 Microsoft Calling Convention function address to call from C# code to invoke our game function.</returns>
        private static IntPtr CreateWrapperFunctionInternal <TFunction>(IntPtr functionAddress, X64ReloadedFunctionAttribute reloadedFunction)
        {
            // Retrieve number of parameters.
            int numberOfParameters    = FunctionCommon.GetNumberofParametersWithoutFloats(typeof(TFunction));
            int nonRegisterParameters = numberOfParameters - reloadedFunction.SourceRegisters.Length;

            // List of ASM Instructions to be Compiled
            List <string> assemblyCode = new List <string> {
                "use64"
            };

            // Backup Stack Frame
            assemblyCode.Add("push rbp");       // Backup old call frame
            assemblyCode.Add("mov rbp, rsp");   // Setup new call frame

            // Setup Function Parameters
            if (numberOfParameters > 0)
            {
                assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, reloadedFunction.SourceRegisters));
            }

            // Make Shadow Space if necessary.
            if (reloadedFunction.ShadowSpace)
            {
                assemblyCode.Add("sub rsp, 32");   // Setup new call frame
            }
            // Assemble the call to the game function in question.
            assemblyCode.AddRange(HookCommon.X64AssembleAbsoluteCallMnemonics(functionAddress, reloadedFunction));

            // Move return register back if necessary.
            if (reloadedFunction.ReturnRegister != X64ReloadedFunctionAttribute.Register.rax)
            {
                assemblyCode.Add("mov rax, " + reloadedFunction.ReturnRegister);
            }

            // Restore the stack pointer from the Shadow Space
            // 8 = IntPtr.Size
            // 32 = Shadow Space
            if (reloadedFunction.ShadowSpace)
            {
                assemblyCode.Add("add rsp, " + ((nonRegisterParameters * 8) + 32));
            }
            else
            {
                assemblyCode.Add("add rsp, " + (nonRegisterParameters * 8));
            }

            // Restore Stack Frame and Return
            assemblyCode.Add("pop rbp");
            assemblyCode.Add("ret");

            // Write function to buffer and return pointer.
            byte[] assembledMnemonics = Assembler.Assembler.Assemble(assemblyCode.ToArray());
            return(MemoryBufferManager.Add(assembledMnemonics));
        }