Windows Privilege Escalation: Parent PID Spoofing with SeDebugPrivilege
When the textbook techniques fail, understanding Windows internals saves the day.
Introduction
During a recent penetration testing engagement, I encountered a hardened Windows machine where none of the standard privilege escalation techniques worked. No writable services, no unquoted paths, no Potato-friendly SeImpersonatePrivilege, no stored credentials, no kernel exploits. The box was locked down tight.
What I did have was a low-privileged domain user account with one unusual privilege: SeDebugPrivilege. Not a local admin. Not a member of any privileged group. Just one extra privilege — quietly assigned through Group Policy — that most pentesters would glance past in the whoami /priv output. That single privilege, combined with an understanding of how Windows process creation works, gave me a SYSTEM shell in under a minute.
This article covers the technique known as Parent PID Spoofing — what it is, why it works, and how to use it in practice. This technique is well-documented in offensive security research but is often absent from mainstream certification courses, making it a valuable addition to any pentester's toolkit.
The Setup
After gaining initial access to the target, I had a shell as a regular domain user — not a local administrator. The standard privilege escalation playbook had been exhausted: no writable services, no unquoted paths, no SeImpersonatePrivilege for Potato exploits, no stored credentials, no kernel exploits. WinPEAS showed nothing actionable. The box appeared locked down.
Out of habit, I ran the one command that should always be checked early:
C:\> whoami /priv
Privilege Name Description State
=============================== ============================== ========
SeDebugPrivilege Debug programs Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
SeDebugPrivilege — on a non-admin account. This was a misconfiguration. Someone had assigned debug privileges to this user or a group it belongs to through Local Security Policy or Group Policy, probably for "development" or "troubleshooting" purposes. They likely didn't realize they had just handed out the keys to SYSTEM.
Understanding SeDebugPrivilege
Windows uses a privilege model to control what actions a process can perform, independent of standard file/object permissions. SeDebugPrivilege is one of the most powerful privileges in the system. Its intended purpose is to allow developers to debug system processes, but its security implications go far beyond debugging.
When a process holds SeDebugPrivilege, it can:
- Open a handle to any process on the system, regardless of the process owner's security context
- Read and write the memory of any process, including SYSTEM processes
- Inject code into any running process
- Access tokens of any running process
In practical terms, SeDebugPrivilege gives you the ability to interact with SYSTEM-level processes as if you were SYSTEM yourself. The kernel's normal access control checks on process handles are effectively bypassed.
Who Has SeDebugPrivilege?
By default, only members of the local Administrators group are assigned SeDebugPrivilege. For administrators, there's an additional nuance involving User Account Control (UAC): the privilege is only active after UAC elevation ("Run as Administrator"). In a non-elevated admin session, SeDebugPrivilege exists but is disabled.
However — and this is the critical finding from my engagement — SeDebugPrivilege can be assigned to any user or group through Local Security Policy or Group Policy:
Local Security Policy → Local Policies → User Rights Assignment → "Debug programs"
When a non-administrator account has SeDebugPrivilege, there is no UAC barrier. The privilege is simply present and enabled in every session. No elevation required. No password prompt. The user opens a command prompt and has full debug access to every process on the system.
This is the scenario I encountered: a regular domain user with SeDebugPrivilege granted through policy. It's more common than you might expect — development teams, help desk accounts, and service accounts are sometimes given this privilege by administrators who don't understand its implications. In Active Directory environments, a single Group Policy can push this misconfiguration to hundreds of machines.
Why This Is Worse Than It Sounds
Administrators seeing "Debug programs" in a policy setting might assume it only allows attaching a debugger — a developer convenience with limited security impact. In reality, SeDebugPrivilege bypasses the kernel's process access control entirely. It is functionally equivalent to granting SYSTEM access to anyone who knows how to use it. There is no legitimate reason for a non-administrator account to hold this privilege in a production environment.
The Technique: Parent PID Spoofing
How Windows Process Creation Works
When a new process is created in Windows, it inherits certain properties from its parent process. Normally, the parent is the process that called CreateProcess — if you type notepad.exe in cmd.exe, then cmd.exe is the parent and notepad.exe is the child.
However, Windows provides an extended attribute called PROC_THREAD_ATTRIBUTE_PARENT_PROCESS that can be passed through the STARTUPINFOEX structure when calling CreateProcessA/W. This attribute allows the caller to specify an arbitrary parent process. The new child process will then inherit security properties from the specified parent instead of the actual calling process.
This is a legitimate, documented Windows API feature. It was designed for scenarios like the Windows Error Reporting service, which needs to create processes in the context of the crashing application.
The Attack
The attack follows from these two facts:
- SeDebugPrivilege allows us to open a handle to any process, including those running as SYSTEM
CreateProcesswithPROC_THREAD_ATTRIBUTE_PARENT_PROCESScreates a child that inherits the parent's token
Combining them:
- Enumerate processes running as NT AUTHORITY\SYSTEM
- Open a handle to one of these processes (SeDebugPrivilege makes this possible)
- Call
CreateProcessspecifying that SYSTEM process as the parent - The new process inherits the SYSTEM token
- We now have a SYSTEM shell
The entire attack can be performed with a single PowerShell script and takes less than a second to execute.
Practical Exploitation
Finding SYSTEM Processes
First, identify processes running as SYSTEM and note their PIDs:
# PowerShell — list SYSTEM processes:
Get-Process | Where-Object { $_.SessionId -eq 0 } | Format-Table Name, Id -AutoSize
# Or from CMD:
tasklist /FI "USERNAME eq NT AUTHORITY\SYSTEM"
Good target processes — these typically run as SYSTEM and are not Protected Process Light (PPL):
| Process | Typical PID Range | Notes |
|---|---|---|
| winlogon.exe | Low | Manages logon sessions. Reliable target. |
| services.exe | Low | Service Control Manager. Usually works. |
| svchost.exe | Various | Multiple instances as SYSTEM. Many to choose from. |
| spoolsv.exe | Medium | Print Spooler. Works if Spooler is running. |
| lsass.exe | Low | Works on older systems. PPL-protected on newer ones. |
Processes to Avoid
Some SYSTEM processes are protected by Protected Process Light (PPL), a kernel-level protection that prevents even SeDebugPrivilege holders from opening full-access handles:
| Process | Why It's Protected |
|---|---|
| csrss.exe | Core subsystem, always PPL |
| smss.exe | Session Manager, always PPL |
| MsMpEng.exe | Windows Defender |
| lsass.exe | Protected if RunAsPPL is enabled via registry |
| svchost.exe (some) | Certain service hosts may have PPL |
If OpenProcess fails with "Access Denied" on a SYSTEM process despite having SeDebugPrivilege, that process is likely PPL-protected. Simply try another PID.
Using psgetsys.ps1
The psgetsys.ps1 script by Andrea Pierini (decoder-it) implements the Parent PID Spoofing technique in pure PowerShell. It calls the Windows API to create a process with a spoofed parent.
# Load the script:
. .\psgetsys.ps1
# Spawn cmd.exe as SYSTEM using winlogon.exe (PID example: 624) as parent:
ImpersonateFromParentPid -ppid 624 -command "C:\Windows\System32\cmd.exe" -cmdargs ""
If the call succeeds, a new cmd.exe window opens running as SYSTEM:
[+] Got Handle for ppid: 624
[+] Updated proc attribute list
[+] Starting C:\Windows\System32\cmd.exe ...True - pid: 5488 - Last error: 0
What If the First PID Doesn't Work?
During my engagement, the first three processes I tried were PPL-protected or otherwise inaccessible. The fourth — a svchost.exe instance — worked. The approach is:
- List all SYSTEM processes
- Try
winlogon.exefirst (most reliable) - If it fails, try
services.exe - If it fails, try different
svchost.exeinstances - If it fails, try
spoolsv.exe
In practice, at least one of these will work on any Windows system.
Alternative: Direct Token Manipulation
For situations where psgetsys.ps1 isn't available, the same concept can be achieved through other tools:
Mimikatz (if already on the target):
mimikatz# privilege::debug
mimikatz# token::elevate
mimikatz# sekurlsa::logonpasswords
Meterpreter (if Metasploit is in play):
meterpreter> steal_token PID
Custom C# or C code using OpenProcess, OpenProcessToken, DuplicateTokenEx, and CreateProcessWithTokenW. Multiple public implementations exist on GitHub.
SeDebugPrivilege vs SeImpersonatePrivilege
Most pentesters are familiar with the SeImpersonatePrivilege escalation path through the "Potato" family of exploits. SeDebugPrivilege escalation is less commonly discussed but equally powerful. Here's how they compare:
| Aspect | SeImpersonatePrivilege | SeDebugPrivilege |
|---|---|---|
| Common on | IIS app pools, SQL services | Local admins (after UAC), misconfigured non-admin accounts |
| Technique | Potato exploits (coerce SYSTEM auth) | Parent PID spoofing (inherit SYSTEM token) |
| Requires admin? | No (service accounts have it) | No (if assigned via policy to non-admins) |
| Dependency | Specific Potato for specific OS version | Universal — works on any Windows version |
| External tools | GodPotato, PrintSpoofer, JuicyPotato | psgetsys.ps1 (pure PowerShell, ~50 lines) |
| Failure modes | Print Spooler disabled, COM blocked, .NET mismatch | PPL-protected processes (try another PID) |
| Speed | Fast (seconds) | Fast (seconds) |
| Detection | Moderate (COM/named pipe activity) | Low (standard process creation API) |
In offensive engagements, checking both privileges should be standard:
whoami /priv → SeImpersonatePrivilege? → Potato exploits
whoami /priv → SeDebugPrivilege? → Parent PID spoofing
Detection and Defense
For Blue Teams
Parent PID spoofing creates a visible anomaly: a process whose parent doesn't match the expected process tree. For example, cmd.exe appearing as a child of winlogon.exe is unusual and detectable.
Detection strategies:
- Process tree monitoring: Flag processes whose parent PID doesn't match the expected execution chain.
cmd.exeorpowershell.exespawned fromwinlogon.exeorservices.exeis a strong indicator. - ETW (Event Tracing for Windows): Monitor for
CreateProcesscalls withPROC_THREAD_ATTRIBUTE_PARENT_PROCESSset. - Sysmon Event ID 1: Process creation events include the ParentProcessId field. Cross-reference with expected parent-child relationships.
- Credential Guard: Reduces the value of token theft by isolating credential material in a virtualization-based security (VBS) container.
Mitigation
- Minimize SeDebugPrivilege assignments: Audit Local Security Policy and GPOs. Only accounts that genuinely require debug access should have this privilege.
- Enable PPL for LSASS: Set
RunAsPPLin the registry to protectlsass.exefrom handle-based attacks. - Use Protected Users group: Members of this AD group have additional protections against credential theft.
- Enforce UAC properly: Don't disable UAC on workstations. The filtered token model prevents SeDebugPrivilege from being active in non-elevated contexts.
Key Takeaways
-
Always check
whoami /privearly — even on non-admin accounts. SeDebugPrivilege on a regular user is an immediate, guaranteed path to SYSTEM. Don't skip this check just because you're not an administrator. -
SeDebugPrivilege on a non-admin account is a critical misconfiguration — it means someone assigned "Debug programs" through policy without understanding the implications. In a real engagement, this should be flagged as a high-severity finding in your report.
-
Parent PID Spoofing is universal — unlike Potato exploits which vary by OS version, this technique works on any Windows version from Vista through Server 2025. The only variable is which SYSTEM process isn't PPL-protected.
-
PPL is the only real obstacle — and it's easily bypassed by targeting unprotected SYSTEM processes. Most systems have several available. If the first PID fails, try the next one.
-
The technique is fast and quiet — no network traffic, no COM activation, no named pipe creation. A single
CreateProcesscall with an extended attribute. Many EDR solutions don't flag it. -
Always check both SeImpersonate and SeDebug — they offer parallel escalation paths. If one fails, the other might succeed. On a hardened machine where Potato exploits are blocked, SeDebugPrivilege with Parent PID spoofing can be the only way up.
References
- psgetsystem — decoder-it (Andrea Pierini)
- Parent PID Spoofing — Microsoft Documentation on PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
- SeDebugPrivilege — Token Privileges in Windows Internals
- Protected Process Light (PPL) — Understanding Windows Protected Processes
Published on the Funway Interactive Blog — June 2026