private static IntPtr Create(IntPtr functionAddress, IFunctionAttribute fromFunction)
        {
            // CDECL is hot path, as our TFunction will already be CDECL, we marshal if it's anything else.
            if (fromFunction.Equals(new FunctionAttribute(CallingConventions.Cdecl)))
            {
                return(functionAddress);
            }

            // Retrieve number of parameters and setup list of ASM instructions to be compiled.
            int           numberOfParameters    = Utilities.GetNumberofParameters(typeof(TFunction));
            int           nonRegisterParameters = numberOfParameters - fromFunction.SourceRegisters.Length;
            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, fromFunction.SourceRegisters));

            // Call target function
            var    pointerBuffer     = Utilities.FindOrCreateBufferInRange(IntPtr.Size);
            IntPtr targetFunctionPtr = pointerBuffer.Add(ref functionAddress);

            assemblyCode.Add("call dword [0x" + targetFunctionPtr.ToString("X") + "]");

            // MOV EAX return register into custom calling convention's return register.
            if (fromFunction.ReturnRegister != FunctionAttribute.Register.eax)
            {
                assemblyCode.Add($"mov {fromFunction.ReturnRegister}, eax");
            }

            // Restore stack pointer.
            if (numberOfParameters > 0)
            {
                assemblyCode.Add($"add esp, {numberOfParameters * 4}");
            }

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

            if (fromFunction.Cleanup == FunctionAttribute.StackCleanup.Callee)
            {
                assemblyCode.Add($"ret {nonRegisterParameters * 4}");
            }
            else
            {
                assemblyCode.Add("ret");
            }

            byte[] assembledMnemonics = Utilities.Assembler.Assemble(assemblyCode.ToArray());
            var    wrapperBuffer      = Utilities.FindOrCreateBufferInRange(assembledMnemonics.Length);

            return(wrapperBuffer.Add(assembledMnemonics));
        }
Beispiel #2
0
        private static string[] AssembleFunctionParameters(int parameterCount, ref IFunctionAttribute fromConvention, ref IFunctionAttribute toConvention)
        {
            List <string> assemblyCode = new List <string>();

            /*
             * At the current moment in time, our register contents and parameters are as follows: RCX, RDX, R8, R9.
             * The base address of old call stack (RBP) is at [rbp + 0]
             * The return address of the calling function is at [rbp + 8]
             * Last stack parameter is at [rbp + 16] (+ "Shadow Space").
             *
             * Note: Reason return address is not at [rbp + 0] is because we pushed rbp and mov'd rsp to it.
             * Reminder: The stack grows by DECREMENTING THE STACK POINTER.
             *
             * Example (Parameters passed right to left)
             * Stack 1st Param: [rbp + 16]
             * Stack 2nd Param: [rbp + 24]
             *
             * Parameter Count == 1:
             * baseStackOffset = 8
             * lastParameter = baseStackOffset + (toStackParams * 8) == 16
             */

            int toStackParams   = parameterCount - toConvention.SourceRegisters.Length;
            int baseStackOffset = (toConvention.ShadowSpace ? 32 : 0) + 8;   // + 8

            baseStackOffset += (toStackParams) * 8;

            // Re-push all toConvention stack params, then register parameters. (Right to Left)
            for (int x = 0; x < toStackParams; x++)
            {
                assemblyCode.Add($"push qword [rbp + {baseStackOffset}]");
                baseStackOffset -= 8;
            }

            for (int x = Math.Min(toConvention.SourceRegisters.Length, parameterCount) - 1; x >= 0; x--)
            {
                assemblyCode.Add($"push {toConvention.SourceRegisters[x]}");
            }

            // Pop all necessary registers to target. (Left to Right)
            for (int x = 0; x < fromConvention.SourceRegisters.Length && x < parameterCount; x++)
            {
                assemblyCode.Add($"pop {fromConvention.SourceRegisters[x]}");
            }

            return(assemblyCode.ToArray());
        }
