# Binary Diff Report — csc.sys / KB5068861 / CVE-2025-60705

**Tool:** capstone (x86_64 disassembly) + pefile — ZwCreateKey/ZwOpenKey IAT cross-reference and OBJECT_ATTRIBUTES.Attributes pattern matching
**Pre-patch:**  `csc.sys` 10.0.26100.5074 — PE timestamp `0xDBAEA51` — SHA-256 `b80320fb15aa881239b04d030d069c2671b960b579817fc7e3314a1c2015ac88` — 632,208 bytes
**Post-patch:** `csc.sys` 10.0.26100.7171 — PE timestamp `0x2E0C3175` — SHA-256 `4bf3f8ed10649d98f7f8d945bfae8c88988811353c41f4b806e2fa047a16226c` — 632,216 bytes (+8)
**KB:** KB5068861 (Windows 11 Version 24H2 — November 11, 2025 Patch Tuesday)
**CVE:** CVE-2025-60705

---

## Summary

Privilege check bypass fix in the Windows Client-Side Caching kernel driver. `CscRebootRenamepOpenKey` previously called `ZwCreateKey`/`ZwOpenKey` with `OBJECT_ATTRIBUTES.Attributes = 0x240` (`OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE`), omitting `OBJ_FORCE_ACCESS_CHECK` (0x400). A low-privileged user could trigger this via `IOfflineFilesCache::RenameItem` → `IRP_MJ_FILE_SYSTEM_CONTROL` → `CscRebootRenameAddEntry` → `CscRebootRenamepOpenKey`, then use a registry symbolic link to redirect the SYSTEM-level registry write to an arbitrary protected path (e.g., `HKLM\SYSTEM\CurrentControlSet\Services\<attacker_service>`).

**Patch:** `CscRebootRenamepOpenKey` grew from 162 → 406 bytes (+244 bytes). A feature flag check was added at function entry. When the flag is enabled, a new explicit caller access-check helper validates the calling thread's security context before `ZwCreateKey`/`ZwOpenKey` is invoked; if the check fails (NTSTATUS < 0), the function returns an error immediately without performing the registry operation. The feature flag uses Microsoft's Windows Feature Experimentation (Velocity) infrastructure for kill-switch capability — the same gradual-rollout pattern as CVE-2025-62455 (`mqac.sys`). Unlike that patch, the fix here adds an explicit pre-call privilege check rather than `OBJ_FORCE_ACCESS_CHECK`.

