public void CallFunction(CallDescriptor callDescriptor) { var completionFlagBuffer = _memory.AllocateBlock(IntPtr.Zero, sizeof(bool), ProtectionType.ReadWrite); // Write the shellcode used to perform the function call into a buffer var shellcode = AssembleShellcode(callDescriptor, completionFlagBuffer); var shellcodeBuffer = _memory.AllocateBlock(IntPtr.Zero, shellcode.Length, ProtectionType.ReadWrite); _memory.WriteBlock(shellcodeBuffer, shellcode); _memory.ProtectBlock(shellcodeBuffer, shellcode.Length, ProtectionType.ExecuteRead); // Open a handle to the first thread var firstThreadHandle = Kernel32.OpenThread(AccessMask.SpecificRightsAll | AccessMask.StandardRightsAll, false, _process.Threads[0].Id); if (firstThreadHandle.IsInvalid) { throw new Win32Exception($"Failed to call OpenThread with error code {Marshal.GetLastWin32Error()}"); } if (callDescriptor.IsWow64Call) { // Suspend the thread if (Kernel32.Wow64SuspendThread(firstThreadHandle) == -1) { throw new Win32Exception($"Failed to call Wow64SuspendThread with error code {Marshal.GetLastWin32Error()}"); } // Get the context of the thread var threadContext = new Wow64Context { ContextFlags = Wow64ContextFlags.Control }; if (!Kernel32.Wow64GetThreadContext(firstThreadHandle, ref threadContext)) { throw new Win32Exception($"Failed to call Wow64GetThreadContext with error code {Marshal.GetLastWin32Error()}"); } // Write the original instruction pointer of the thread into the top of its stack threadContext.Esp -= sizeof(int); _memory.Write((IntPtr)threadContext.Esp, threadContext.Eip); // Overwrite the instruction pointer of the thread with the address of the shellcode threadContext.Eip = (int)shellcodeBuffer; // Update the context of the thread if (!Kernel32.Wow64SetThreadContext(firstThreadHandle, ref threadContext)) { throw new Win32Exception($"Failed to call Wow64SetThreadContext with error code {Marshal.GetLastWin32Error()}"); } } else { // Suspend the thread if (Kernel32.SuspendThread(firstThreadHandle) == -1) { throw new Win32Exception($"Failed to call SuspendThread with error code {Marshal.GetLastWin32Error()}"); } // Get the context of the thread var threadContext = new Context { ContextFlags = ContextFlags.Control }; var threadContextBuffer = Marshal.AllocHGlobal(Unsafe.SizeOf <Context>()); Marshal.StructureToPtr(threadContext, threadContextBuffer, false); if (!Kernel32.GetThreadContext(firstThreadHandle, threadContextBuffer)) { throw new Win32Exception($"Failed to call GetThreadContext with error code {Marshal.GetLastWin32Error()}"); } threadContext = Marshal.PtrToStructure <Context>(threadContextBuffer); // Write the original instruction pointer of the thread into the top of its stack threadContext.Rsp -= sizeof(long); _memory.Write((IntPtr)threadContext.Rsp, threadContext.Rip); // Overwrite the instruction pointer of the thread with the address of the shellcode threadContext.Rip = (long)shellcodeBuffer; Marshal.StructureToPtr(threadContext, threadContextBuffer, false); // Update the context of the thread if (!Kernel32.SetThreadContext(firstThreadHandle, threadContextBuffer)) { throw new Win32Exception($"Failed to call SetThreadContext with error code {Marshal.GetLastWin32Error()}"); } Marshal.FreeHGlobal(threadContextBuffer); } // Send a message to the thread to ensure it executes the shellcode if (!User32.PostThreadMessage(_process.Threads[0].Id, MessageType.Null, IntPtr.Zero, IntPtr.Zero)) { throw new Win32Exception($"Failed to call PostThreadMessage with error code {Marshal.GetLastWin32Error()}"); } // Resume the thread if (Kernel32.ResumeThread(firstThreadHandle) == -1) { throw new Win32Exception($"Failed to call ResumeThread with error code {Marshal.GetLastWin32Error()}"); } while (!_memory.Read <bool>(completionFlagBuffer)) { Thread.Sleep(1); } firstThreadHandle.Dispose(); _memory.FreeBlock(shellcodeBuffer); _memory.FreeBlock(completionFlagBuffer); }
public void CallFunction(CallDescriptor callDescriptor) { // Write the shellcode used to perform the function call into a buffer var shellcode = AssembleShellcode(callDescriptor); var shellcodeBuffer = _memory.AllocateBlock(IntPtr.Zero, shellcode.Length, ProtectionType.ReadWrite); _memory.WriteBlock(shellcodeBuffer, shellcode); _memory.ProtectBlock(shellcodeBuffer, shellcode.Length, ProtectionType.ExecuteRead); // Create a suspended thread with a spoofed start address var ntStatus = Ntdll.NtCreateThreadEx(out var threadHandle, AccessMask.SpecificRightsAll | AccessMask.StandardRightsAll, IntPtr.Zero, _process.SafeHandle, _process.MainModule.BaseAddress, IntPtr.Zero, ThreadCreationFlags.CreateSuspended | ThreadCreationFlags.HideFromDebugger, 0, 0, 0, IntPtr.Zero); if (ntStatus != NtStatus.Success) { throw new Win32Exception($"Failed to call NtCreateThreadEx with error code {ntStatus}"); } if (callDescriptor.IsWow64Call) { // Get the context of the thread var threadContext = new Wow64Context { ContextFlags = Wow64ContextFlags.Integer }; if (!Kernel32.Wow64GetThreadContext(threadHandle, ref threadContext)) { throw new Win32Exception($"Failed to call Wow64GetThreadContext with error code {Marshal.GetLastWin32Error()}"); } // Change the spoofed start address to the address of the shellcode threadContext.Eax = (int)shellcodeBuffer; // Update the context of the thread if (!Kernel32.Wow64SetThreadContext(threadHandle, ref threadContext)) { throw new Win32Exception($"Failed to call Wow64GetThreadContext with error code {Marshal.GetLastWin32Error()}"); } } else { // Get the context of the thread var threadContext = new Context { ContextFlags = ContextFlags.Integer }; var threadContextBuffer = Marshal.AllocHGlobal(Unsafe.SizeOf <Context>()); Marshal.StructureToPtr(threadContext, threadContextBuffer, false); if (!Kernel32.GetThreadContext(threadHandle, threadContextBuffer)) { throw new Win32Exception($"Failed to call GetThreadContext with error code {Marshal.GetLastWin32Error()}"); } threadContext = Marshal.PtrToStructure <Context>(threadContextBuffer); // Change the spoofed start address to the address of the shellcode threadContext.Rcx = (long)shellcodeBuffer; Marshal.StructureToPtr(threadContext, threadContextBuffer, false); // Update the context of the thread if (!Kernel32.SetThreadContext(threadHandle, threadContextBuffer)) { throw new Win32Exception($"Failed to call SetThreadContext with error code {Marshal.GetLastWin32Error()}"); } Marshal.FreeHGlobal(threadContextBuffer); } // Resume the thread if (Kernel32.ResumeThread(threadHandle) == -1) { throw new Win32Exception($"Failed to call ResumeThread with error code {Marshal.GetLastWin32Error()}"); } if (Kernel32.WaitForSingleObject(threadHandle, int.MaxValue) == -1) { throw new Win32Exception($"Failed to call WaitForSingleObject with error code {Marshal.GetLastWin32Error()}"); } threadHandle.Dispose(); _memory.FreeBlock(shellcodeBuffer); }