Beispiel #3
0
        private static string[] AssembleFunctionParameters(int parameterCount, ref IFunctionAttribute fromConvention, ref IFunctionAttribute toConvention)
        {
            List <string> assemblyCode = new List <string>();

            // At the current moment in time, our register contents and parameters are as follows: RCX, RDX, R8, R9.
            // 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].

            // Note: Reason return address is not at [ebp + 0] is because we pushed rbp and mov'd rsp to it.
            // Reminder: The stack grows by DECREMENTING THE STACK POINTER.

            int toStackParams   = parameterCount - toConvention.SourceRegisters.Length;
            int baseStackOffset = toConvention.ShadowSpace ? 32 : 0;

            baseStackOffset = (toStackParams + 1) * 8;

            // Re-push all source call convention stack params, then register parameters. (Right to Left)
            for (int x = 0; x < toStackParams; x++)
            {
                assemblyCode.Add($"push qword [rbp + {baseStackOffset}]");
                baseStackOffset -= 8;
            }

            for (int x = Math.Min(toConvention.SourceRegisters.Length, parameterCount) - 1; x >= 0; x--)
            {
                assemblyCode.Add($"push {toConvention.SourceRegisters[x].ToString()}");
            }

            // Pop all necessary registers to target. (Left to Right)
            for (int x = 0; x < fromConvention.SourceRegisters.Length && x < parameterCount; x++)
            {
                assemblyCode.Add($"pop {fromConvention.SourceRegisters[x].ToString()}");
            }

            return(assemblyCode.ToArray());
        }
Beispiel #4
0
        /// <summary>
        /// Creates a wrapper function which allows you to call a function with a custom calling convention using the calling convention of
        /// <typeparamref name="TFunction"/>.
        /// </summary>
        /// <param name="functionAddress">The address of the function using <paramref name="fromConvention"/>.</param>
        /// <param name="fromConvention">The calling convention to convert to <paramref name="toConvention"/>. This is the convention of the function (<paramref name="functionAddress"/>) called.</param>
        /// <param name="toConvention">The target convention to which convert to <paramref name="fromConvention"/>. This is the convention of the function returned.</param>
        /// <returns>Address of the wrapper in memory.</returns>
        public static IntPtr Create <TFunction>(IntPtr functionAddress, IFunctionAttribute fromConvention, IFunctionAttribute toConvention)
        {
            // 256 Bytes should allow for around 60-70 parameters in worst case scenario.
            // If you need more than that, then... I don't know what you're doing with your life.
            // Please do a pull request though and we can stick some code to predict the size.
            const int MaxFunctionSize = 256;
            var       minMax          = Utilities.GetRelativeJumpMinMax((long)functionAddress, Int32.MaxValue - MaxFunctionSize);
            var       buffer          = Utilities.FindOrCreateBufferInRange(MaxFunctionSize, minMax.min, minMax.max);

            return(buffer.ExecuteWithLock(() =>
            {
                // Align the code.
                buffer.SetAlignment(16);
                var codeAddress = buffer.Properties.WritePointer;

                // Write pointer.
                // toFunction (target) is CDECL
                int numberOfParameters = Utilities.GetNumberofParameters <TFunction>();
                List <string> assemblyCode = new List <string>
                {
                    "use32",
                    $"org {codeAddress}" // Tells FASM where code should be located.
                };

                // Calculate some stack stuff.
                int fromStackParamBytesTotal = (fromConvention.Cleanup == StackCleanup.Caller) ? (numberOfParameters - fromConvention.SourceRegisters.Length) * 4 : 0;
                int toStackParamBytesTotal = (toConvention.Cleanup == StackCleanup.Callee) ? (numberOfParameters - toConvention.SourceRegisters.Length) * 4 : 0;
                int stackCleanupBytesTotal = fromStackParamBytesTotal + fromConvention.ReservedStackSpace;

                // Callee Saved Registers
                assemblyCode.Add("push ebp");       // Backup old call frame
                assemblyCode.Add("mov ebp, esp");   // Setup new call frame
                foreach (var register in toConvention.CalleeSavedRegisters)
                {
                    assemblyCode.Add($"push {register}");
                }

                // Reserve Extra Stack Space
                if (fromConvention.ReservedStackSpace > 0)
                {
                    assemblyCode.Add($"sub esp, {fromConvention.ReservedStackSpace}");
                }

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

                // Call target function
                assemblyCode.Add($"call {functionAddress}");

                // Stack cleanup if necessary
                if (stackCleanupBytesTotal > 0)
                {
                    assemblyCode.Add($"add esp, {stackCleanupBytesTotal}");
                }

                // Setup return register
                if (fromConvention.ReturnRegister != toConvention.ReturnRegister)
                {
                    assemblyCode.Add($"mov {toConvention.ReturnRegister}, {fromConvention.ReturnRegister}");
                }

                // Callee Restore Registers
                foreach (var register in toConvention.CalleeSavedRegisters.Reverse())
                {
                    assemblyCode.Add($"pop {register}");
                }

                assemblyCode.Add("pop ebp");
                assemblyCode.Add($"ret {toStackParamBytesTotal}"); // FASM optimizes `ret 0` as `ret`

                // Write function to buffer and return pointer.
                return buffer.Add(Utilities.Assembler.Assemble(assemblyCode.ToArray()), 1);
            }));
        }
