1. Overview

  • Binaries: csc_unpatched.syscsc_patched.sys
  • Driver: csc.sys (Windows Offline Files / Client Side Caching)
  • Overall similarity: 0.9916 (7 functions changed of 1432 matched)
  • Diff stats: 1432 matched / 7 changed / 1425 identical / 0 unmatched (either direction)

Verdict: The patch removes a WIL feature-staging gate in the registry-key-creation helper CscRebootRenamepOpenKey (sub_1C004785C) so that an owner-only security descriptor naming LocalSystem as the key owner is always attached when ZwCreateKey is invoked for the reboot-rename tracking keys under HKLM\System\CurrentControlSet\Services\CSC\Parameters (and its RebootRename subkey). Without the patch, whether the security descriptor was attached depended on the feature-staging flag; when the flag was off, ZwCreateKey was called with SecurityDescriptor = NULL, so the new key's owner defaulted to the creating (SYSTEM) context and its DACL was inherited from the parent. The security descriptor built here sets only the owner SID (no DACL is ever constructed), so the delivered change makes the key's owner explicit and deterministic. The DACL — i.e. who may read or write the key — is inherited from the parent container in both builds and is not altered by the patch. This is a delivered defense-in-depth ownership hardening; no exploitable weak-ACL primitive is demonstrable from the binaries.


2. Vulnerability Summary

Finding 1: Owner security descriptor on CSC Parameters registry keys gated behind a feature-staging flag (CWE-732)

  • Severity: Low (defense-in-depth ownership hardening; no demonstrable exploitable primitive)
  • Class: Incorrect Permission Assignment for Critical Resource (CWE-732), specifically owner-SID assignment on a created registry key
  • Affected function: CscRebootRenamepOpenKey (sub_1C004785C) (registry key create/open helper)
  • Root cause: The unpatched helper decides whether to attach a security descriptor when creating a registry key based on the return value of Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4), a WIL feature-staging (velocity/rollout) check reading Feature_2858196282__private_featureState (data_1C003A080). When that check returns false, the helper skips the SD builder (CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)) and invokes ZwCreateKey with OBJECT_ATTRIBUTES.SecurityDescriptor = NULL; when it returns true, the SD is attached on create. The SD produced by CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0) is an absolute descriptor that sets only the owner to SeLocalSystemSid (via RtlCreateSecurityDescriptor + RtlSetOwnerSecurityDescriptor); it never constructs a DACL. Because no DACL is set in either the NULL-SD path or the SD path, the created key's DACL is governed by parent-container inheritance in both cases. The only behavioral difference is whether the key's owner is set explicitly to LocalSystem or defaults to the creating context.
  • Assessed impact: Marginal. The CSC reboot-rename keys are created by the driver in the SYSTEM/kernel context, so in the NULL-SD path the default owner is already an admin-equivalent principal (SYSTEM/Administrators), and the DACL — which governs who can read/write the key — is identical (inherited from the parent) in both builds. Under HKLM\System\CurrentControlSet\Services, the inherited DACL does not grant write access to non-admin principals on a default install. No non-admin write, denial-of-service, or privilege-escalation primitive is demonstrable from the binaries; the change makes key ownership explicit and deterministic rather than closing a reachable authorization gap.
  • Patch: The Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) feature-staging call is removed entirely. The branch is now driven only by arg3 (r8b, the create flag). When arg3 != 0, CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0) is unconditionally invoked, and ZwCreateKey always receives the owner-only (LocalSystem) SD. When arg3 == 0, ZwOpenKey is used (no SD needed for open).

