internal TStructure CallFunction <TStructure>(CallingConvention callingConvention, IntPtr functionAddress, params long[] parameters) where TStructure : unmanaged { var returnBuffer = Memory.Allocate(Unsafe.SizeOf <TStructure>(), ProtectionType.ReadWrite); // Write the shellcode used to perform the function call into a buffer in the remote process var shellcode = Assembler.AssembleCallDescriptor(new CallDescriptor(functionAddress, callingConvention, IsWow64, parameters, returnBuffer)); var shellcodeBuffer = Memory.Allocate(shellcode.Length, ProtectionType.ExecuteReadWrite); Memory.Write(shellcodeBuffer, shellcode); // Create a thread in the remote process to execute the shellcode var ntStatus = Ntdll.RtlCreateUserThread(_processHandle, IntPtr.Zero, false, 0, IntPtr.Zero, IntPtr.Zero, shellcodeBuffer, IntPtr.Zero, out var threadHandle, IntPtr.Zero); if (ntStatus != NtStatus.Success) { throw new Win32Exception($"Failed to call RtlCreateUserThread with error code {Ntdll.RtlNtStatusToDosError(ntStatus)}"); } if (Kernel32.WaitForSingleObject(threadHandle, int.MaxValue) == -1) { throw new Win32Exception($"Failed to call WaitForSingleObject with error code {Marshal.GetLastWin32Error()}"); } threadHandle.Dispose(); Memory.Free(shellcodeBuffer); try { return(Memory.Read <TStructure>(returnBuffer)); } finally { Memory.Free(returnBuffer); } }