From 6f51e788c2c290c6f21c1aa22aa1a21701bdeb6c Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Mon, 25 Aug 2025 12:53:05 +0000 Subject: [PATCH] Add content from: CreateProcessAsPPL: launch a Windows Protected Process Light --- .../credentials-protections.md | 67 ++++++++++++++++++- .../windows-c-payloads.md | 50 +++++++++++++- 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/src/windows-hardening/stealing-credentials/credentials-protections.md b/src/windows-hardening/stealing-credentials/credentials-protections.md index 337b57c59..84f857150 100644 --- a/src/windows-hardening/stealing-credentials/credentials-protections.md +++ b/src/windows-hardening/stealing-credentials/credentials-protections.md @@ -38,6 +38,64 @@ This structure is packed into a single byte and determines **who can access whom - LSASS being PPL does **not prevent credential dumping if you can execute kernel shellcode** or **leverage a high-privileged process with proper access**. - **Setting or removing PPL** requires reboot or **Secure Boot/UEFI settings**, which can persist the PPL setting even after registry changes are reversed. +### Create a PPL process at launch (documented API) + +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. + +Minimal flow in C/C++: + +```c +// Request a PPL protection level for the child process at creation time +// Requires Windows 8.1+ and a properly signed image for the selected level +#include + +int wmain(int argc, wchar_t **argv) { + STARTUPINFOEXW si = {0}; + PROCESS_INFORMATION pi = {0}; + si.StartupInfo.cb = sizeof(si); + + SIZE_T attrSize = 0; + InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize); + si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrSize); + if (!si.lpAttributeList) return 1; + + if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrSize)) return 1; + + DWORD level = PROTECTION_LEVEL_ANTIMALWARE_LIGHT; // or WINDOWS_LIGHT/LSA_LIGHT/WINTCB_LIGHT + if (!UpdateProcThreadAttribute( + si.lpAttributeList, 0, + PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, + &level, sizeof(level), NULL, NULL)) { + return 1; + } + + DWORD flags = EXTENDED_STARTUPINFO_PRESENT; + if (!CreateProcessW(L"C\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, + flags, NULL, NULL, &si.StartupInfo, &pi)) { + // If the image isn't signed appropriately for the requested level, + // CreateProcess will fail with ERROR_INVALID_IMAGE_HASH (577). + return 1; + } + + // cleanup + DeleteProcThreadAttributeList(si.lpAttributeList); + HeapFree(GetProcessHeap(), 0, si.lpAttributeList); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return 0; +} +``` + +Notes and constraints: +- Use `STARTUPINFOEX` with `InitializeProcThreadAttributeList` and `UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, ...)`, then pass `EXTENDED_STARTUPINFO_PRESENT` to `CreateProcess*`. +- 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`. +- 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)`. +- This is not a bypass — it’s a supported API meant for appropriately signed images. Useful to harden tools or validate PPL-protected configurations. + +Example CLI using a minimal loader: +- Antimalware signer: `CreateProcessAsPPL.exe 3 C:\Tools\agent.exe --svc` +- LSA-light signer: `CreateProcessAsPPL.exe 4 C:\Windows\System32\notepad.exe` + **Bypass PPL protections options:** 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 | Schema Admins | Schema Admins | Schema Admins | Schema Admins | | Server Operators | Server Operators | Server Operators | Server Operators | -{{#include ../../banners/hacktricks-training.md}} - +## References +- [CreateProcessAsPPL – minimal PPL process launcher](https://github.com/2x7EQ13/CreateProcessAsPPL) +- [STARTUPINFOEX structure (Win32 API)](https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-startupinfoexw) +- [InitializeProcThreadAttributeList (Win32 API)](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist) +- [UpdateProcThreadAttribute (Win32 API)](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute) +- [LSASS RunAsPPL – background and internals](https://itm4n.github.io/lsass-runasppl/) +{{#include ../../banners/hacktricks-training.md}} \ No newline at end of file diff --git a/src/windows-hardening/windows-local-privilege-escalation/windows-c-payloads.md b/src/windows-hardening/windows-local-privilege-escalation/windows-c-payloads.md index 828b44483..580ea62d2 100644 --- a/src/windows-hardening/windows-local-privilege-escalation/windows-c-payloads.md +++ b/src/windows-hardening/windows-local-privilege-escalation/windows-c-payloads.md @@ -106,7 +106,7 @@ int wmain(void) { STARTUPINFOW si = { .cb = sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; if (CreateProcessWithTokenW(dupToken, LOGON_WITH_PROFILE, - L"C\\\Windows\\\System32\\\cmd.exe", NULL, CREATE_NEW_CONSOLE, + L"C\\\\Windows\\\\System32\\\\cmd.exe", NULL, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); @@ -159,8 +159,54 @@ int main(void) { --- +## Create child as Protected Process Light (PPL) +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). + +```c +// x86_64-w64-mingw32-gcc -O2 -o spawn_ppl.exe spawn_ppl.c +#include + +int wmain(void) { + STARTUPINFOEXW si = {0}; + PROCESS_INFORMATION pi = {0}; + si.StartupInfo.cb = sizeof(si); + + SIZE_T attrSize = 0; + InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize); + si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrSize); + InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrSize); + + DWORD lvl = PROTECTION_LEVEL_ANTIMALWARE_LIGHT; // choose the desired level + UpdateProcThreadAttribute(si.lpAttributeList, 0, + PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, + &lvl, sizeof(lvl), NULL, NULL); + + if (!CreateProcessW(L"C\\\Windows\\\System32\\\notepad.exe", NULL, NULL, NULL, FALSE, + EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi)) { + // likely ERROR_INVALID_IMAGE_HASH (577) if the image is not properly signed for that level + return 1; + } + DeleteProcThreadAttributeList(si.lpAttributeList); + HeapFree(GetProcessHeap(), 0, si.lpAttributeList); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return 0; +} +``` + +Levels used most commonly: +- `PROTECTION_LEVEL_WINDOWS_LIGHT` (2) +- `PROTECTION_LEVEL_ANTIMALWARE_LIGHT` (3) +- `PROTECTION_LEVEL_LSA_LIGHT` (4) + +Validate the result with Process Explorer/Process Hacker by checking the Protection column. + +--- + ## References * Ron Bowes – “Fodhelper UAC Bypass Deep Dive” (2024) * SplinterCode – “AMSI Bypass 2023: The Smallest Patch Is Still Enough” (BlackHat Asia 2023) +* CreateProcessAsPPL – minimal PPL process launcher: https://github.com/2x7EQ13/CreateProcessAsPPL +* Microsoft Docs – STARTUPINFOEX / InitializeProcThreadAttributeList / UpdateProcThreadAttribute -{{#include ../../banners/hacktricks-training.md}} +{{#include ../../banners/hacktricks-training.md}} \ No newline at end of file