Entry point and call chain:

  1. CSC driver operation queues a file rename to be applied at next boot (offline-files reboot-rename tracking).
  2. CscDclMRxRebootRenameAdd (sub_1C000C964) — dispatch helper.
  3. CscRebootRenameAddEntry (sub_1C006F0F4) — calls CscRebootRenamepOpenKey (sub_1C004785C)(&Handle, &path, 1, nullptr) with path = \Registry\Machine\System\CurrentControlSet\Services\CSC\Parameters\RebootRename.
  4. (Sibling caller) CscRebootRenamepAddToKey (sub_1C006F42C) — opens/creates the RebootRename tracking key and writes indexed rename entries (a LastIndexValue value) via CscRebootRenamepOpenKey (sub_1C004785C)(&Handle, arg1, 1, &var_f8).
  5. CscRebootRenamepOpenKey (sub_1C004785C) — key create/open helper.
  6. Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) — feature-flag gate returning the state of Feature_2858196282__private_featureState (data_1C003A080).
  7. ZwCreateKey with SecurityDescriptor = NULL (when flag is false) → key inherits parent ACL.

3. Pseudocode Diff

// ===================== UNPATCHED CscRebootRenamepOpenKey (sub_1C004785C) =====================
NTSTATUS CscRebootRenamepOpenKey (sub_1C004785C)(PHANDLE Handle, PUNICODE_STRING Path,
                       BOOLEAN Create, PULONG Disposition)
{
    OBJECT_ATTRIBUTES oa;
    *Handle = 0;

    if (Disposition) *Disposition = 1;        // (cosmetic side channel)

    // *** feature-staging gate ***
    if (Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4)) {                    // Feature_2858196282__private_featureState (data_1C003A080) feature state
        if (Create) {
            PSECURITY_DESCRIPTOR sd;
            if (CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)(&sd) < 0) return status;
            InitializeObjectAttributes(&oa, Path, OBJ_CASE_INSENSITIVE,
                                       NULL, sd);     // SD = LocalSystem-owner
            status = ZwCreateKey(Handle, KEY_ALL_ACCESS, &oa, 0,
                                 NULL, 0, Disposition);
            ExFreePoolWithTag(sd, 'CrsR');
        } else {
            InitializeObjectAttributes(&oa, Path, OBJ_CASE_INSENSITIVE,
                                       NULL, NULL);
            status = ZwOpenKey(Handle, KEY_ALL_ACCESS, &oa);
        }
    } else {
        // *** NO-SD PATH (owner defaults to creating context) ***
        InitializeObjectAttributes(&oa, Path, OBJ_CASE_INSENSITIVE,
                                   NULL, NULL);          // <-- SD = NULL!
        if (Create)
            status = ZwCreateKey(Handle, KEY_ALL_ACCESS, &oa, 0,
                                 NULL, 0, Disposition); // inherits parent ACL
        else
            status = ZwOpenKey(Handle, KEY_ALL_ACCESS, &oa);
    }
    return status;
}

// ===================== PATCHED CscRebootRenamepOpenKey (sub_1C004785C) =======================
NTSTATUS CscRebootRenamepOpenKey (sub_1C004785C)(PHANDLE Handle, PUNICODE_STRING Path,
                       BOOLEAN Create, PULONG Disposition)
{
    OBJECT_ATTRIBUTES oa;
    *Handle = 0;
    if (Disposition) *Disposition = 1;

    // Feature-flag call REMOVED. Decision is now purely on Create.
    if (Create) {
        PSECURITY_DESCRIPTOR sd;
        if (CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)(&sd) < 0) return status;       // ALWAYS build SD
        InitializeObjectAttributes(&oa, Path, OBJ_CASE_INSENSITIVE,
                                   NULL, sd);
        status = ZwCreateKey(Handle, KEY_ALL_ACCESS, &oa, 0,
                             NULL, 0, Disposition);      // always WITH SD
        ExFreePoolWithTag(sd, 'CrsR');
    } else {
        InitializeObjectAttributes(&oa, Path, OBJ_CASE_INSENSITIVE,
                                   NULL, NULL);
        status = ZwOpenKey(Handle, KEY_ALL_ACCESS, &oa);
    }
    return status;
}

Key contrast: - Unpatched line if (Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4)) → decides SD application. - Unpatched NO-SD block: InitializeObjectAttributes(..., NULL); ZwCreateKey(...) ← flaw. - Patched: Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) call removed; SD always built when Create == TRUE.

SD builder reference (CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0))

This function is identical in both builds. It sets only the owner SID; it never constructs a DACL or SACL. The descriptor is an absolute (not self-relative) SECURITY_DESCRIPTOR.

