One of the most useful techniques hunt teams can use for detecting anomalous activity is the analysis of parent-child process relationships. However, more capable adversaries can bypass this using Parent PID (PPID) Spoofing allowing the execution of a malicious process from an arbitrary parent process. While this technique itself is not new, having been covered by Cobalt Strike  and Didier Stevens  , very little research has been done in detecting such attacks.
In this blog, we will explore how this technique works and how defenders can utilize Event Tracing for Windows (ETW) to detect this technique. We’ll also release a proof of concept PowerShell script to perform PPID spoofing and DLL injection, as well as a Python script that makes use of the pywintrace  library to detect this activity.
Why spoof in the first place?
In the past attackers were often able to move through networks undetected, but with the rise of EDR and threat hunting the tables have started to turn. The use of parent-child process analysis in particular has been a useful technique in detecting anomalous activity generated during nearly every phase of a kill chain.
Some examples we use at Countercept:
- Macro payload delivery - WinWord spawning processes
- JS/VBS C# payload delivery - cscript spawning csc
- Lateral movement - services/wmiprvse spawning new processes
This has pushed attackers to re-evaluate their approach and look at techniques like PPID spoofing in order to bypass modern defensive teams.
Spoofing via CreateProcessA
There are a number of different ways to spoof process parents. In this post we’re going to focus on one of the simplest and most commonly used techniques involving the API call CreateProcessA.
CreateProcessA, unsurprisingly, lets users create new processes and by default, processes will be created with their inherited parent. However, this function also supports a parameter called “lpStartupInfo” where you can essentially define the parent process you want to use. This feature was first introduced in Windows Vista with the addition of UAC in order to correctly set parents.
At a deeper technical level, the lpStartupInfo parameter points to a STARTUPINFOEX structure .This structure contains an lpAttributeList and using UpdateProcThreadAttribute you can set the process parent via the “PROC_THREAD_ATTRIBUTE_PARENT_PROCESS” attribute.
As an aside, this approach can also be used for privilege escalation. The documentation alludes to this stating that “Attributes inherited from the specified process include handles, the device map, processor affinity, priority, quotas, the process token and job object.” Adam Chester has a blog that demonstrates how this can be abused to get SYSTEM in Windows .
Have I lied to my parents?
One of the most common ways to gain a foothold on a network is using a malicious macro document. Many payloads will often launch new processes, such as cmd, PowerShell, regsvr32 or certutil; Figure 3 shows an example of this with rundll32 spawning from winword. However, this behavior is relatively anomalous and can easily be detected by most modern blue-teams.
To overcome this issue, an attacker could instead use a VBS macro implementation of the CreateProcessA technique to launch a payload from an expected parent process (e.g. Explorer launching cmd) to blend in with the environment. Figure 4 gives a glimpse into how this could be implemented.
We won’t be releasing this VBS code; however, more information can be found here .
But, can we take this even further to avoid common Windows utilities completely? One option would be to use some form of DLL or memory injection to load a payload within an already running process.
To illustrate this we created a PowerShell script based on code from Didier Stevens  that can be used to create a process with a spoofed parent and then inject a DLL within it.
# ppid spoofing and process spoofing happening now. $result1 = [Kernel32]::InitializeProcThreadAttributeList([IntPtr]::Zero, 1, 0, [ref]$lpSize) $sInfoEx.lpAttributeList = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($lpSize) $result1 = [Kernel32]::InitializeProcThreadAttributeList($sInfoEx.lpAttributeList, 1, 0, [ref]$lpSize) $result1 = [Kernel32]::UpdateProcThreadAttribute($sInfoEx.lpAttributeList, 0, 0x00020000, # PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000 $lpValue, [IntPtr]::Size, [IntPtr]::Zero, [IntPtr]::Zero) $result1 = [Kernel32]::CreateProcess($spawnTo, [IntPtr]::Zero, [ref]$SecAttr, [ref]$SecAttr, 0, 0x08080004, #EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW | CREATE_SUSPENDED # 0x00080010, # EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE (This will show the window of the process) [IntPtr]::Zero, $GetCurrentPath, [ref] $sInfoEx, [ref] $pInfo)
# Load and execute dll into spoofed process. $loadLibAddress = [Kernel32]::GetProcAddress([Kernel32]::GetModuleHandle("kernel32.dll"), "LoadLibraryA") $lpBaseAddress = [Kernel32]::VirtualAllocEx($pInfo.hProcess, 0, $dllPath.Length, 0x00003000, 0x4) $result1 = [Kernel32]::WriteProcessMemory($pInfo.hProcess, $lpBaseAddress, (New-Object "System.Text.ASCIIEncoding").GetBytes($dllPath), $dllPath.Length, [ref]0) # $result1 = [Kernel32]::VirtualProtectEx($pInfo.hProcess, $lpBaseAddress, $dllPath.Length, 0x20, [ref]0) $result1 = [Kernel32]::CreateRemoteThread($pInfo.hProcess, 0, 0, $loadLibAddress, $lpBaseAddress, 0, 0)
To demonstrate how such a script could be used to hide activity we looked at commonly running processes on Windows 10. One quite common legitimate relationship we saw was “svchost.exe” launching “RuntimeBroker.exe” (Figure 7).
Using the PowerShell script we were able to mimic this activity and forcibly spawn a legitimate “RuntimeBroker.exe” from “svchost.exe”, then inject and execute our DLL payload.
This vector shows how such techniques can potentially bypass detection rules focused on parent-child relationships.
How can we find the liar?
In the previous section we showed that the CreateProcessA technique can spoof parent IDs and from a blue team perspective if you queried running processes with something like Task Manager or Process Explorer you would see the spoofed IDs. But is there some way to find out the real IDs?
One of the best forensic data sources in Windows is Event Tracing for Windows (ETW). ETW provides a real-time stream of data about events occurring on a system and is something we use within our endpoint agent at Countercept.
The Microsoft-Windows-Kernel-Process provider in particular can give some useful insights into process creation and can help us detect the process ID spoofing. In the example below you’ll see how “rundll32.exe” (PID 5180) is being spawned from “winword.exe” (PID 9224) (Figure 9).
Looking into the ETW data collected (Figure 10) you’ll see there are multiple ProcessId fields, including the EventHeader ProcessId, as well as the actual event ProcessID and ParentProcessID. Although this is somewhat confusing, reading through the MSDN documentation, we find that the EventHeader ProcessId actually identifies the process that generated the event – i.e. the parent.
And in this legitimate example you’ll notice that the EventHeader ProcessId and ParentProcessId correctly match.
In this second example we executed our malicious PowerShell script and spawned a “RuntimeBroker.exe” (PID 4976) from “svchost.exe” (PID 4652).
As before, we can see ETW generated a process event (Figure 12); however, this time the EventHeader ProcessId and ParentProcessID are different. And in fact the EventHeader ProcessId shows the real parent, which is “winword.exe” (PID 9224). We’ve just caught someone performing ParentPID spoofing!
However, as always in threat detection, things aren’t that simple and if you attempt to do this at scale you’re going to see false positives from legitimate spoofing. One common example is User Account Control (UAC), which is used to elevate process privileges. In Windows 10 when UAC executes, the Application Information service (through svchost) is used to launch the elevated process, but will then spoof the parent to show the original caller. The example below shows how an elevated cmd.exe will show explorer.exe as the parent, when in fact it was svchost.exe.
Another false positive we saw involved crash handling with WerFault. In the example below, when MicrosoftEdge crashed svchost was used to launch WerFault.exe and the parent was spoofed as MicrosoftEdge.exe.
For testing purposes we created a simple proof-of-concept Python script that used pywintrace to log events from ETW, compare PIDs, and then filter results to remove false positives (Figure 15).
The code for the PowerShell spoofing script, as well as the detection script, can be found on our Github.
In this post we’ve shown how attackers can abuse legitimate Windows functions to fool blue teamers and potentially bypass detection techniques based on parent-child relationships.
From a defensive perspective though, we’ve shown how analysis of ETW process events can easily highlight anomalous parent spoofing and help discover the true origin of a process. As always this research shows how essential it is for defenders to push the boundaries of current technology and stay one step ahead of attackers at all times.