▸ On this page 7 sections
Background
Every process on Windows carries an access token describing its security context. If you can open a more privileged process and duplicate its token, you can impersonate that context - often without ever spawning a new shell, which is exactly what EDR is watching for.
This is a lab note on the mechanics. Code is illustrative pseudo-C.
Locating a useful token
Enumerate processes, filter for ones running as a target identity, and open their tokens with TOKEN_DUPLICATE access:
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
HANDLE tok;
OpenProcessToken(h, TOKEN_DUPLICATE | TOKEN_QUERY, &tok);
You only need SeDebugPrivilege for many cross-session opens - check what you already hold before reaching for anything louder.
Impersonating
Duplicate the token as an impersonation token, then apply it to the current thread:
DuplicateTokenEx(tok, MAXIMUM_ALLOWED, NULL,
SecurityImpersonation, TokenImpersonation, &dup);
ImpersonateLoggedOnUser(dup);
ImpersonateLoggedOnUser applies the token to the calling thread, not the process. That matters: process-level token queries (e.g. EDR walking NtQueryInformationProcess) will still see the original identity. Only file / network / registry calls made from that thread run under the impersonated context.
At this point, file and network calls from the current thread run under the duplicated identity. Revert with RevertToSelf() when done.
Creating a process under the stolen token
If you need a full process context (not just thread impersonation), use CreateProcessWithTokenW:
HANDLE primary;
DuplicateTokenEx(tok, MAXIMUM_ALLOWED, NULL,
SecurityImpersonation, TokenPrimary, &primary);
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcessWithTokenW(primary, LOGON_NETCREDENTIALS_ONLY,
L"cmd.exe", NULL, 0, NULL, NULL, &si, &pi);
LOGON_NETCREDENTIALS_ONLY keeps local execution under your own identity but uses the stolen token for any network authentication. Useful for lateral movement without touching disk on the source host.
Privilege escalation via token manipulation
Certain privileges - when held by your impersonation token - are directly exploitable. SeImpersonatePrivilege is the classic example: service accounts running under IIS, MSSQL, or DCOM hold it by default, and it lets you impersonate any token that authenticates to a COM server you control (the "potato" family of techniques).
whoami /priv
# Look for:
# SeImpersonatePrivilege Enabled
# SeAssignPrimaryTokenPrivilege Enabled
Enabling a privilege requires AdjustTokenPrivileges - but you can only enable privileges already present in the token. You can't grant yourself SeDebugPrivilege from nothing; you need a process that already holds it.
Detection surface
Token theft is deliberately low-noise, but not invisible:
OpenProcesswithPROCESS_QUERY_INFORMATIONon high-integrity processes is flagged by many EDRs when the caller is lower-integrity.DuplicateHandleacross process boundaries (if relayed through a broker) is visible in kernel callbacks.CreateProcessWithTokenWemits a process-creation event with mismatched parent/token SIDs - straightforward to alert on.- Thread impersonation via
ImpersonateLoggedOnUserleaves an ETW event underMicrosoft-Windows-Security-Auditingif object access auditing is enabled.
The stealthiest path stays at thread impersonation, avoids cross-process handle duplication where possible, and cleans up the duplicated handle immediately after use.
Takeaway
Token theft is a first-class technique precisely because it blends into Windows' own impersonation model. The defensive answer isn't to disable impersonation - too much breaks - but to audit the chain: which service accounts hold SeImpersonatePrivilege, which processes run at high integrity with queryable handles, and whether your EDR correlates thread-level identity changes rather than just process-creation events.