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)

  1. Who is the process? tasklist / Sysinternals Process Explorer. Integrity column on.
  2. Primary token vs thread impersonation token. whoami is the process; a thread may differ. I use Process Explorer → Threads → Permissions only to read.
  3. Integrity vs the resource’s mandatory label. Medium cannot write High objects even with matching DACLs.
  4. Privilege present vs enabled. whoami /priv State column. SeDebugPrivilege Disabled is not the same as missing.
  5. 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 lsass
  • OpenProcessToken(..., 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, no SeImpersonatePrivilege unless the account is a service that must impersonate.
  • Services that need impersonation: isolate, no SeDebugPrivilege on the same account.
  • UAC: keep Admin Approval Mode so High is a split token, not the default.
  • Audit: Audit Privilege Use for SeDebugPrivilege / SeImpersonatePrivilege success. Noisy; filter to lsass/csrss targets in the SIEM, do not disable.
  • Do not grant SeTcbPrivilege, SeAssignPrimaryTokenPrivilege to 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

  • labuser Medium, SeDebugPrivilege absent, SeChangeNotifyPrivilege Enabled
  • Elevated split token: SeDebugPrivilege present Disabled, SeImpersonatePrivilege Enabled
  • OpenProcess(QUERY_LIMITED) on self and notepad: ok; PROCESS_ALL_ACCESS on 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