NTSTATUS CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)(PSECURITY_DESCRIPTOR *Out)
{
    *Out = 0;
    PSID sys = *(PSID *)(*SeExports + 0x108);   // SeLocalSystemSid
    ULONG len = RtlLengthSid(sys) + 0x28;
    PSECURITY_DESCRIPTOR sd = ExAllocatePool2(POOL_FLAG_NON_PAGED, len, 'CrsR');
    if (!sd) return STATUS_INSUFFICIENT_RESOURCES;
    RtlCreateSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION);   // absolute SD, DACL absent
    PSID owner = (PSID)((PUCHAR)sd + RtlLengthSecurityDescriptor(sd));
    memmove(owner, sys, RtlLengthSid(sys));
    RtlSetOwnerSecurityDescriptor(sd, owner, FALSE);   // OWNER only; no DACL is set
    *Out = sd;
    return STATUS_SUCCESS;
}

Because the descriptor carries no DACL, attaching it to ZwCreateKey fixes the new key's owner to LocalSystem but leaves the key's DACL to parent inheritance — the same DACL the key would receive on the NULL-SD path.


4. Assembly Analysis

Full unpatched CscRebootRenamepOpenKey (sub_1C004785C) (annotated)

CscRebootRenamepOpenKey (sub_1C004785C):
0x1c004785c: mov     qword [rsp+0x10], rbx
0x1c0047861: mov     qword [rsp+0x18], rsi
0x1c0047866: push    rbp
0x1c0047867: push    rdi
0x1c0047868: push    r12
0x1c004786a: push    r14
0x1c004786c: push    r15
0x1c004786e: mov     rbp, rsp
0x1c0047871: sub     rsp, 0x70
0x1c0047875: xor     r12d, r12d          ; r12 = 0 (constant zero)
0x1c0047878: mov     rbx, r9             ; rbx = arg4 (Disposition*)
0x1c004787b: mov     dword [rbp-0x2c], r12d
0x1c004787f: mov     r14b, r8b           ; r14b = arg3 (Create flag)
0x1c0047882: mov     dword [rbp-0x14], r12d
0x1c0047886: mov     r15, rdx            ; r15 = arg2 (UNICODE_STRING* path)
0x1c0047889: mov     dword [rbp+0x30], r12d
0x1c004788d: mov     rsi, rcx            ; rsi = arg1 (Handle*)
0x1c0047890: mov     qword [rbp+0x48], r12
0x1c0047894: mov     edi, r12d
0x1c0047897: mov     qword [rcx], r12
0x1c004789a: test    r9, r9
0x1c004789d: je      0x1c00478a3
0x1c004789f: mov     byte [r9], 0x1      ; *Disposition = 1 default

; *** ROOT-CAUSE: FEATURE-FLAG GATE ***
0x1c00478a3: call    0x1c00088b4         ; returns Feature_2858196282__private_featureState (data_1C003A080) state
0x1c00478a8: test    eax, eax
0x1c00478aa: je      0x1c004797d         ; false -> NO-SD path

