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)); }
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()); }
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()); }
/// <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); })); }
public IntPtr CreateNativeWrapperX86 <TFunction>(IntPtr functionAddress, IFunctionAttribute fromConvention, IFunctionAttribute toConvention) => X86.Wrapper.Create <TFunction>(functionAddress, fromConvention, toConvention);
public IntPtr CreateNativeWrapperX86 <TFunction>(IntPtr functionAddress, IFunctionAttribute fromConvention) => X86.Wrapper.Create <TFunction>(functionAddress, fromConvention, FunctionAttribute.GetAttribute <TFunction>().GetEquivalent(Misc.TryGetAttributeOrDefault <TFunction, UnmanagedFunctionPointerAttribute>()));
/// <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); })); }
/// <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)); }
/// <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)); }
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();
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();