Reporter: **T0** ([@t0zhang](https://x.com/t0zhang)). Architecturally similar to **CVE-2022-35820** (Windows Bluetooth `bthport.sys` EoP via registry symlink).

---

## Statistics

| Metric | Value |
|---|---|
| Total binary size delta | +8 bytes |
| Primary changed function | `CscRebootRenamepOpenKey` |
| Pre-patch function size | 162 bytes |
| Post-patch function size | 406 bytes (+244 bytes) |
| New helper functions | 2 (FeatureFlagEvaluator, PrivilegeCheckHelper) |

---

## Functions Changed (Code)

### 1. `CscRebootRenamepOpenKey` — **PRIMARY FIX**

| Field | Pre-patch | Post-patch |
|---|---|---|
| Address | `0x140050870` | `0x140050974` |
| Length | 162 bytes | 406 bytes |
| Delta | +244 bytes | |

**Pre-patch (vulnerable) decompiled C — from MSRC submission:**

```c
NTSTATUS __fastcall CscRebootRenamepOpenKey(
    void **a1,              // output: key handle
    UNICODE_STRING *a2,     // registry path
    BOOL a3,                // create if not present
    BYTE *a4)               // disposition output
{
    OBJECT_ATTRIBUTES ObjectAttributes;

    *a1 = NULL;
    ObjectAttributes.RootDirectory = NULL;
    ObjectAttributes.ObjectName    = a2;
    ObjectAttributes.Length        = 0x30;
    ObjectAttributes.Attributes    = 576;   // 0x240 = OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE
                                            // ← Missing OBJ_FORCE_ACCESS_CHECK (0x400)
    ObjectAttributes.SecurityDescriptor       = NULL;
    ObjectAttributes.SecurityQualityOfService = NULL;

    if (!a3)
        return ZwOpenKey(a1, 0xF003F, &ObjectAttributes);

    return ZwCreateKey(a1, 0xF003F, &ObjectAttributes, 0, NULL, 0, &Disposition);
}
```

**Pre-patch disassembly (capstone, VA `0x140050870`):**

```asm
; CscRebootRenamepOpenKey — pre-patch (162 bytes)
140050870: push rbp
140050875: sub  rsp, 0x70
14005088b: and  dword ptr [rbp-0x14], eax       ; clear local fields
140050891: test r9, r9                           ; a4 disposition ptr
140050896: mov  byte ptr [r9], 1                 ; set initial disposition
1400508a1: test r8b, r8b                         ; a3: create flag
1400508ac: mov  dword ptr [rbp-0x30], 0x30      ; ObjectAttributes.Length
1400508b3: mov  dword ptr [rbp-0x18], 0x240     ; ObjectAttributes.Attributes = 576 ← VULN
1400508ba: mov  edx, 0xf003f                     ; DesiredAccess = KEY_ALL_ACCESS
1400508bf: movdqu xmmword ptr [rbp-0x10], xmm0  ; zero SecurityDescriptor + SQoS
1400508c4: je   0x1400508f9                      ; a3==0 → ZwOpenKey
             ; ZwCreateKey path:
1400508dd: call qword ptr [rip - 0xd594]         ; ZwCreateKey(a1, KEY_ALL_ACCESS, &OA, ...)
             ; ZwOpenKey path (a3==0):
1400508f9: call qword ptr [rip - 0xd708]         ; ZwOpenKey(a1, KEY_ALL_ACCESS, &OA)
140050912: ret
```

**Post-patch disassembly (capstone, VA `0x140050974`):**

```asm
; CscRebootRenamepOpenKey — post-patch (406 bytes)
140050974: push rbp, rdi, r12, r14, r15
140050989: sub  rsp, 0x70
           ; ... save parameters ...
1400509bb: call 0x1400220c4                      ; ← NEW: feature flag evaluator
1400509c0: test eax, eax
1400509c2: je   0x140050a95                      ; feature disabled → old code path

           ; --- feature ENABLED path ---
1400509c8: test r14b, r14b                       ; a3: create flag
1400509cb: je   0x140050a40                      ; → ZwOpenKey branch (no helper needed)
             ; create path: check caller privileges first
1400509d1: call 0x140050420                      ; ← NEW: caller access-check helper
1400509d6: test eax, eax
1400509d8: js   0x140050b02                      ; NTSTATUS < 0 → return error (skip ZwCreateKey)
             ; privilege check passed — proceed with registry op:
140050a0f: mov  dword ptr [rbp-0x18], 0x240      ; Attributes still 0x240 (no OBJ_FORCE_ACCESS_CHECK)
140050a16: mov  qword ptr [rbp-0x20], r15        ; ObjectName
140050a22: call ZwCreateKey

           ; ZwOpenKey path (a3==0, feature enabled):
140050a40: mov  dword ptr [rbp-0x18], 0x240      ; Attributes 0x240
140050a6a: call ZwOpenKey

           ; --- feature DISABLED (kill-switch) path at 0x140050a95 ---
140050a95: mov  dword ptr [rbp-0x30], 0x30      ; ObjectAttributes.Length
140050a9c: xorps xmm0, xmm0
140050aa7: mov  dword ptr [rbp-0x18], 0x240      ; Attributes = 0x240 (old behavior)
140050aae: mov  edx, 0xf003f
140050ada: call ZwCreateKey                      ; no privilege check (old behavior)
140050af6: call ZwOpenKey                        ; no privilege check (old behavior)
```

**`OBJECT_ATTRIBUTES.Attributes` flag breakdown:**

| Flag | Value | Pre-patch | Post-patch (feature on) | Post-patch (feature off) |
|---|---|---|---|---|
| `OBJ_CASE_INSENSITIVE` | 0x040 | ✓ | ✓ | ✓ |
| `OBJ_KERNEL_HANDLE` | 0x200 | ✓ | ✓ | ✓ |
| `OBJ_FORCE_ACCESS_CHECK` | 0x400 | — | — (explicit check instead) | — |

**Note on fix approach:** Microsoft chose explicit pre-call privilege checking rather than adding `OBJ_FORCE_ACCESS_CHECK` to the `Attributes` field. Both approaches address the root cause (missing caller identity validation), but the explicit check allows finer-grained control over error handling and rollback via the feature flag.

---

### 2. `FeatureFlagEvaluator` — **NEW (41 bytes)**

| Field | Pre-patch | Post-patch |
|---|---|---|
| Address | — | `0x1400220c4` |
| Length | — | 41 bytes |

```asm
; Feature flag evaluator — post-patch only
1400220c4: sub  rsp, 0x28
1400220c8: and  qword ptr [rsp+0x30], 0
1400220ce: mov  eax, dword ptr [rip + 0x1b77c]   ; read g_FeatureState (.data RVA 0x3d850)
1400220d4: mov  dword ptr [rsp+0x30], eax
1400220d8: test al, 0x10                          ; initialized bit
1400220da: je   0x1400220e1
1400220dc: and  eax, 1                            ; return enabled flag
1400220df: jmp  0x1400220f0
1400220e1: mov  rcx, qword ptr [rsp+0x30]
1400220e6: mov  edx, 3
1400220eb: call 0x1400220fc                       ; compute feature state
1400220f0: add  rsp, 0x28
1400220f4: ret
; Return: 0 = feature disabled (old path), non-zero = feature enabled (privilege check path)
```

---

### 3. `PrivilegeCheckHelper` — **NEW (~0xe9 bytes)**

| Field | Pre-patch | Post-patch |
|---|---|---|
| Address | — | `0x140050420` |

```asm
; Privilege check helper — post-patch only
140050420: mov  qword ptr [rsp+8], rbx
140050425: ...
140050434: mov  rax, qword ptr [rip - 0xd253]    ; read kernel structure pointer
14005043b: mov  rsi, rcx                          ; output handle ptr
14005043e: and  qword ptr [rcx], 0               ; *out = NULL
140050442: mov  rdx, qword ptr [rax]
140050445: mov  rbp, qword ptr [rdx + 0x108]     ; navigate to security-relevant kernel object
14005044f: call qword ptr [rip - 0xd2fe]         ; ObReferenceObjectByPointer-class (token ref)
14005045b: mov  ecx, 0x100                        ; allocation size
140050460: mov  r8d, 0x52727343                   ; pool tag 'CsrR'
140050466: lea  edx, [rax + 0x28]                ; tag | priority
140050469: call qword ptr [rip - 0xd3e8]         ; ExAllocatePoolWithTag
14005047b: je   0x14005047d
14005047d: mov  ebx, 0xc000009a                   ; STATUS_INSUFFICIENT_RESOURCES on alloc fail
14005048c: call qword ptr [rip - 0xd2bb]         ; Object Manager access check
1400504a1: call qword ptr [rip - 0xd128]         ; cleanup / dereference
1400504ea: mov  qword ptr [rsi], rdi             ; store handle on success
140050503: mov  eax, ebx                          ; return NTSTATUS
140050519: ret
; Returns: NTSTATUS (< 0 = caller lacks privileges or resource error)
```

---

## Feature Flag Details

The feature state global at `.data` RVA `0x3d850` (VA `0x14003d850`) controls rollout:

- **Bit 0x10**: "initialized" flag — set after the feature state has been computed
- **Bit 0x01**: "enabled" flag — 1 = privilege check active, 0 = old behavior

This is Microsoft's Windows Feature Experimentation (Velocity) infrastructure, the same kill-switch gradual-deployment mechanism used in the CVE-2025-62455 (`mqac.sys`) patch. It allows Microsoft to remotely enable or disable the security check without shipping a new binary.

---

## Comparison with CVE-2025-62455 (mqac.sys)

| Aspect | CVE-2025-62455 (mqac.sys) | CVE-2025-60705 (csc.sys) |
|---|---|---|
| Root cause | ZwCreateFile without OBJ_FORCE_ACCESS_CHECK | ZwCreateKey/ZwOpenKey without OBJ_FORCE_ACCESS_CHECK |
| Reporter | T0 (@t0zhang) | T0 (@t0zhang) |
| Fix mechanism | Add OBJ_FORCE_ACCESS_CHECK to Attributes (0x240 → 0x640) | Add explicit pre-call privilege check helper |
| Feature flag | Yes (EvaluateCurrentState) | Yes (same pattern) |
| Kill-switch | Yes | Yes |
| Affected path | MSMQ storage file creation | CSC reboot-rename registry write |

---

## Binaries

| Role | File | SHA-256 | MSDL URL |
|---|---|---|---|
| Pre-patch | `csc-2025-09.sys` | `b80320fb15aa881239b04d030d069c2671b960b579817fc7e3314a1c2015ac88` | `https://msdl.microsoft.com/download/symbols/csc.sys/DBAAEA5199000/csc.sys` |
| Post-patch | `csc-2025-11.sys` | `4bf3f8ed10649d98f7f8d945bfae8c88988811353c41f4b806e2fa047a16226c` | `https://msdl.microsoft.com/download/symbols/csc.sys/2E0C317599000/csc.sys` |
