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:
$ whoami /priv
Privilege Name Description State
SeCreateTokenPrivilege Create a token object Disabled
SeChangeNotifyPrivilege Bypass traverse checking EnabledDisabled 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:
| 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:
- C:\Windows\System32
- malicious.dllsize 8704
Another record reports token creation and successful impersonation before displaying:
[*] 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
| 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:
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) == 76The 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:
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
- 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
New token
Record user, groups and attributes, privileges, integrity, type, impersonation level, and AuthenticationId independently. No single field represents the whole security context.
- 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
Downstream service
Independently verify RPC reachability, conditional branches, work submission, actual load path, and execution identity. File existence is not execution evidence.
- 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.