CreateProcessInternal function prototype

Hooking process creation APIs

Today I had to deal with the problem of logging the creation of new processes in an API spying project. The options were to:

  • hook each function that deals with process creation separately (WinExec, ShellExecute*, ShellExecuteEx* and CreateProcess*), or
  • find a function called by all of the above (and if possible, the highest one in the call tree)

The latter is the obvious choice, since it requires much less code and is easier to parse in the generated logs. At this point, the choices were:

  • NtCreateSection (or better yet, NtCreateProcessEx) from ntdll.dll (the process of obtaining the process name from the handle passed to the function is described here), and
  • CreateProcessInternal from kernel32.dll, which unfortunately is an internal function that Google knows nothing about.

Again I chose the latter option, and did some digging to find out what gets passed to CreateProcessInternal(W).

CreateProcessInternalW

On Windows XP SP2 (possibly other versions too), it looks something like this:

DWORD WINAPI CreateProcessInternal(
__in DWORD unknown1, // always (?) NULL
__in_opt LPCTSTR lpApplicationName,
__inout_opt LPTSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCTSTR lpCurrentDirectory,
__in LPSTARTUPINFO lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation,
__in DWORD unknown2 // always (?) NULL
);

If you're accustomed to the Win32 API, you've probably noticed that the arguments are the same as the ones passed to CreateProcess, except for the first and the last ones, which always appear to be NULL.

CreateProcessInternalA calls CreateProcessInternalW internally (no pun intended), and (at some point) so do all the other process-creation APIs mentioned above, so CreateProcessInternalW is an excellent API to hook in order to catch process creations. The lpCommandLine argument contains both the executable image path and the arguments.

A better alternative

A better solution would be hooking NtCreateProcessEx in ntdll.dll (prototype "documented" here), which is at the lowest possible level in user mode. The process name is in the ObjectName:PUNICODE_STRING field of the ObjectAttributes:OBJECT_ATTRIBUTES argument ("documented" here). For my purposes, however, CreateProcessInternalW was plentifully enough.

A small downside is loosing the program arguments, but a solution could probably be found.

In the WTF?! department

As a piece of fun trivia, here is the implementation of the (internal) CreateProcessInternalWSecure API on the same Windows XP SP2:

; Exported entry 102. CreateProcessInternalWSecure
_CreateProcessInternalWSecure@0 proc near
C3 retn
_CreateProcessInternalWSecure@0 endp

Yes, it's actually just a RET. Why in the world would Microsoft need a dummy **internal** API called CreateProcessInternalWSecure is far beyond my understanding. If it were published, backwards-compatibility would be a possible explanation, but since it's hidden, it makes very little sense.

1 comment:

LizardM4 said...

Looking for the same thing (and solved detouring ZwCreateProcessEx in ntdll). I was astonished too when I discovered about CreateProcessInternalWSecure..!

But as regards CreateProcessInternal in kernel32, the first parameters aren't always 0: CreateProcessAsUser in advapi32.dll uses CreateProcessInternal and passes different values for the first and last param, so they probably regards the execution context.