; --- Safe path: SD built and applied ---
0x1c00478b0: test    r14b, r14b          ; Create?
0x1c00478b3: je      0x1c0047928
0x1c00478b5: lea     rcx, [rbp+0x48]
0x1c00478b9: call    0x1c00454b0         ; build LocalSystem-owner SD
0x1c00478be: test    eax, eax
0x1c00478c0: js      0x1c00479ea
0x1c00478c6: mov     rdi, qword [rbp+0x48]  ; rdi = SD
0x1c00478ca: lea     rax, [rbp+0x30]
0x1c00478ce: mov     qword [rsp+0x30], rax
0x1c00478d3: lea     r8, [rbp-0x30]      ; r8 = OBJECT_ATTRIBUTES*
0x1c00478d7: mov     dword [rsp+0x28], r12d
0x1c00478dc: xor     r9d, r9d
0x1c00478df: mov     edx, 0xf003f        ; KEY_ALL_ACCESS
0x1c00478e4: mov     qword [rsp+0x20], r12
0x1c00478e9: mov     rcx, rsi            ; rcx = Handle*
0x1c00478ec: mov     dword [rbp-0x30], 0x30      ; OA.Length = 0x30
0x1c00478f3: mov     qword [rbp-0x28], r12       ; OA.RootDirectory = 0
0x1c00478f7: mov     dword [rbp-0x18], 0x240     ; OA.Attributes = OBJ_CASE_INSENSITIVE|...
0x1c00478fe: mov     qword [rbp-0x20], r15       ; OA.ObjectName = path
0x1c0047902: mov     qword [rbp-0x10], rdi       ; OA.SecurityDescriptor = SD  <-- safe
0x1c0047906: mov     qword [rbp-0x8], r12        ; OA.SecurityQoS = 0
0x1c004790a: call    qword [rel 0x1c0041338]     ; ZwCreateKey (WITH SD)
0x1c0047916: cmp     dword [rbp+0x30], 0x1
0x1c004791a: mov     esi, eax
0x1c004791c: jne     0x1c0047960
0x1c004791e: test    rbx, rbx
0x1c0047921: je      0x1c0047960
0x1c0047923: mov     byte [rbx], r12b
0x1c0047926: jmp     0x1c0047960

; *** NO-SD PATH (owner defaults to creating context) (only reached when feature flag is false) ***
0x1c004797d: mov     dword [rbp-0x30], 0x30      ; OA.Length
0x1c0047984: xorps   xmm0, xmm0                  ; xmm0 = 0
0x1c0047987: mov     qword [rbp-0x28], r12       ; OA.RootDirectory = 0
0x1c004798b: lea     r8, [rbp-0x30]              ; r8 = OBJECT_ATTRIBUTES*
0x1c004798f: mov     dword [rbp-0x18], 0x240     ; OA.Attributes
0x1c0047996: mov     edx, 0xf003f                ; KEY_ALL_ACCESS
0x1c004799b: mov     qword [rbp-0x20], r15       ; OA.ObjectName = path
0x1c004799f: mov     rcx, rsi                    ; rcx = Handle*
0x1c00479a2: movdqu  xmmword [rbp-0x10], xmm0    ; OA.SD=0 AND OA.QoS=0 (NULL)
0x1c00479a7: test    r14b, r14b                  ; Create?
0x1c00479aa: je      0x1c00479de
0x1c00479ac: lea     rax, [rbp+0x30]
0x1c00479b0: xor     r9d, r9d
0x1c00479b3: mov     qword [rsp+0x30], rax
0x1c00479b8: mov     dword [rsp+0x28], r12d
0x1c00479bd: mov     qword [rsp+0x20], r12
0x1c00479c2: call    qword [rel 0x1c0041338]     ; ZwCreateKey (SecurityDescriptor = NULL)
0x1c00479ce: cmp     dword [rbp+0x30], 0x1
0x1c00479d2: jne     0x1c00479ea
0x1c00479d4: test    rbx, rbx
0x1c00479d7: je      0x1c00479ea
0x1c00479d9: mov     byte [rbx], r12b
0x1c00479dc: jmp     0x1c00479ea
0x1c00479de: call    qword [rel 0x1c00411e0]     ; ZwOpenKey (no SD needed)

; --- Epilogue ---
0x1c0047960: test    rdi, rdi
0x1c0047963: je      0x1c0047979
0x1c0047965: mov     edx, 0x52727343              ; 'CrsR' tag
0x1c004796a: mov     rcx, rdi
0x1c004796d: call    qword [rel 0x1c00410c0]      ; ExFreePoolWithTag
0x1c0047979: mov     eax, esi
0x1c004797b: jmp     0x1c00479ea
0x1c00479ea: lea     r11, [rsp+0x70]
0x1c00479ef: mov     rbx, qword [r11+0x38]
0x1c00479f3: mov     rsi, qword [r11+0x40]
0x1c00479f7: mov     rsp, r11
0x1c00479fa: pop     r15
0x1c00479fc: pop     r14
0x1c00479fe: pop     r12
0x1c0047a00: pop     rdi
0x1c0047a01: pop     rbp
0x1c0047a02: retn