Beispiel #5
0
 public IntPtr CreateNativeWrapperX86 <TFunction>(IntPtr functionAddress, IFunctionAttribute fromConvention,
                                                  IFunctionAttribute toConvention) => X86.Wrapper.Create <TFunction>(functionAddress, fromConvention, toConvention);
Beispiel #6
0
 public IntPtr CreateNativeWrapperX86 <TFunction>(IntPtr functionAddress, IFunctionAttribute fromConvention) => X86.Wrapper.Create <TFunction>(functionAddress, fromConvention, FunctionAttribute.GetAttribute <TFunction>().GetEquivalent(Misc.TryGetAttributeOrDefault <TFunction, UnmanagedFunctionPointerAttribute>()));
Beispiel #7
0
        /// <summary>
        /// Creates a wrapper converting a call to a source calling convention to a given target calling convention.
        /// </summary>
        /// <param name="functionAddress">Address of the function in fromConvention to execute.</param>
        /// <param name="fromConvention">The calling convention to convert to toConvention. This is the convention of the function (<paramref name="functionAddress"/>) called.</param>
        /// <param name="toConvention">The target convention to which convert to fromConvention. This is the convention of the function returned.</param>
        /// <returns>Address of the wrapper in memory you can call .</returns>
        public static nuint Create <
#if NET5_0_OR_GREATER
            [DynamicallyAccessedMembers(Trimming.ReloadedAttributeTypes)]
#endif
            TFunction>(nuint functionAddress, IFunctionAttribute fromConvention, IFunctionAttribute toConvention)
        {
            // 384 Bytes should allow for around 100 parameters in worst case scenario.
            // If you need more than that, then... I don't know what you're doing with your life.
            // Please do a pull request though and we can stick some code to predict the size.
            const int MaxFunctionSize    = 384;
            var       minMax             = Utilities.GetRelativeJumpMinMax(functionAddress, Int32.MaxValue - MaxFunctionSize);
            var       buffer             = Utilities.FindOrCreateBufferInRange(MaxFunctionSize, minMax.min, minMax.max);
            int       numberOfParameters = Utilities.GetNumberofParametersWithoutFloats <TFunction>();

            return(buffer.ExecuteWithLock(() =>
            {
                // Align the code.
                buffer.SetAlignment(16);
                var codeAddress = buffer.Properties.WritePointer;

                // Retrieve number of parameters.
                List <string> assemblyCode = new List <string>
                {
                    "use64",
                    $"org {codeAddress}"
                };

                // On enter, stack is misaligned by 8.
                // Callee Backup Registers
                // Backup Stack Frame
                assemblyCode.Add("push rbp");       // Backup old call frame
                assemblyCode.Add("mov rbp, rsp");   // Setup new call frame
                foreach (var register in toConvention.CalleeSavedRegisters)
                {
                    assemblyCode.Add($"push {register}");
                }

                // Even my mind gets a bit confused. So here is a reminder:
                // fromConvention is the convention that gets called.
                // toConvention is the convention we are marshalling from.
                int targetStackParameters = numberOfParameters - fromConvention.SourceRegisters.Length;
                targetStackParameters = targetStackParameters < 0 ? 0 : targetStackParameters;

                int stackParamBytesTotal = ((targetStackParameters) * 8);
                int stackMisalignment = (stackParamBytesTotal + 8) % 16; // Adding +8 here because we assume that we are starting with a misaligned stack.
                int shadowSpace = fromConvention.ShadowSpace ? 32 : 0;

                // Note: Our stack is already aligned at this point because of `push` above.
                // stackBytesTotal % 16 represent the amount of bytes away from alignment after pushing parameters up the stack.
                // Setup stack alignment.
                if (stackMisalignment != 0)
                {
                    assemblyCode.Add($"sub rsp, {stackMisalignment}");
                }

                // Setup parameters for target.
                if (numberOfParameters > 0)
                {
                    assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, ref fromConvention, ref toConvention));
                }

                // Make shadow space if target requires it.
                if (fromConvention.ShadowSpace)
                {
                    assemblyCode.Add($"sub rsp, {shadowSpace}");
                }

                // Call target.
                assemblyCode.Add($"call {functionAddress}");

                // Restore the stack pointer after function call.
                if (stackParamBytesTotal + shadowSpace + stackMisalignment != 0)
                {
                    assemblyCode.Add($"add rsp, {stackParamBytesTotal + shadowSpace + stackMisalignment}");
                }

                // Marshal return register back from target to source.
                if (fromConvention.ReturnRegister != toConvention.ReturnRegister)
                {
                    assemblyCode.Add($"mov {toConvention.ReturnRegister}, {fromConvention.ReturnRegister}");
                }

                // Callee Restore Registers
                foreach (var register in toConvention.CalleeSavedRegisters.Reverse())
                {
                    assemblyCode.Add($"pop {register}");
                }

                assemblyCode.Add("pop rbp");
                assemblyCode.Add("ret");

                // Write function to buffer and return pointer.
                return buffer.Add(Utilities.Assembler.Assemble(assemblyCode.ToArray()), 1);
            }));
        }
