This is a token-reading lab, not a steal/impersonate cookbook. Target is a Windows VM where I log on as a lab user, dump whoami /all, then call OpenProcess / OpenProcessToken on a process I started. Goal: record integrity level, present vs enabled privileges, and what OpenProcess returns when I do and do not have SeDebugPrivilege enabled. I do not duplicate SYSTEM tokens, I do not call ImpersonateLoggedOnUser on a stolen handle, I do not paste a potato-class pipe server.
1Figure 1. Authorization reads the token. Presence of a privilege is not the same as enabled.
2process token: integrity | groups | privileges present/enabled
3OpenProcess(QUERY_LIMITED) ok; PROCESS_ALL_ACCESS -> err=5
Lab layout
1labs/token_lab/
2 whoami-all.txt # captured as labuser
3 priv.c # OpenProcess + OpenProcessToken on self and on notepad
I run everything as labuser in a non-domain VM. SIDs below are the machine’s, with the unique part redacted.
Artifact: whoami /priv and /all
1C:\lab> whoami /user
2USER INFORMATION
3----------------
4User Name SID
5=================== ==============================================
6labvm\labuser S-1-5-21-[REDACTED]-1001
7
8C:\lab> whoami /groups | findstr /i "Mandatory Level"
9Mandatory Label\Medium Mandatory Level Label S-1-16-8192
10
11C:\lab> whoami /priv
12PRIVILEGES INFORMATION
13----------------------
14Privilege Name Description State
15============================= ==================================== ========
16SeShutdownPrivilege Shut down the system Disabled
17SeChangeNotifyPrivilege Bypass traverse checking Enabled
18SeUndockPrivilege Remove computer from docking station Disabled
19SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
20SeTimeZonePrivilege Change the time zone Disabled
Notes I write on the ticket before any API call:
- Integrity: Medium (
S-1-16-8192). Not High, not System (S-1-16-16384). SeDebugPrivilege: absent, not merely disabled. Absence vs disabled is the first fork in the checklist.SeChangeNotifyPrivilege: Enabled. Everyone has it; it is not a finding.SeImpersonatePrivilege: absent on this interactive user. It is present on service accounts and that is a different row.
Admin-elevated prompt on the same box, for comparison (still labuser, UAC split token):
1C:\lab> whoami /priv | findstr /i "Debug Impersonate Assign"
2SeDebugPrivilege Debug programs Disabled
3SeImpersonatePrivilege Impersonate a client after auth Enabled
4SeAssignPrimaryTokenPrivilege Replace a process level token Disabled
High integrity, SeDebugPrivilege present and Disabled. Enabling it is a AdjustTokenPrivileges call I do not need for this notebook. The dump is enough to say “this token could debug if a process enabled the privilege”. I do not enable it here.
OpenProcess on a process we own
1/* priv.c — lab, no impersonation */
2#include <windows.h>
3#include <stdio.h>
4
5static void try_open(DWORD pid, DWORD access) {
6 HANDLE p = OpenProcess(access, FALSE, pid);
7 if (!p) {
8 printf("OpenProcess pid=%lu access=0x%lx err=%lu\n",
9 pid, access, GetLastError());
10 return;
11 }
12 HANDLE tok = NULL;
13 if (!OpenProcessToken(p, TOKEN_QUERY, &tok)) {
14 printf("OpenProcessToken err=%lu\n", GetLastError());
15 } else {
16 DWORD lev = 0, n = 0;
17 GetTokenInformation(tok, TokenIntegrityLevel, NULL, 0, &n);
18 printf("OpenProcess+Token pid=%lu ok token=%p (query only)\n", pid, tok);
19 CloseHandle(tok);
20 }
21 CloseHandle(p);
22}
23
24int main(void) {
25 DWORD self = GetCurrentProcessId();
26 try_open(self, PROCESS_QUERY_LIMITED_INFORMATION);
27 /* notepad.exe started by labuser — PID from tasklist, not SYSTEM */
28 try_open(4412, PROCESS_QUERY_LIMITED_INFORMATION);
29 try_open(4412, PROCESS_ALL_ACCESS);
30 /* csrss.exe is out of scope; I do not call OpenProcess on it */
31 return 0;
32}
1C:\lab> cl /nologo priv.c
2C:\lab> priv.exe
3OpenProcess+Token pid=5501 ok token=00000000000000A4 (query only)
4OpenProcess+Token pid=4412 ok token=00000000000000B0 (query only)
5OpenProcess pid=4412 access=0x1fffff err=5
4412 is notepad at Medium, same user: QUERY_LIMITED_INFORMATION succeeds, PROCESS_ALL_ACCESS returns 5 ERROR_ACCESS_DENIED. That denied line is the artifact. It is not a prelude to enabling SeDebugPrivilege and retrying on lsass. I stop.
GetLastError=5 on a SYSTEM pid from a Medium token is the same number. Do not confuse “denied on notepad with ALL_ACCESS” with “denied on csrss”. Record the PID, image name, and integrity of the target next to the error.
1C:\lab> tasklist /FI "PID eq 4412"
2Image Name PID Session Name Session# Mem Usage
3========================= ======== ================ =========== ============
4notepad.exe 4412 Console 1 8,192 K
Analysis checklist (the actual field order)
- Who is the process?
tasklist/ Sysinternals Process Explorer. Integrity column on. - Primary token vs thread impersonation token.
whoamiis the process; a thread may differ. I use Process Explorer → Threads → Permissions only to read. - Integrity vs the resource’s mandatory label. Medium cannot write High objects even with matching DACLs.
- Privilege present vs enabled.
whoami /privState column.SeDebugPrivilegeDisabled is not the same as missing. - How did this token get here? Logon type (2 interactive, 5 service, 9 NewCredentials, 10 RemoteInteractive), or a service SID. Event 4624 with Logon Type,
[REDACTED]IP.
1# Event 4624 excerpt (lab)
2Logon Type: 2
3Security ID: S-1-5-21-[REDACTED]-1001
4Account Name: labuser
5Workstation Name: LABVM
6Source Network Address: 127.0.0.1
7Logon Process: User32
Sanitized reproduction (denied / crash only)
The err=5 line is the repro I keep. A crash analog if I pass a bogus PID:
1C:\lab> priv.exe
2OpenProcess pid=1 access=0x1000 err=87
3# ERROR_INVALID_PARAMETER — pid 1 is not a Windows userspace process here
I do not write a token-stealing snippet that:
- enables
SeDebugPrivilege OpenProcess(PROCESS_ALL_ACCESS)on lsassOpenProcessToken(..., TOKEN_DUPLICATE)DuplicateTokenEx+CreateProcessWithTokenW
Those calls in that order are a steal PoC. They do not appear in priv.c.
Failed-auth: run priv.exe from a NetworkService-like lab service account and try notepad of labuser:
1OpenProcess pid=4412 access=0x1000 err=5
2# expected: different user, Medium, no SeDebugPrivilege
Linked tokens (UAC) in one dump
On an admin user the split token is two rows, not one. Process Explorer shows “Elevated: No” for the shell I started from the Start menu and “Elevated: Yes” for the “Run as administrator” twin. whoami /groups on the unelevated side includes Mandatory Label\Medium and a filtered Administrators SID marked Use for deny only. On the elevated side: High, Administrators enabled.
1C:\lab> whoami /groups | findstr /i "Administrators Mandatory"
2# unelevated:
3BUILTIN\Administrators Group used for deny only
4Mandatory Label\Medium Mandatory Level Label
5# elevated (separate prompt):
6BUILTIN\Administrators Group
7Mandatory Label\High Mandatory Level Label
I attach both dumps when the question is “did this process run elevated?”. One whoami from the wrong prompt is how IR writes the wrong integrity into the ticket. integrity in Process Explorer must match the dump; if it does not, I am looking at the wrong PID.
Mitigation / what I want on a workstation
- Interactive users: no
SeDebugPrivilege, noSeImpersonatePrivilegeunless the account is a service that must impersonate. - Services that need impersonation: isolate, no
SeDebugPrivilegeon the same account. - UAC: keep Admin Approval Mode so High is a split token, not the default.
- Audit:
Audit Privilege UseforSeDebugPrivilege/SeImpersonatePrivilegesuccess. Noisy; filter to lsass/csrss targets in the SIEM, do not disable. - Do not grant
SeTcbPrivilege,SeAssignPrimaryTokenPrivilegeto app pools.
1# local policy dump (lab)
2C:\lab> secedit /export /cfg C:\lab\sec.cfg
3C:\lab> findstr /i "SeDebug SeImpersonate SeAssign" C:\lab\sec.cfg
4SeDebugPrivilege = *S-1-5-32-544
5SeImpersonatePrivilege = *S-1-5-19,*S-1-5-20,*S-1-5-32-544
6# S-1-5-32-544 = Administrators; 19/20 = LocalService/NetworkService
What I file after this lab
labuserMedium,SeDebugPrivilegeabsent,SeChangeNotifyPrivilegeEnabled- Elevated split token:
SeDebugPrivilegepresent Disabled,SeImpersonatePrivilegeEnabled OpenProcess(QUERY_LIMITED)on self and notepad: ok;PROCESS_ALL_ACCESSon notepad: err=5- Event 4624 logon type 2, address 127.0.0.1, SID
[REDACTED] - Fix: do not grant debug/impersonate to interactive users; audit privilege use
- Out of scope: token duplication, SYSTEM impersonation, potato-class pipes
Commands appendix
1whoami /all
2whoami /priv
3tasklist /FI "IMAGENAME eq notepad.exe"
4cl priv.c && priv.exe
5secedit /export /cfg sec.cfg