Patched counterpart (key blocks)

; --- Patched CscRebootRenamepOpenKey (sub_1C004785C): gate removed; SD always built on create ---
0x1c0047898: test    r8b, r8b           ; arg3 (Create flag) — only decision now
0x1c004789b: je      0x1c0047912        ; !Create -> ZwOpenKey path
0x1c004789d: lea     rcx, [rbp+0x20]
0x1c00478a1: call    0x1c00454b0        ; ALWAYS build SD on create
0x1c00478a6: test    eax, eax
0x1c00478a8: js      0x1c0047962
0x1c00478b6: mov     rdi, qword [rbp+0x20]   ; rdi = SD
...
0x1c00478f0: mov     qword [rbp-0x10], rdi   ; OA.SecurityDescriptor = SD
0x1c00478f4: call    qword [rel 0x1c0041338] ; ZwCreateKey (WITH SD)

Instruction-level diff highlights

Offset (unpatched) Instruction Significance
0x1c00478a3 call 0x1c00088b4 Removed in patch — the feature-flag gate
0x1c00478aa je 0x1c004797d Branch to NO-SD path (removed)
0x1c004797d0x1c00479a2 OA setup + movdqu [rbp-0x10], xmm0 Zeroes OA.SecurityDescriptor (NULL SD)
0x1c00479c2 call [rel 0x1c0041338] ZwCreateKey with SecurityDescriptor = NULL (owner defaults; DACL inherited)
0x1c00478b9 (safe) call 0x1c00454b0 SD builder (only on safe path unpatched; always on create patched)
0x1c0047902 (safe) mov [rbp-0x10], rdi Sets OA.SecurityDescriptor on safe path
0x1c00454d5 (SD builder) mov rbp, [rdx+0x108] Reads SeExports+0x108 = SeLocalSystemSid

5. Trigger Conditions

  1. Session: A locally logged-on user (any privilege level that can invoke Offline Files / CSC operations) on a system running the unpatched csc.sys.
  2. Flag state: The feature-usage flag at Feature_2858196282__private_featureState (data_1C003A080) must be in the state where Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) returns false (bit 0x1 of the resolved state is clear). This is the default/initial state until telemetry inflight populates it.
  3. Driver operation triggering key creation: Perform an action that causes the CSC driver to initialize or create keys under HKLM\System\CurrentControlSet\Services\CSC\Parameters (or a subkey such as a parameter-index subkey written by CscRebootRenamepAddToKey (sub_1C006F42C)). This is normally reached during:
  4. First-time enabling of Offline Files,
  5. Cache initialization on a fresh boot,
  6. Reconfiguration of the cache location / size.
  7. Key absent: The target key must not already exist (otherwise ZwCreateKey returns the existing handle and the existing ACL is unchanged).
  8. Required values: Both callers invoke the helper with arg3 (Create) = 1, arg4 (Disposition*) either nullptr (CscRebootRenameAddEntry (sub_1C006F0F4)) or &var_f8 (CscRebootRenamepAddToKey (sub_1C006F42C)). No special data payload is needed — the difference is purely structural.
  9. Race / ordering: None intrinsic; the flag state must be false at the moment of the create call. In practice this is satisfied on a default install before the feature telemetry flips it.
  10. Observable difference: After key creation, read the key's security descriptor (e.g. RegGetKeySecurity / accesschk.exe -k HKLM\System\CurrentControlSet\Services\CSC\Parameters\<subkey>). On the unpatched binary with the feature gate off, the key's owner is whatever the creating (SYSTEM) context defaults to and its DACL is inherited from the parent. On the patched binary the key's owner is explicitly NT AUTHORITY\SYSTEM (LocalSystem); its DACL is still the inherited one (the SD carries no DACL). The observable delta is the explicit owner SID, not the access-control (DACL) contents.