Beispiel #8
0
        /// <summary>
        /// Creates the <see cref="Wrapper"/> in memory allowing you to call a function
        /// at functionAddress as if it was a CDECL function.
        /// </summary>
        /// <param name="functionAddress">The address of the function.</param>
        /// <param name="fromFunction">Describes the properties of the function to wrap.</param>
        /// <returns>Address of the wrapper in memory you can call like a CDECL function.</returns>
        public static IntPtr Create <TFunction>(IntPtr functionAddress, IFunctionAttribute fromFunction)
        {
            Mutex.MakeWrapperMutex.WaitOne();

            // toFunction (target) is CDECL
            int           numberOfParameters    = Utilities.GetNumberofParameters(typeof(TFunction));
            int           nonRegisterParameters = numberOfParameters - fromFunction.SourceRegisters.Length;
            List <string> assemblyCode          = new List <string> {
                "use32"
            };

            // Callee Saved Registers
            assemblyCode.Add("push ebp");       // Backup old call frame
            assemblyCode.Add("mov ebp, esp");   // Setup new call frame
            assemblyCode.Add("push ebx");
            assemblyCode.Add("push esi");
            assemblyCode.Add("push edi");

            // Reserve Extra Stack Space
            if (fromFunction.ReservedStackSpace > 0)
            {
                assemblyCode.Add($"sub esp, {fromFunction.ReservedStackSpace}");
            }

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

            // Call target function
            var    pointerBuffer     = Utilities.FindOrCreateBufferInRange(IntPtr.Size);
            IntPtr targetFunctionPtr = pointerBuffer.Add(ref functionAddress);

            assemblyCode.Add("call dword [0x" + targetFunctionPtr.ToString("X") + "]");

            // Stack cleanup if necessary
            if (nonRegisterParameters > 0 && fromFunction.Cleanup == FunctionAttribute.StackCleanup.Caller)
            {
                assemblyCode.Add($"add esp, {nonRegisterParameters * 4}");
            }

            // Setup return register
            if (fromFunction.ReturnRegister != FunctionAttribute.Register.eax)
            {
                assemblyCode.Add("mov eax, " + fromFunction.ReturnRegister);
            }

            // Unreserve Extra Stack Space
            if (fromFunction.ReservedStackSpace > 0)
            {
                assemblyCode.Add($"add esp, {fromFunction.ReservedStackSpace}");
            }

            // Callee Restore Registers
            assemblyCode.Add("pop edi");
            assemblyCode.Add("pop esi");
            assemblyCode.Add("pop ebx");
            assemblyCode.Add("pop ebp");

            assemblyCode.Add("ret");

            // Write function to buffer and return pointer.
            byte[] assembledMnemonics = Utilities.Assembler.Assemble(assemblyCode.ToArray());
            var    wrapperBuffer      = Utilities.FindOrCreateBufferInRange(assembledMnemonics.Length);

            Mutex.MakeWrapperMutex.ReleaseMutex();
            return(wrapperBuffer.Add(assembledMnemonics));
        }
