Skip to content

Commit f28318e

Browse files
authored
Merge pull request #1335 from HackTricks-wiki/update_CreateProcessAsPPL__launch_a_Windows_Protected_Pro_20250825_124827
CreateProcessAsPPL launch a Windows Protected Process Light
2 parents 93b11a0 + 6f51e78 commit f28318e

File tree

2 files changed

+113
-4
lines changed

2 files changed

+113
-4
lines changed

src/windows-hardening/stealing-credentials/credentials-protections.md

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,64 @@ This structure is packed into a single byte and determines **who can access whom
3838
- LSASS being PPL does **not prevent credential dumping if you can execute kernel shellcode** or **leverage a high-privileged process with proper access**.
3939
- **Setting or removing PPL** requires reboot or **Secure Boot/UEFI settings**, which can persist the PPL setting even after registry changes are reversed.
4040

41+
### Create a PPL process at launch (documented API)
42+
43+
Windows exposes a documented way to request a Protected Process Light level for a child process during creation using the extended startup attribute list. This does not bypass signing requirements — the target image must be signed for the requested signer class.
44+
45+
Minimal flow in C/C++:
46+
47+
```c
48+
// Request a PPL protection level for the child process at creation time
49+
// Requires Windows 8.1+ and a properly signed image for the selected level
50+
#include <windows.h>
51+
52+
int wmain(int argc, wchar_t **argv) {
53+
STARTUPINFOEXW si = {0};
54+
PROCESS_INFORMATION pi = {0};
55+
si.StartupInfo.cb = sizeof(si);
56+
57+
SIZE_T attrSize = 0;
58+
InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize);
59+
si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrSize);
60+
if (!si.lpAttributeList) return 1;
61+
62+
if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrSize)) return 1;
63+
64+
DWORD level = PROTECTION_LEVEL_ANTIMALWARE_LIGHT; // or WINDOWS_LIGHT/LSA_LIGHT/WINTCB_LIGHT
65+
if (!UpdateProcThreadAttribute(
66+
si.lpAttributeList, 0,
67+
PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL,
68+
&level, sizeof(level), NULL, NULL)) {
69+
return 1;
70+
}
71+
72+
DWORD flags = EXTENDED_STARTUPINFO_PRESENT;
73+
if (!CreateProcessW(L"C\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE,
74+
flags, NULL, NULL, &si.StartupInfo, &pi)) {
75+
// If the image isn't signed appropriately for the requested level,
76+
// CreateProcess will fail with ERROR_INVALID_IMAGE_HASH (577).
77+
return 1;
78+
}
79+
80+
// cleanup
81+
DeleteProcThreadAttributeList(si.lpAttributeList);
82+
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
83+
CloseHandle(pi.hThread);
84+
CloseHandle(pi.hProcess);
85+
return 0;
86+
}
87+
```
88+
89+
Notes and constraints:
90+
- Use `STARTUPINFOEX` with `InitializeProcThreadAttributeList` and `UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, ...)`, then pass `EXTENDED_STARTUPINFO_PRESENT` to `CreateProcess*`.
91+
- The protection `DWORD` can be set to constants such as `PROTECTION_LEVEL_WINTCB_LIGHT`, `PROTECTION_LEVEL_WINDOWS`, `PROTECTION_LEVEL_WINDOWS_LIGHT`, `PROTECTION_LEVEL_ANTIMALWARE_LIGHT`, or `PROTECTION_LEVEL_LSA_LIGHT`.
92+
- The child only starts as PPL if its image is signed for that signer class; otherwise process creation fails, commonly with `ERROR_INVALID_IMAGE_HASH (577)` / `STATUS_INVALID_IMAGE_HASH (0xC0000428)`.
93+
- This is not a bypass — it’s a supported API meant for appropriately signed images. Useful to harden tools or validate PPL-protected configurations.
94+
95+
Example CLI using a minimal loader:
96+
- Antimalware signer: `CreateProcessAsPPL.exe 3 C:\Tools\agent.exe --svc`
97+
- LSA-light signer: `CreateProcessAsPPL.exe 4 C:\Windows\System32\notepad.exe`
98+
4199
**Bypass PPL protections options:**
42100
43101
If you want to dump LSASS despite PPL, you have 3 main options:
@@ -143,7 +201,12 @@ For more detailed information, consult the official [documentation](https://docs
143201
| Schema Admins | Schema Admins | Schema Admins | Schema Admins |
144202
| Server Operators | Server Operators | Server Operators | Server Operators |
145203

146-
{{#include ../../banners/hacktricks-training.md}}
147-
204+
## References
148205

206+
- [CreateProcessAsPPL – minimal PPL process launcher](https://github.com/2x7EQ13/CreateProcessAsPPL)
207+
- [STARTUPINFOEX structure (Win32 API)](https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-startupinfoexw)
208+
- [InitializeProcThreadAttributeList (Win32 API)](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist)
209+
- [UpdateProcThreadAttribute (Win32 API)](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute)
210+
- [LSASS RunAsPPL – background and internals](https://itm4n.github.io/lsass-runasppl/)
149211

212+
{{#include ../../banners/hacktricks-training.md}}

src/windows-hardening/windows-local-privilege-escalation/windows-c-payloads.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ int wmain(void) {
106106
STARTUPINFOW si = { .cb = sizeof(si) };
107107
PROCESS_INFORMATION pi = { 0 };
108108
if (CreateProcessWithTokenW(dupToken, LOGON_WITH_PROFILE,
109-
L"C\\\Windows\\\System32\\\cmd.exe", NULL, CREATE_NEW_CONSOLE,
109+
L"C\\\\Windows\\\\System32\\\\cmd.exe", NULL, CREATE_NEW_CONSOLE,
110110
NULL, NULL, &si, &pi)) {
111111
CloseHandle(pi.hProcess);
112112
CloseHandle(pi.hThread);
@@ -159,8 +159,54 @@ int main(void) {
159159

160160
---
161161

162+
## Create child as Protected Process Light (PPL)
163+
Request a PPL protection level for a child at creation time using `STARTUPINFOEX` + `PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL`. This is a documented API and will only succeed if the target image is signed for the requested signer class (Windows/WindowsLight/Antimalware/LSA/WinTcb).
164+
165+
```c
166+
// x86_64-w64-mingw32-gcc -O2 -o spawn_ppl.exe spawn_ppl.c
167+
#include <windows.h>
168+
169+
int wmain(void) {
170+
STARTUPINFOEXW si = {0};
171+
PROCESS_INFORMATION pi = {0};
172+
si.StartupInfo.cb = sizeof(si);
173+
174+
SIZE_T attrSize = 0;
175+
InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize);
176+
si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrSize);
177+
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrSize);
178+
179+
DWORD lvl = PROTECTION_LEVEL_ANTIMALWARE_LIGHT; // choose the desired level
180+
UpdateProcThreadAttribute(si.lpAttributeList, 0,
181+
PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL,
182+
&lvl, sizeof(lvl), NULL, NULL);
183+
184+
if (!CreateProcessW(L"C\\\Windows\\\System32\\\notepad.exe", NULL, NULL, NULL, FALSE,
185+
EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi)) {
186+
// likely ERROR_INVALID_IMAGE_HASH (577) if the image is not properly signed for that level
187+
return 1;
188+
}
189+
DeleteProcThreadAttributeList(si.lpAttributeList);
190+
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
191+
CloseHandle(pi.hThread);
192+
CloseHandle(pi.hProcess);
193+
return 0;
194+
}
195+
```
196+
197+
Levels used most commonly:
198+
- `PROTECTION_LEVEL_WINDOWS_LIGHT` (2)
199+
- `PROTECTION_LEVEL_ANTIMALWARE_LIGHT` (3)
200+
- `PROTECTION_LEVEL_LSA_LIGHT` (4)
201+
202+
Validate the result with Process Explorer/Process Hacker by checking the Protection column.
203+
204+
---
205+
162206
## References
163207
* Ron Bowes – “Fodhelper UAC Bypass Deep Dive” (2024)
164208
* SplinterCode – “AMSI Bypass 2023: The Smallest Patch Is Still Enough” (BlackHat Asia 2023)
209+
* CreateProcessAsPPL – minimal PPL process launcher: https://github.com/2x7EQ13/CreateProcessAsPPL
210+
* Microsoft Docs – STARTUPINFOEX / InitializeProcThreadAttributeList / UpdateProcThreadAttribute
165211
166-
{{#include ../../banners/hacktricks-training.md}}
212+
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)