4

On Windows, Python (2)'s standard library routine subprocess.Popen allows you to specify arbitrary flags to CreateProcess, and you can access the process handle for the newly-created process from the object that Popen returns. However, the thread handle for the newly-created process's initial thread is closed by the library before Popen returns.

Now, I need to create a process suspended (CREATE_SUSPENDED in creation flags) so that I can manipulate it (specifically, attach it to a job object) before it has a chance to execute any code. However, that means I need the thread handle in order to release the process from suspension (using ResumeThread). The only way I can find, to recover the thread handle, is to use the "tool help" library to walk over all threads on the entire system (e.g. see this question and answer). This works, but I do not like it. Specifically, I am concerned that taking a snapshot of all the threads on the system every time I need to create a process will be too expensive. (The larger application is a test suite, using processes for isolation; it creates and destroys processes at a rate of tens to hundreds a second.)

So, the question is: is there a more efficient way to resume execution of a process that was suspended by CREATE_SUSPENDED, if all you have is the process handle, and the facilities of the Python 2 standard library (including ctypes, but not the winapi add-on)? Vista-and-higher techniques are acceptable, but XP compatibility is preferred.

icedwater
  • 4,701
  • 3
  • 35
  • 50
zwol
  • 135,547
  • 38
  • 252
  • 361
  • You could make your new thread wait on a global (or named) event object as the first thing it does; then just call `SetEvent` to release it. That might give you a similar outcome. – Jonathan Potter Dec 31 '15 at 21:32
  • @JonathanPotter Alas, I don't control the code of the program being executed. – zwol Dec 31 '15 at 21:44
  • Seems unlikely that there is any other sensible way to find the main thread, though I'll think you'll find that the overhead involved isn't really significant compared to the cost of launching a process. Is there no way to call CreateProcess yourself rather than using .POpen? – Harry Johnston Dec 31 '15 at 22:46
  • @HarryJohnston There's no way to wrap a Popen object around the results of manually calling CreateProcess, and the calling code requires a Popen object (or, more precisely, I do not want to have to reinvent all of the low-level pipe-related knowledge in the subprocess module). – zwol Jan 02 '16 at 18:13
  • OK. Given all the constraints, I think the answer has to be "no", there isn't any more efficient way of doing this. If we loosen the constraints, you could, oh, hook CreateProcess, or use a customized build of Python, or something like that. But I really think if you do the measurements you'll find that the process snapshot doesn't add any significant overhead. I'd be surprised if it was even 1% as expensive as launching a process. – Harry Johnston Jan 02 '16 at 21:50

2 Answers2

2

I have found a faster approach; unfortunately it relies on an undocumented API, NtResumeProcess. This does exactly what it sounds like - takes a process handle and applies the equivalent of ResumeThread to every thread in the process. Python/ctypes code to use it looks something like

import ctypes
from ctypes.wintypes import HANDLE, LONG, ULONG

ntdll = ctypes.WinDLL("ntdll.dll")
RtlNtStatusToDosError = ntdll.RtlNtStatusToDosError
NtResumeProcess = ntdll.NtResumeProcess

def errcheck_ntstatus(status, *etc):
   if status < 0: raise ctypes.WinError(RtlNtStatusToDosError(status))
   return status

RtlNtStatusToDosError.argtypes = (LONG,)
RtlNtStatusToDosError.restype  = ULONG
# RtlNtStatusToDosError cannot fail

NtResumeProcess.argtypes = (HANDLE,)
NtResumeProcess.restype  = LONG
NtResumeProcess.errcheck = errcheck_ntstatus

def resume_subprocess(proc):
    NtResumeProcess(int(proc._handle))

I measured approximately 20% less process setup overhead using this technique than using Toolhelp, on an otherwise-idle Windows 7 virtual machine. As expected given how Toolhelp works, the performance delta gets bigger the more threads exist on the system -- whether or not they have anything to do with the program in question.

Given the obvious general utility of NtResumeProcess and its counterpart NtSuspendProcess, I am left wondering why they have never been documented and given kernel32 wrappers. They are used by a handful of core system DLLs and EXEs all of which, AFAICT, are part of the Windows Error Reporting mechanism (faultrep.dll, werui.dll, werfault.exe, dwwin.exe, etc) and don't appear to re-expose the functionality under documented names. It seems unlikely that these functions would change their semantics without also changing their names, but a defensively-coded program should probably be prepared for them to disappear (falling back to toolhelp, I suppose).

zwol
  • 135,547
  • 38
  • 252
  • 361
-2

I'm posting this here, because I found something that addresses this question. I'm looking into this myself and I believe that I've found the solution with this.

I can't give you an excerpt or a summary, because it's just too much and I found it just two hours ago. I'm posting this here for all the others who, like me, seek a way to "easily" spawn a proper child process in windows, but want to execute a cuckoo instead. ;)

The whole second chapter is of importance, but the specifics start at page 12.

http://lsd-pl.net/winasm.pdf

I hope that it helps others as much as it hopefully going to help me.

Edit:

I guess I can add more to it. From what I've gathered, does this document explain how to spawn a sleeping process which never gets executed. This way we have a properly set-up windows process running. Then it explains that by using the win32api functions VirtualAllocEx and WriteProcessMemory, we can easily allocate executable pages and inject machine code into the other process.

Then - the best part in my opinion - it's possible to change the registers of the process, allowing the programmer to change the instruction pointer to point at the cuckoo!

Amazing!

z0rberg's
  • 674
  • 5
  • 10
  • That's cute and all but it is not an answer to the question I asked. I didn't want to inject code into the subprocess, I just wanted to tweak its state a little and then run it normally. – zwol Jan 13 '17 at 00:02