~/posts/binary/windows-token-creation-effective-impersonation.md

Windows tokens: creation, impersonation, and actual access

Separate token creation, effective thread impersonation, and file access. Examine error 1346, logon-session comparisons, and the StorSvc loading path, with validated SID and buffer-size checks.

date[31:24]
read[23:16]
8 min
cat[15:8]
Binary
Contents
  1. 0x00The starting point is an existing privilege
  2. 0x01Token attributes describe different dimensions
  3. 0x02Error 1346 occurs while opening the source file
  4. 0x03Isolate the logon-session identifier as a variable
  5. 0x04Check SID identity and variable-length capacity first
  6. 0x05File placement is followed by a separate loading path
  7. 0x06Keep an independent checkpoint at every stage
  8. 0x07Official references

A token containing the Administrators group does not by itself give a thread administrator access. A set of Windows observations from 2023 reports successful token creation and impersonation, followed by file-access error 1346. The interesting boundary is between the token object, the thread's effective identity, and the access check on a particular object.

Keep those stages separate: establish the caller's existing SeCreateTokenPrivilege, inspect the new token's attributes, then check the thread that actually performs the file operation. The records include 10.0.17134.1 and 10.0.20348.587. Other static records lack complete build information and should not be treated as evidence about every Windows version.

The starting point is an existing privilege

The initial output on the older build includes:

10.0.17134.1 · selected privilege output
$ whoami /priv
Privilege Name                Description                  State
SeCreateTokenPrivilege        Create a token object        Disabled
SeChangeNotifyPrivilege       Bypass traverse checking     Enabled

Disabled means the token already contains the privilege, but it is not enabled. AdjustTokenPrivileges changes existing privileges; it does not introduce a privilege absent from the token. A nonzero return also requires checking GetLastError() for ERROR_NOT_ALL_ASSIGNED. This is an explicit API contract, not the usual shorthand of treating a successful call as proof that every request took effect. Microsoft API documentation

The adjustment must also target the token performing the checked operation. Adding a privilege to a token being constructed or prepared for later use is different from having that privilege as the caller of the creation operation. An Administrator window caption does not establish the account configuration, token provenance, or elevation mechanism.

Token attributes describe different dimensions

The sample requests an impersonation token through ZwCreateToken, retains the current user identity, adds the built-in Administrators group, and selects medium integrity. Interpret the reported fields separately:

Equal numbers do not imply equal meanings8 rows
Field Recorded value or object Meaning for analysis
TokenUser Current user SID Represented user, not the default owner
TokenGroups S-1-5-32-544 and others Group SIDs require enabled and deny-only attributes
TokenPrivileges 6 entries Presence and enabled state are separate
TokenIntegrityLevel S-1-16-8192 Medium integrity is not a complete ordinary-user permission set
TokenType 2 TokenImpersonation
TokenImpersonationLevel 2 SecurityImpersonation, local impersonation
AuthenticationId 0x3e7 or 0x3e6 Logon-session LUID, not a user SID
TokenOwner Default owner SID Not a substitute for TokenUser

TokenType = 2 and TokenImpersonationLevel = 2 belong to different enumerations. SecurityIdentification allows identifying the client without the same local impersonation capability; SecurityDelegation extends impersonation to remote systems. See Access tokens and SECURITY_IMPERSONATION_LEVEL for the respective definitions.

Medium integrity and an Administrators group SID are not contradictory. Integrity policy, group attributes, privileges, and the object's DACL affect the operation. The token's default DACL instead helps initialize security descriptors for new objects; it is not a pass for accessing every existing object.

Error 1346 occurs while opening the source file

The successful record reports 8704 bytes read and written. The directory listing shows 8.50 KB, consistent with 8704 / 1024 = 8.5. Only the relevant file is retained below:

Recorded destination
  • C:\Windows\System32
    • malicious.dllsize 8704
1 directory, 1 file

Another record reports token creation and successful impersonation before displaying:

Recorded failurelog
[*] Successfully impersonated the elevated token.
[-] Could not open source file by CreateFileW: [1346].
[-] Failed to exploit SeCreateTokenPrivilege.

The failed operation opens the source file; destination writing has not been reached. 1346 = 0x542 is ERROR_BAD_IMPERSONATION_LEVEL, indicating a missing or invalid required impersonation level. It is neither a generic DACL denial nor the status returned by ZwCreateToken. Microsoft error codes

After SetThreadToken succeeds, reopen that thread's impersonation token with OpenThreadToken and query its type, level, group attributes, privileges, and integrity. OpenAsSelf = TRUE makes the access check for opening the token handle use the process security context. It neither reverts the thread nor raises the impersonation level of the returned token. OpenThreadToken documentation

Isolate the logon-session identifier as a variable

What the three records establish3 rows
Record AuthenticationId Printed impersonation level File result Environment boundary
Early success 0x3e7 2 8704 bytes read and written Paired with the 10.0.17134.1 starting record
Failure comparison 0x3e7 2 Source open returns 1346 Static record has no complete build
Success after adjustment 0x3e6 2 8704 bytes read and written Static record has no complete build

0x3e7 and 0x3e6 are the low parts of the system and anonymous logon-session constants, respectively, with zero high parts. Changing AuthenticationId does not make TokenUser the system or anonymous user. Nor does it select the SecurityAnonymous impersonation level.