6. Exploit Primitive & Development Notes

  • Primitive: None demonstrable. The change is a defense-in-depth ownership hardening: the newly created CSC reboot-rename keys now always get an owner-only security descriptor naming LocalSystem, instead of relying on the default owner of the creating (SYSTEM) context. No attacker-controlled input, no memory-safety issue, and no authorization boundary crossing is present or reachable in either build.
  • Why there is no reachable weak-ACL primitive:
  • The security descriptor built by CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0) sets only the owner; it does not set a DACL. The DACL that governs who can read or write the key is inherited from the parent container HKLM\System\CurrentControlSet\Services\CSC\Parameters in both builds and is unchanged by the patch.
  • The keys are created by the driver in the SYSTEM/kernel context, so even on the NULL-SD path the default owner is an admin-equivalent principal.
  • On a default Windows install, the parent ...\Services container does not grant write access to non-admin principals, so the created key is not writable by standard users on either binary.
  • What the change does provide: deterministic, explicit ownership of the created key by LocalSystem, removing dependence on a feature-staging flag for a security-descriptor decision. This is a robustness/hardening improvement rather than the closure of an exploitable flaw.
  • Mitigations / exploitability context:
  • KASLR / SMEP / SMAP / HVCI / CFG: Not relevant — there is no memory-corruption primitive here.
  • Driver signature enforcement / Secure Launch: Not relevant to this change.
  • Real-world restrictions: Because the DACL is unchanged and the owner is admin-equivalent in both builds, there is no standard-user-reachable tampering vector introduced or removed by this patch.

7. Debugger PoC Playbook

This playbook is for a WinDbg/KD session attached to the unpatched csc.sys.

Breakpoints (ready-to-paste)

bp csc_unpatched!CscRebootRenamepOpenKey (sub_1C004785C)
bp csc_unpatched+0x78a3     ; call Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) — THE removed feature-flag gate
bp csc_unpatched+0x797d     ; NO-SD OA setup block (only on the NULL-SD path)
bp csc_unpatched!CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)   ; SD builder (LocalSystem-owner)
bp csc_unpatched!CscRebootRenameAddEntry (sub_1C006F0F4)   ; caller that creates CSC Parameters key
bp csc_unpatched!CscRebootRenamepAddToKey (sub_1C006F42C)   ; caller that creates parameter subkeys

If symbols are stripped, use the raw base-relative offsets (apply the loaded module base):

bp csc_unpatched+0x4785C
bp csc_unpatched+0x478A3
bp csc_unpatched+0x4797D
bp csc_unpatched+0x454B0
bp csc_unpatched+0x6F0F4
bp csc_unpatched+0x6F42C

Optionally set a hardware/conditional breakpoint on the actual ZwCreateKey invocation:

bp csc_unpatched+0x479C2    ; ZwCreateKey with NULL SD — confirm r8+0x28 == 0

What to inspect at each breakpoint

  • At CscRebootRenamepOpenKey (sub_1C004785C) entry:
  • rcx = Handle* (output)
  • rdx = UNICODE_STRING* path → du poi(@rdx+0x8) to print the path string
  • r8b = Create flag (must be 1 for the dangerous case)
  • r9 = optional Disposition* (may be NULL)
  • At +0x78A3 (feature-flag call): step over with p, then read eax.
  • eax == 0 → the NULL-SD path will be taken
  • eax != 0 → safe SD path
  • At +0x797D: dump the OBJECT_ATTRIBUTES about to be passed to ZwCreateKey:
  • dt _OBJECT_ATTRIBUTES @rbp-0x30
  • Confirm SecurityDescriptor (@rbp-0x10) is 0 and SecurityQualityOfService (@rbp-0x8) is 0
  • At +0x479C2 (ZwCreateKey call): r8 = OBJECT_ATTRIBUTES* → check @@c++(@r8->SecurityDescriptor). NULL confirms the NULL-SD path.
  • At CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0) return: [@rbp+0x48] (or [@rbp+0x20] patched) holds the freshly allocated SD pointer. Step into RtlSetOwnerSecurityDescriptor to confirm the owner SID is S-1-5-18 (LocalSystem).

