csc.sys — CSC reboot-rename registry keys could be created without an explicit owner security descriptor when a feature-staging gate was off (CWE-732), fixed
KB5075912
1. Overview
- Binaries:
csc_unpatched.sys↔csc_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 readingFeature_2858196282__private_featureState (data_1C003A080). When that check returns false, the helper skips the SD builder (CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)) and invokesZwCreateKeywithOBJECT_ATTRIBUTES.SecurityDescriptor = NULL; when it returns true, the SD is attached on create. The SD produced byCscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)is an absolute descriptor that sets only the owner toSeLocalSystemSid(viaRtlCreateSecurityDescriptor+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 byarg3(r8b, the create flag). Whenarg3 != 0,CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)is unconditionally invoked, andZwCreateKeyalways receives the owner-only (LocalSystem) SD. Whenarg3 == 0,ZwOpenKeyis used (no SD needed for open).
Entry point and call chain:
- CSC driver operation queues a file rename to be applied at next boot (offline-files reboot-rename tracking).
CscDclMRxRebootRenameAdd (sub_1C000C964)— dispatch helper.CscRebootRenameAddEntry (sub_1C006F0F4)— callsCscRebootRenamepOpenKey (sub_1C004785C)(&Handle, &path, 1, nullptr)with path =\Registry\Machine\System\CurrentControlSet\Services\CSC\Parameters\RebootRename.- (Sibling caller)
CscRebootRenamepAddToKey (sub_1C006F42C)— opens/creates theRebootRenametracking key and writes indexed rename entries (aLastIndexValuevalue) viaCscRebootRenamepOpenKey (sub_1C004785C)(&Handle, arg1, 1, &var_f8). CscRebootRenamepOpenKey (sub_1C004785C)— key create/open helper.Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4)— feature-flag gate returning the state ofFeature_2858196282__private_featureState (data_1C003A080).ZwCreateKeywithSecurityDescriptor = 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) |
0x1c004797d–0x1c00479a2 |
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
- Session: A locally logged-on user (any privilege level that can invoke Offline Files / CSC operations) on a system running the unpatched
csc.sys. - Flag state: The feature-usage flag at
Feature_2858196282__private_featureState (data_1C003A080)must be in the state whereFeature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4)returns false (bit0x1of the resolved state is clear). This is the default/initial state until telemetry inflight populates it. - 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 byCscRebootRenamepAddToKey (sub_1C006F42C)). This is normally reached during: - First-time enabling of Offline Files,
- Cache initialization on a fresh boot,
- Reconfiguration of the cache location / size.
- Key absent: The target key must not already exist (otherwise
ZwCreateKeyreturns the existing handle and the existing ACL is unchanged). - Required values: Both callers invoke the helper with
arg3 (Create) = 1,arg4 (Disposition*)eithernullptr(CscRebootRenameAddEntry (sub_1C006F0F4)) or&var_f8(CscRebootRenamepAddToKey (sub_1C006F42C)). No special data payload is needed — the difference is purely structural. - 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.
- 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 explicitlyNT 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 containerHKLM\System\CurrentControlSet\Services\CSC\Parametersin 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
...\Servicescontainer 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 stringr8b= Create flag (must be1for the dangerous case)r9= optional Disposition* (may be NULL)- At
+0x78A3(feature-flag call): step over withp, then readeax. eax == 0→ the NULL-SD path will be takeneax != 0→ safe SD path- At
+0x797D: dump theOBJECT_ATTRIBUTESabout to be passed toZwCreateKey: dt _OBJECT_ATTRIBUTES @rbp-0x30- Confirm
SecurityDescriptor (@rbp-0x10)is 0 andSecurityQualityOfService (@rbp-0x8)is 0 - At
+0x479C2(ZwCreateKey call):r8 = OBJECT_ATTRIBUTES*→ check@@c++(@r8->SecurityDescriptor).NULLconfirms the NULL-SD path. - At
CscRebootRenamepCreateSecurityDescriptor (sub_1C00454B0)return:[@rbp+0x48](or[@rbp+0x20]patched) holds the freshly allocated SD pointer. Step intoRtlSetOwnerSecurityDescriptorto confirm the owner SID isS-1-5-18(LocalSystem).
Key instructions / offsets
0x1C00478A3—call Feature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4)(removed in patch; root cause).0x1C00478AA—je 0x1C004797D— branch to NO-SD path.0x1C00479A2—movdqu [rbp-0x10], xmm0— zeroes OA.SecurityDescriptor on the NULL-SD path.0x1C00479C2—call qword [rel 0x1C0041338]—ZwCreateKeywith NULL SD.0x1C00478B9—call 0x1C00454B0— SD builder (safe path unpatched / always-on patched).0x1C0047902—mov [rbp-0x10], rdi— sets OA.SecurityDescriptor on safe path.0x1C00454D5—mov rbp, [rdx+0x108]— readsSeExports+0x108(SeLocalSystemSid).
IAT slots referenced:
- 0x1C00411E0 = ZwOpenKey
- 0x1C0041338 = ZwCreateKey
- 0x1C00410C0 = ExFreePoolWithTag
- 0x1C0041370 = ExAllocatePool2
Trigger setup from user mode
- Load the unpatched
csc.syson a fresh test VM. - Pre-clean the target key (delete if present) so
ZwCreateKeywill create it: HKLM\System\CurrentControlSet\Services\CSC\Parametersor a known subkey written byCscRebootRenamepAddToKey (sub_1C006F42C)(look for theLastIndexValuevalue).- Force a parameter-initialization path:
- Re-enable Offline Files via
vssadmin/ PowerShellEnable-WindowsOptionalFeature, or - Trigger Offline Files cache initialization (boot with cache enabled), or
- Invoke a CSC-related WMI / service-control path that forces parameter re-read.
- With the debugger attached, the breakpoint at
+0x78A3should fire on the create path. Force the NULL-SD path if the flag is already set: - At the breakpoint:
r eax=0; r eip=csc_unpatched+0x478AA;theng. - Or patch the flag global directly:
ed csc_unpatched+0x3A080 0to forceFeature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4)to return false on subsequent calls. - After
ZwCreateKeyreturns, inspect the resulting key from a non-admin token: accesschk.exe -accepteula -k HKLM\System\CurrentControlSet\Services\CSC\Parameters- 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) isNULL, soZwCreateKeycreates 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 anExAllocatePool2'd absolute SD;!sd <ptr>showsS-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\Servicesdoes 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 theFeature_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). RemovedFeature_2858196282__private_IsEnabledDeviceUsage (sub_1C00088B4)call before aZwOpenKeyread-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-argwil_details_IsEnabledFallback (sub_1C0008248)(arg1, arg2, &data_1C00378F8)to 2-argwil_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-suppliedarg3. 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 globalFeature_2858196282__private_featureState (data_1C003A080)instead of a caller-supplied pointer. Atomiclock cmpxchglogic unchanged. -
wil_details_FeatureReporting_ReportUsageToServiceDirect (sub_1C0007E88)— similarity 0.9765 — cosmetic.RtlNotifyFeatureUsagecaller now uses hardcoded values (feature ID0x3818AA2,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\Servicesdoes 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)andCscRebootRenamepAddToKey (sub_1C006F42C)invoke the helper with the create flag set (r8b = 1at0x1C006F19Cand0x1C006F538) for paths under\Registry\Machine\System\CurrentControlSet\Services\CSC\Parameters(and itsRebootRenamesubkey). 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
...\Servicessemantics (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.