Beispiel #9
0
        /// <summary>
        /// Creates a wrapper converting a call to a source calling convention to a given target calling convention.
        /// </summary>
        /// <param name="functionAddress">Address of the function in fromConvention to execute.</param>
        /// <param name="fromConvention">The calling convention to convert to toConvention. This is the convention of the function called.</param>
        /// <param name="toConvention">The target convention to which convert to fromConvention. This is the convention of the function returned.</param>
        /// <returns>Address of the wrapper in memory you can call .</returns>
        public static IntPtr Create <TFunction>(IntPtr functionAddress, IFunctionAttribute fromConvention, IFunctionAttribute toConvention)
        {
            Mutex.MakeWrapperMutex.WaitOne();

            // Retrieve number of parameters.
            int           numberOfParameters = Utilities.GetNumberofParametersWithoutFloats(typeof(TFunction));
            List <string> assemblyCode       = new List <string> {
                "use64"
            };

            // On enter, stack is misaligned by 8.
            // Callee Backup Registers
            // Backup Stack Frame
            assemblyCode.Add("push rbp");       // Backup old call frame
            assemblyCode.Add("mov rbp, rsp");   // Setup new call frame

            // Stack now realigned, but will misalign here too.
            assemblyCode.Add("push rbx");
            assemblyCode.Add("push rsi");
            assemblyCode.Add("push rdi");
            assemblyCode.Add("push r12");
            assemblyCode.Add("push r13");
            assemblyCode.Add("push r14");
            assemblyCode.Add("push r15");

            // Even my mind gets a bit confused. So here is a reminder:
            // fromConvention is the convention that gets called.
            // toConvention is the convention we are marshalling from.

            int targetStackParameters = numberOfParameters - fromConvention.SourceRegisters.Length;

            targetStackParameters = targetStackParameters < 0 ? 0 : targetStackParameters;

            int stackParamBytesTotal = ((targetStackParameters) * 8);
            int stackMisalignment    = (stackParamBytesTotal + 8) % 16; // Adding +8 here because we assume that we are starting with a misaligned stack.
            int shadowSpace          = fromConvention.ShadowSpace ? 32 : 0;

            // Note: Our stack is already aligned at this point because of `push` above.
            // stackBytesTotal % 16 represent the amount of bytes away from alignment after pushing parameters up the stack.
            // Setup stack alignment.
            if (stackMisalignment != 0)
            {
                assemblyCode.Add($"sub rsp, {stackMisalignment}");
            }

            // Setup parameters for target.
            if (numberOfParameters > 0)
            {
                assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, ref fromConvention, ref toConvention));
            }

            // Make shadow space if target requires it.
            if (fromConvention.ShadowSpace)
            {
                assemblyCode.Add($"sub rsp, {shadowSpace}");
            }

            // Call target.
            var    pointerBuffer     = Utilities.FindOrCreateBufferInRange(IntPtr.Size, 1, UInt32.MaxValue);
            IntPtr targetFunctionPtr = pointerBuffer.Add(ref functionAddress);

            assemblyCode.Add("call qword [qword 0x" + targetFunctionPtr.ToString("X") + "]");

            // Restore the stack pointer after function call.
            if (stackParamBytesTotal + shadowSpace + stackMisalignment != 0)
            {
                assemblyCode.Add($"add rsp, {stackParamBytesTotal + shadowSpace + stackMisalignment}");
            }

            // Marshal return register back from target to source.
            if (fromConvention.ReturnRegister != toConvention.ReturnRegister)
            {
                assemblyCode.Add($"mov {fromConvention.ReturnRegister}, {toConvention.ReturnRegister}");
            }

            // Callee Restore Registers
            assemblyCode.Add("pop r15");
            assemblyCode.Add("pop r14");
            assemblyCode.Add("pop r13");
            assemblyCode.Add("pop r12");
            assemblyCode.Add("pop rdi");
            assemblyCode.Add("pop rsi");
            assemblyCode.Add("pop rbx");
            assemblyCode.Add("pop rbp");

            assemblyCode.Add("ret");

            // Write function to buffer and return pointer.
            byte[] assembledMnemonics = Utilities.Assembler.Assemble(assemblyCode.ToArray());
            var    wrapperBuffer      = Utilities.FindOrCreateBufferInRange(assembledMnemonics.Length, 1, long.MaxValue);

            Mutex.MakeWrapperMutex.ReleaseMutex();
            return(wrapperBuffer.Add(assembledMnemonics));
        }
Beispiel #10
0
        public IntPtr CreateNativeWrapperX86 <
#if NET5_0_OR_GREATER
            [DynamicallyAccessedMembers(Trimming.ReloadedAttributeTypes)]
#endif
            TFunction>(IntPtr functionAddress, IFunctionAttribute fromConvention,
                       IFunctionAttribute toConvention) => X86.Wrapper.Create <TFunction>(functionAddress.ToUnsigned(), fromConvention, toConvention).ToSigned();
Beispiel #11
0
        public IntPtr CreateNativeWrapperX86 <
#if NET5_0_OR_GREATER
            [DynamicallyAccessedMembers(Trimming.ReloadedAttributeTypes)]
#endif
            TFunction>(IntPtr functionAddress, IFunctionAttribute fromConvention) => X86.Wrapper.Create <TFunction>(functionAddress.ToUnsigned(), fromConvention, FunctionAttribute.GetAttribute <TFunction>().GetEquivalent(Misc.TryGetAttributeOrDefault <TFunction, UnmanagedFunctionPointerAttribute>())).ToSigned();