Key instructions / offsets

  • 0x1C00478A3call Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) (removed in patch; root cause).
  • 0x1C00478AAje 0x1C004797D — branch to NO-SD path.
  • 0x1C00479A2movdqu [rbp-0x10], xmm0 — zeroes OA.SecurityDescriptor on the NULL-SD path.
  • 0x1C00479C2call qword [rel 0x1C0041338]ZwCreateKey with NULL SD.
  • 0x1C00478B9call 0x1C00454B0 — SD builder (safe path unpatched / always-on patched).
  • 0x1C0047902mov [rbp-0x10], rdi — sets OA.SecurityDescriptor on safe path.
  • 0x1C00454D5mov rbp, [rdx+0x108] — reads SeExports+0x108 (SeLocalSystemSid).

IAT slots referenced: - 0x1C00411E0 = ZwOpenKey - 0x1C0041338 = ZwCreateKey - 0x1C00410C0 = ExFreePoolWithTag - 0x1C0041370 = ExAllocatePool2

Trigger setup from user mode

  1. Load the unpatched csc.sys on a fresh test VM.
  2. Pre-clean the target key (delete if present) so ZwCreateKey will create it:
  3. HKLM\System\CurrentControlSet\Services\CSC\Parameters or a known subkey written by CscRebootRenamepAddToKey (sub_1C006F42C) (look for the LastIndexValue value).
  4. Force a parameter-initialization path:
  5. Re-enable Offline Files via vssadmin / PowerShell Enable-WindowsOptionalFeature, or
  6. Trigger Offline Files cache initialization (boot with cache enabled), or
  7. Invoke a CSC-related WMI / service-control path that forces parameter re-read.
  8. With the debugger attached, the breakpoint at +0x78A3 should fire on the create path. Force the NULL-SD path if the flag is already set:
  9. At the breakpoint: r eax=0; r eip=csc_unpatched+0x478AA; then g.
  10. Or patch the flag global directly: ed csc_unpatched+0x3A080 0 to force Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) to return false on subsequent calls.
  11. After ZwCreateKey returns, inspect the resulting key from a non-admin token:
  12. accesschk.exe -accepteula -k HKLM\System\CurrentControlSet\Services\CSC\Parameters
  13. Or from kernel debugger: !reg openkeys / !reg keyinfo <hive> <path>.

Expected observation of the behavioral difference

  • At +0x479C2 (unpatched, gate off), [r8+0x28] (OA.SecurityDescriptor) is NULL, so ZwCreateKey creates the key with a default (SYSTEM-context) owner and a parent-inherited DACL.
  • On the patched binary, [r8+0x28] is a non-NULL pointer to an ExAllocatePool2'd absolute SD; !sd <ptr> shows S-1-5-18 (LocalSystem) as owner, with no DACL present in the descriptor.
  • The created key's DACL is the parent-inherited one in both builds. On a default install HKLM\System\CurrentControlSet\Services does not grant write access to non-admin principals, so the key is not standard-user-writable on either binary. The single observable delta is the explicit owner SID on the patched build.

Struct / offset notes

OBJECT_ATTRIBUTES layout (size 0x30): - +0x00 Length (0x30) - +0x08 RootDirectory - +0x10 ObjectName (PUNICODE_STRING) - +0x18 Attributes (0x240 = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE) - +0x20 SecurityDescriptor ← this is what gets NULLed - +0x28 SecurityQualityOfService

In CscRebootRenamepOpenKey (sub_1C004785C), the OA is built on [rbp-0x30], so SecurityDescriptor lives at [rbp-0x10] (matches the movdqu [rbp-0x10], xmm0 zeroing instruction).

SeExports+0x108 = SeLocalSystemSid (SID = S-1-5-18).


8. Changed Functions — Full Triage

Security-relevant

  • CscRebootRenamepOpenKey (sub_1C004785C) — similarity 0.7011 — behavioral, low-severity hardening. Removed the Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) feature-staging gate; the owner-only SD is now always built on key create. This is the only security-relevant change, and its impact is limited to making the created key's owner explicit (the SD carries no DACL, so access control is unchanged).

