Geoff Chappell - Software Analyst
The PS_CREATE_INFO structure is exchanged between user and kernel modes for the NtCreateUserProcess and ZwCreateUserProcess functions, which were new for Windows Vista.
The PS_CREATE_INFO structure is not documented. Even for an undocumented structure that is exposed through undocumented interfaces, PS_CREATE_INFO must count as obscure. Though the structure is prepared by KERNEL32.DLL or KERNELBASE.DLL, depending on the Windows version, and is passed through NTDLL.DLL to kernel mode, and is then interpreted by the kernel, public symbol files for none of these modules have type information for the PS_CREATE_INFO in any known Windows version.
Where this type information turns up instead is in symbol files for a handful of higher-level user-mode modules that ought never to see the structure. That these modules are built with knowledge of the PS_CREATE_INFO is revealed because for some of them in some versions (starting with Windows 8) the symbol files that Microsoft has included in downloadable packages of public symbols are in fact private symbols. Why these modules are built with knowledge of the PS_CREATE_INFO or of anything else that’s defined in the same unpublished header (ntpsapi.h) is not revealed. Especially prominent among them is URLMON.DLL for being a key component of Internet Explorer and thus of having some bearing on the success of an anti-trust suit among whose allegations was that Microsoft abused its Windows monpoly to help establish Internet Explorer anti-competitively. Official reviewers of Microsoft’s settlement compliance apparently either did not notice or did not care that Internet Explorer is built with more detailed knowledge of Windows internals than Microsoft publishes even for kernel-mode programmers.
Type information for the PS_CREATE_INFO also turns up in a statically linked library, named CLFSMGMT.LIB, which Microsoft publishes with the Software Development Kit (SDK). The 32-bit builds have the type information starting with Windows Vista, the 64-bit not until Windows 8. For both, the disclosure stops after the 1903 release of Windows 10.
The only other disclosure yet known is in the Windows Driver KIt (WDK) for the original and 1511 releases of Windows 10. These have a header named ZWAPI.H which presents a prototype for ZwCreateUserProcess. From inspection of binaries, everyone who cares can have known all along that the function’s second argument is a pointer to an undocumented structure. The disclosed prototype declares this argument’s type as a PPS_CREATE_INFO. In Microsoft’s long established convention for naming pointer types, this type is consistent with PS_CREATE_INFO being Micorosft’s name for the structure that the argument points to. No other header in the same WDK even references either type, not even to define the structure as opaque. That ZWAPI.H as supplied with the WDK does not compile without prior inclusion of otherwise unpublished headers is a strong suggestion that its own publication was an oversight.
The PS_CREATE_INFO structure is 0x48 or 0x58 bytes in 32-bit and 64-bit Windows, respectively.
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x00 | 0x00 |
ULONG_PTR Size; |
0x04 | 0x08 |
PS_CREATE_STATE State; |
0x08 | 0x10 |
union { /* changing members, see below */ }; |
While the PS_CREATE_STATE enumeration is not known to be used elsewhere, it is as well given here. The defined values are:
The immediate point to the State member is to indicate whether the structure is input to or output from the NtCreateUserProcess function (or its Zw alias). As input, it defines an initial state for the created process. Output tells of the creation’s success or failure. In some cases, the State is itself enough output. In most, both for input and output, there is more data to follow in the anonymous union, and the State then selects from this union’s members:
Definition | Versions |
---|---|
struct { /* see below */ } InitState; |
6.0 and higher |
struct { HANDLE FileHandle; } FailSection; |
6.0 and higher |
struct { USHORT DllCharacteristics; } ExeFormat; |
6.2 and higher |
struct { HANDLE IFEOKey; } ExeName; |
6.0 and higher |
struct { /* see below */ } SuccessState; |
6.0 and higher |
The PsCreateInitialState value is required in State for input, i.e., when passing a PS_CREATE_INFO to NtCreateUserProcess. It selects the InitState branch.
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x08 | 0x10 |
union { ULONG InitFlags; struct { /* changing bit fields, follow link */ }; }; |
0x0C | 0x14 |
ACCESS_MASK AdditionalFileAccess; |
Six values of State in the PS_CREATE_INFO on output tell something of why NtCreateUserProcess failed. One tells of success.
For failure, the State may still be PsCreateInitialState. This means the process creation didn’t get past parameter validation—notably not even to fail at opening the file. For three of the failure states, the corresponding branch of the anonymous union has information that may help the user-mode caller not just report the problem but even to try recovering from it.
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x08 | 0x10 |
HANDLE FileHandle; |
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x08 | 0x10 |
USHORT DllCharacteristics; |
Failure as PsCreateFailExeName indicates that however usable may be the executable as a file its execution is prevented by something about its name, specifically about its configuration as a subkey of Image File Execution Options. The caller is not explicitly told what prevents execution but is returned a handle to the subkey:
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x08 | 0x10 |
HANDLE IFEOKey; |
There may be multiple causes as a point of design but only one is implemented. The executable’s Image File Execution Options subkey has a Debugger value. The kernel in effect interprets this value’s presence as meaning that a process with this name cannot ordinarily be created. (The kernel’s error code is STATUS_OBJECT_PATH_INVALID.) The one exception to “ordinarily” is if the IFEOSkipDebugger bit is set on input.
MORE TO WRITE ABOUT THIS!
The greatest output in the anonymous union is produced when State is PsCreateSuccess to indicate that the process has indeed been created. The caller is given information that it likely will want for completing the user-mode configuration of the process and would otherwise have to obtain through multiple queries:
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x08 | 0x10 |
union { ULONG OutputFlags; struct { /* changing bit fields, follow link */ }; }; |
0x0C | 0x18 |
HANDLE FileHandle; |
0x10 | 0x20 |
HANDLE SectionHandle; |
0x18 | 0x28 |
ULONGLONG UserProcessParametersNative; |
0x20 | 0x30 |
ULONG UserProcessParametersWow64; |
0x24 | 0x34 |
ULONG CurrentParameterFlags; |
0x28 | 0x38 |
ULONGLONG PebAddressNative; |
0x30 | 0x40 |
ULONG PebAddressWow64; |
0x38 | 0x48 |
ULONGLONG ManifestAddress; |
0x40 | 0x50 |
ULONG ManifestSize; |