Successful access with 0x3e6 is notable, but the record does not expose every kernel check. A causal comparison would hold the user SID, groups and attributes, privileges, integrity, impersonation level, target object, and requested access constant, changing only the logon-session identifier. Both paths should retain effective-token snapshots before and after attachment. The evidence establishes different outcomes, not that every subsequent check is skipped.

The animated demonstration separately shows 10.0.20348.587, group count 13, and AuthenticationId = 0x3e6. The static success record has group count 12. These are not identical snapshots from one run, so their version and configuration labels must remain separate.

Check SID identity and variable-length capacity first

A token builder can mix authorization errors with buffer errors. The following check creates no token; it tests full-SID distinctions and variable-length array sizing:

token-capacity-model.pypython
def required_bytes(count, first, stride, maximum):
    if first < 0 or stride <= 0 or first > maximum:
        raise ValueError("invalid layout")
    if count < 0 or count > (maximum - first) // stride:
        raise ValueError("invalid count")
    return first + count * stride

builtin_users = (1, 5, 32, 545)
unrelated_sid = (1, 5, 21, 111, 222, 333, 545)
assert builtin_users[-1] == unrelated_sid[-1]
assert builtin_users != unrelated_sid
assert required_bytes(6, 4, 12, 0xffffffff) == 76

The tuples are a conceptual model, not SID memory encodings. Windows EqualSid compares complete, valid SIDs. Converting and comparing these examples with the system APIs returns unequal; their lengths are 16 and 28 bytes. Overwriting storage for a shorter SID with a longer one corrupts adjacent data.

Read-only local validation also confirmed a 4-byte offset for TOKEN_PRIVILEGES.Privileges, a 12-byte LUID_AND_ATTRIBUTES entry, and 76 bytes for six entries. The model passed 1025 ordinary counts, one upper boundary, and two rejection cases. Real C/C++ code should use the target ABI's offsetof, sizeof, and capacity limit rather than treating these constants as universal layout guarantees.

ANYSIZE_ARRAY = 1 neither limits the structure to one entry nor allocates storage for additional entries. The TOKEN_PRIVILEGES documentation requires enough allocation for the extra elements. Also review overwritten allocation pointers, missing cleanup, short reads and writes, and confusion between NTSTATUS and Win32 errors. Those are correctness issues in the builder itself.

File placement is followed by a separate loading path

Three StorSvc decompilation excerpts show an RPC entry, work-object creation, and module loading in the callback. The dashed edge marks a submission site absent from the displayed excerpts:

flowchart TD
  accTitle: StorSvc entry, work object, and callback
  accDescr: Device and capability checks precede initialization and work-object creation. The excerpts omit submission; module loading occurs in the callback.
  A["SvcRebootToFlashingMode"] --> B["Device and capability checks"]
  B --> C["InitResetPhone"]
  C --> D["CreateThreadpoolWork"]
  D -. "Submission site not shown" .-> E["ResetPhoneWorkerCallback"]
  E --> F["LoadLibraryW"]
  F --> G["GetProcAddress"]

CreateThreadpoolWork creates the work object; SubmitThreadpoolWork posts it to the pool. Registering a callback address does not prove the callback executed. The entry also contains device-family, multi-session configuration, and client-capability branches. Already-initialized and first-initialization paths must be distinguished.

The callback's relevant order reduces to this semantic excerpt, omitting waits, locks, and later shutdown branches:

ResetPhoneWorkerCallback · semantic excerptcpp
HMODULE module = LoadLibraryW(L"SprintCSP.dll");
if (module) {
    auto factory_reset = reinterpret_cast<void (*)()>(
        GetProcAddress(module, "FactoryResetUICC"));
    if (factory_reset) {
        factory_reset();
    }
    FreeLibrary(module);
}

Distinguish initialization on first DLL load from calling an export. For a newly loaded DLL, LoadLibraryW triggers load initialization; a later failure to find an export does not undo initialization that already ran. Previously loaded modules, search policy, architecture, signing or loading policy, and the service's effective token can all affect the result. LoadLibraryW documentation

The 10.0.20348.587 demonstration ends with whoami reporting nt authority\system and also shows the file-write result and RPC client's message. That supports the outcome in that environment, not an execution guarantee across versions. Without the service binary, complete token snapshots, and execution tracing, unresolved loading conditions remain unresolved.

Keep an independent checkpoint at every stage

Review sequence5 steps
  1. 1

    Caller

    Record the OS build, initial token provenance, privilege presence, enable request, and resulting state. Keep each API return value together with its documented error channel.

  2. 2

    New token

    Record user, groups and attributes, privileges, integrity, type, impersonation level, and AuthenticationId independently. No single field represents the whole security context.

  3. 3

    Effective thread token

    Query the thread again after attachment, then perform one object operation. Record source open, destination creation, reading, and writing as separate stages.

  4. 4

    Downstream service

    Independently verify RPC reachability, conditional branches, work submission, actual load path, and execution identity. File existence is not execution evidence.

  5. 5

    Completion and cleanup

    Restore the thread identity and check the result, close handles, and verify file lengths. If restoration fails, terminate that operation path rather than letting later work inherit a residual context.

The conclusion belongs to the specific access check: an existing creation privilege, new token fields, effective impersonation state, and service loading conditions each require evidence. Local validation covered only SID and size checks, not privileged token creation, system-directory writes, or StorSvc execution. The historical patch sets and precise kernel explanation still require separate verification.

Official references

NORMAL~/posts/binary/windows-token-creation-effective-impersonation.md§--
0%en