Not security-relevant (WIL feature-staging / feature-usage refactor)

  • CscRebootRenamepEnumRegistry (sub_1C0047F68) — similarity 0.9912 — behavioral (minor). Removed Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) call before a ZwOpenKey read-only enumeration. Key is opened (not created) so no SD is required; change aligns with the surrounding telemetry rework.

  • Feature_4082332987__private_IsEnabledFallback (sub_1C0007AA8) — similarity 0.6736 — cosmetic. Telemetry wrapper switched from 3-arg wil_details_IsEnabledFallback (sub_1C0008248)(arg1, arg2, &data_1C00378F8) to 2-arg wil_details_IsEnabledFallback (sub_1C0008230)(arg1, arg2); the feature-context pointer is now embedded in the callee.

  • wil_details_IsEnabledFallback (sub_1C0008248) — similarity 0.9582 — cosmetic. Feature-usage dispatcher now reads hardcoded globals (data_1C00378C0 / Feature_2858196282__private_featureState (data_1C003A080)) instead of dereferencing caller-supplied arg3. Several call-target addresses shifted due to the surrounding refactor.

  • wil_details_FeatureStateCache_TryEnableDeviceUsageFastPath (sub_1C0008070) — similarity 0.8176 — cosmetic. Feature-state CAS helper now operates on global Feature_2858196282__private_featureState (data_1C003A080) instead of a caller-supplied pointer. Atomic lock cmpxchg logic unchanged.

  • wil_details_FeatureReporting_ReportUsageToServiceDirect (sub_1C0007E88) — similarity 0.9765 — cosmetic. RtlNotifyFeatureUsage caller now uses hardcoded values (feature ID 0x3818AA2, 0x1C002FA60, &data_1C003A0A0) instead of dereferencing caller context.

  • wil_details_FeatureReporting_ReportUsageToService (sub_1C0007E00) — similarity 0.9774 — cosmetic. Feature-notification wrapper now passes hardcoded globals instead of caller context pointer.

All six non-security changes are part of a coordinated refactor of the feature-usage/telemetry infrastructure (RtlNotifyFeatureUsage) that fed the flag used by the removed feature-staging gate. They are not independently security-relevant.


9. Unmatched Functions

None — no functions were added or removed between the two binaries (unmatched_unpatched: 0, unmatched_patched: 0). The patch was strictly an in-place modification of existing functions. There is no new sanitizer or removed mitigation to flag.


10. Confidence & Caveats

  • Confidence: High on the mechanics; the severity is deliberately conservative. The diff is small (7 functions), the removed instruction (call Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4) + je 0x1C004797D) and the always-on SD path in the patched build are unambiguous and fully traceable. The corrected key fact is that the SD builder (CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)) sets only the owner SID and never a DACL, so the patch changes the created key's owner but not its access-control (DACL) contents.

  • Why severity is Low rather than High:

  • The descriptor attached on the patched build carries no DACL; the key's DACL is parent-inherited in both builds, so no principal gains or loses access as a result of the patch.
  • The keys are created in the SYSTEM/kernel context, so even on the unpatched NULL-SD path the default owner is admin-equivalent (SYSTEM/Administrators).
  • On a default install, HKLM\System\CurrentControlSet\Services does not grant write access to non-admin principals, so no standard-user tampering vector exists on either binary.

  • Assumptions / limits of static analysis:

  • The default runtime state of Feature_2858196282__private_featureState (data_1C003A080) is not determinable from the static image; the patch guarantees the SD is applied on create regardless of that state, which is the point of the change.
  • The callers CscRebootRenameAddEntry (sub_1C006F0F4) and CscRebootRenamepAddToKey (sub_1C006F42C) invoke the helper with the create flag set (r8b = 1 at 0x1C006F19C and 0x1C006F538) for paths under \Registry\Machine\System\CurrentControlSet\Services\CSC\Parameters (and its RebootRename subkey). The exact user-mode trigger for these Offline Files operations is not established from the binaries alone.
  • The parent-container DACL is assumed to follow default Windows ...\Services semantics (no non-admin write). On a system where that container had been deliberately loosened, the inherited DACL — unchanged by this patch — would be the relevant factor, not the owner SID this change sets.