Overview

  • Unpatched Binary: http_unpatched.sys
  • Patched Binary: http_patched.sys
  • Overall Similarity Score: 0.9908
  • Diff Statistics: 3585 matched functions, 21 changed functions, 3564 identical functions, and 0 unmatched functions in either direction.

Verdict: The patch removes a Known-Issue-Rollback (KIR) gate, UxKirSetCbtHardeningAsMedium, and the legacy channel-binding routines it selected, making the channel-binding-token setup path unconditional. The flag is a servicing rollback switch materialized from a WIL feature-staging check at module init, and the old code path was retained in the else branch. This is a servicing/hardening consolidation (a KIR graduating to permanent), not a fix for an attacker-reachable vulnerability. No demonstrable, reachable security-relevant change is delivered by this diff.


2. Vulnerability Summary

  • Severity: None (informational)
  • Vulnerability Class: No security-relevant change. Known-Issue-Rollback gate removal / servicing churn.
  • Affected Functions: UlpSspiAuthenticationHandler (Negotiate/NTLM SSPI handler, channel-binding setup), UlpAdjustSspChannelBindParamsForDigestChallenge (Digest-challenge channel-binding setup), UlpDigestAuthenticationHandler (Digest auth channel-binding setup), UxStartEnvironmentFacet (registry/config reader for DefaultAuthHardeningLevel), UxStartEnvironmentModule (module-init site that materializes the flag).

Root Cause / Nature of the change: In the unpatched http.sys, a global byte UxKirSetCbtHardeningAsMedium selects between two channel-binding setup routines at each SSP auth site. The name follows the UxKir* convention used throughout this module for Known-Issue-Rollback gates: at module init UxStartEnvironmentModule calls wil_InitializeFeatureStaging (0x1C0176831) and then materializes each gate from a WIL feature-staging query, e.g. UxKirSetCbtHardeningAsMedium = (Feature_2058039610__private_IsEnabledDeviceUsage() != 0), compiled as setnz cs:UxKirSetCbtHardeningAsMedium at 0x1C017684B. Sibling gates set the same way in the same block include UxKirChunkExtHeaderFix, UxKirHttpReadingTrailersFromUm, UxKirWritingAndReadingUm, and UxKirHKERangeRequestH2StatusCode.

When the gate is non-zero, each SSP auth site calls UlpAdjustSspChannelBindParameters, passing the registry-configured DefaultAuthHardeningLevel (*(cfg+0x418)) as a default hardening level. When the gate is zero, the sites call the legacy UlpAdjustSspChannelBindParameters_Old, which sources the hardening level from the per-connection context field *(ctx+0x8C) instead of the registry parameter. Correspondingly, the config reader UxStartEnvironmentFacet reads DefaultAuthHardeningLevel from the registry only when the gate is set.

Both routines are otherwise functionally equivalent: both call UlQueryChannelBindToken and perform the same channel-binding bit-setting (bts on the SSP flags word), and, in the caller, both branches write the same three context fields (+0x88, +0x8C, +0x90) before the call. The difference is limited to whether the default hardening level comes from the registry DefaultAuthHardeningLevel value or from the per-connection field. The patch removes the gate everywhere, reads the policy unconditionally, always uses UlpAdjustSspChannelBindParameters, and deletes the legacy UlpAdjustSspChannelBindParameters_Old and UlCopyChannelBindConfigToIrp_Old functions.

Why this is not scored as a security fix: * The selector is a UxKir* Known-Issue-Rollback gate, i.e. a servicing switch, not an attacker-controlled input. Reaching the legacy path requires the KIR to be in its rolled-back state, which is an administrative/servicing condition, not a network-reachable one. * The legacy path is retained in the else branch (staged-rollout pattern). Removing it and hardcoding the enabled behavior is the KIR graduating to permanent, not the elimination of an exposed vulnerability. * Both paths still perform channel-binding enforcement (UlQueryChannelBindToken + flag bit-setting). The diff does not show the legacy path accepting authentication that the new path rejects; the observable difference is only the source of the default hardening level.


3. Pseudocode Diff

The change in UlpAdjustSspChannelBindParamsForDigestChallenge is the removal of the gate and its retained legacy else branch. Both branches in the unpatched build write the same context fields; they differ only in which setup routine is called and, therefore, where the default hardening level is sourced.

// === UNPATCHED: UlpAdjustSspChannelBindParamsForDigestChallenge @ 0x1C01553B8 ===
int32_t authFlags = *(arg3 + 0x50);
if (UxKirSetCbtHardeningAsMedium != 0) {                       // <-- KIR gate (rollback switch)
    ctx->_88 = authFlags; ctx->_90 = *(arg3+0x58); ctx->_8C = *(arg3+0x54);
    rc = UlpAdjustSspChannelBindParameters(ctx, *(cfg+0x418), arg4, arg5); // level default from registry DefaultAuthHardeningLevel
} else if (authFlags & 1) {
    ctx->_88 = authFlags; ctx->_90 = *(arg3+0x58); ctx->_8C = *(arg3+0x54); // same context fields written
    rc = UlpAdjustSspChannelBindParameters_Old(ctx, arg4, arg5);            // level sourced from ctx->_8C instead
}

// === PATCHED: UlpAdjustSspChannelBindParamsForDigestChallenge @ 0x1C0154B88 ===
int32_t authFlags = *(arg3 + 0x50);
ctx->_88 = authFlags; ctx->_90 = *(arg3+0x58); ctx->_8C = *(arg3+0x54);
rc = UlpAdjustSspChannelBindParameters(ctx, *(cfg+0x418), arg4, arg5);      // unconditional

The registry reader UxStartEnvironmentFacet is modified to read the policy unconditionally:

// === UNPATCHED: UxStartEnvironmentFacet @ 0x1C0127CB0 ===
if (UxKirSetCbtHardeningAsMedium) {                    // only reads when the KIR gate is set
    UxReadBoundedULongSetting(KeyHandle, u"DefaultAuthHardeningLevel", 1, 0, 2, 0, cfg+0x418);
}

// === PATCHED: UxStartEnvironmentFacet ===
UxReadBoundedULongSetting(KeyHandle, u"DefaultAuthHardeningLevel", 1, 0, 2, 0, cfg+0x418); // always reads

4. Assembly Analysis

Both branches of the unpatched UlpAdjustSspChannelBindParamsForDigestChallenge write the same three context fields (+0x88, +0x8C, +0x90) before their respective call. The only real difference is the callee and the fourth argument (edx): the gated-on path passes edx = *(rdi+0x418) (DefaultAuthHardeningLevel) to UlpAdjustSspChannelBindParameters; the legacy path calls the 3-argument UlpAdjustSspChannelBindParameters_Old, which reads the hardening level from [ctx+0x8C] internally.

### UNPATCHED: UlpAdjustSspChannelBindParamsForDigestChallenge @ 0x1C01553B8 ###

00000001C01553D5  mov     eax, [r8+50h]                   ; auth flags
00000001C01553D9  xor     ebp, ebp
00000001C01553DB  cmp     cs:UxKirSetCbtHardeningAsMedium, bpl  ; *** KIR GATE ***
00000001C01553E8  mov     rdi, rcx
00000001C01553EB  jz      loc_1C01554A3                   ; gate == 0 -> legacy branch

; ---- gate != 0 branch ----
00000001C01553F6  lea     rcx, [r8+318h]
00000001C01553FD  mov     [rcx+88h], eax                  ; ctx+0x88 = auth flags
00000001C0155406  mov     eax, [r8+58h]
00000001C015540A  mov     [rcx+90h], eax                  ; ctx+0x90
00000001C0155410  mov     eax, [r8+54h]
00000001C0155417  mov     [rcx+8Ch], eax                  ; ctx+0x8C
00000001C015541D  mov     edx, [rdi+418h]                 ; edx = DefaultAuthHardeningLevel
00000001C0155423  call    UlpAdjustSspChannelBindParameters

; ---- gate == 0 branch (legacy) ----
00000001C01554A3  test    al, 1
00000001C01554A5  jz      loc_1C015555D
00000001C01554B0  lea     rcx, [r8+318h]
00000001C01554B7  mov     [rcx+88h], eax                  ; ctx+0x88 = auth flags (same)
00000001C01554BD  mov     rdx, rsi                        ; 3rd arg to _Old
00000001C01554C0  mov     eax, [r8+58h]
00000001C01554C4  mov     [rcx+90h], eax                  ; ctx+0x90 (same)
00000001C01554CA  mov     eax, [r8+54h]
00000001C01554CE  mov     r8, r15
00000001C01554D1  mov     [rcx+8Ch], eax                  ; ctx+0x8C (same; _Old reads its level from here)
00000001C01554D7  call    UlpAdjustSspChannelBindParameters_Old

In the patched build (0x1C0154B88) the gate check, the conditional jump, and the entire legacy branch are removed; the sole path writes +0x88/+0x8C/+0x90 and calls UlpAdjustSspChannelBindParameters with edx = [rsi+0x418] (0x1C0154BDE-0x1C0154BE4).

The two setup routines are near-identical. UlpAdjustSspChannelBindParameters (0x1C01552AC) takes the level in edx (ebp = edx) and, when *(ctx+0x88) & 1, overrides it from *(ctx+0x8C); UlpAdjustSspChannelBindParameters_Old (0x1C00C69DC) reads the level directly from *(ctx+0x8C) (mov r10d, [rbx+8Ch]). Both then call UlQueryChannelBindToken and perform the same channel-binding flag bit-setting (bts at bits 0x1A/0x1C).


5. Reachability

Selection between the two paths is controlled entirely by the UxKirSetCbtHardeningAsMedium KIR gate, which is set at module init from a WIL feature-staging query and is not derived from any request field. An HTTP client cannot influence which path runs. Reaching the legacy path requires the KIR to be in its rolled-back state, which is a servicing/administrative condition rather than a network-reachable one. There is no attacker-controlled trigger for a differential behavior in this diff.


6. Exploit Primitive

None. This is not a memory-corruption change and it exposes no read/write/execute primitive. The diff removes a servicing rollback gate and consolidates to a single channel-binding setup routine. Both the retained and removed routines perform channel-binding enforcement via UlQueryChannelBindToken; the binaries do not demonstrate the legacy routine accepting authentication that the new routine rejects, so no authentication-bypass primitive is established from this diff.


7. Observation Notes

For a researcher confirming the nature of the change on the unpatched binary with a kernel debugger:

  • UxKirSetCbtHardeningAsMedium is the byte read by cmp cs:UxKirSetCbtHardeningAsMedium, ... at 0x1C01553DB (Digest-challenge), 0x1C01580D2 (SSPI handler), 0x1C0067DD1 (Digest handler), and the config reader gate at 0x1C01285D4. Its value is fixed at module init by setnz cs:UxKirSetCbtHardeningAsMedium (0x1C017684B) from Feature_2058039610__private_IsEnabledDeviceUsage().
  • bp http!UlpAdjustSspChannelBindParamsForDigestChallenge and bp http!UlpAdjustSspChannelBindParameters_Old: whether the legacy routine is reached during auth depends solely on the gate value, not on request contents.
  • Inspecting the gate byte shows which routine the build will use. This is a configuration/servicing observation, not an exploit condition.

No crash, corruption, or authentication-decision differential attributable to attacker input is expected or demonstrable from this change.


8. Changed Functions — Full Triage

Known-Issue-Rollback gate removal (servicing consolidation, not a security fix) * UlpAdjustSspChannelBindParamsForDigestChallenge (0x1C01553B8 -> 0x1C0154B88): Removed the gate check at 0x1C01553DB and the legacy else branch; now always calls UlpAdjustSspChannelBindParameters with DefaultAuthHardeningLevel ([rsi+0x418]). Both unpatched branches wrote the same context fields; the difference was the callee and level source. * UlpSspiAuthenticationHandler (0x1C0157F40 -> 0x1C0157650): Removed the gate at 0x1C01580D2 and the _Old fallback in the AcceptSecurityContext channel-binding setup. * UlpDigestAuthenticationHandler (0x1C0067D20 -> 0x1C0067E20): Removed the gate at 0x1C0067DD1 and the legacy branch; now unconditionally calls UlpAdjustSspChannelBindParameters with DefaultAuthHardeningLevel. * UxStartEnvironmentFacet (0x1C0127CB0): Reads DefaultAuthHardeningLevel (UxReadBoundedULongSetting, default 1, range 0-2) into +0x418 unconditionally instead of only when the gate was set (gate check at 0x1C01285D4). * UlDelegateRequest (0x1C010E1EC): Removed the three gate checks (0x1C010E395, 0x1C010E50F, 0x1C010E843) and the legacy branch, consolidating to the single channel-binding path. Stack frame also grew +8 consistent with the struct-version bump below. * UlGetServerSessionProperty (0x1C0120D74) & UlQueryConfigGroupProperty (0x1C011B4F8): Removed the if(!UxKirSetCbtHardeningAsMedium) gate (0x1C0120EA6 / 0x1C011B658) that routed the channel-bind config query (info class 0xa) to the legacy UlCopyChannelBindConfigToIrp_Old; now always call UlCopyChannelBindConfigToIrp. * UxStartEnvironmentModule (0x1C01767B0): Module-init site that materializes the gate from the WIL feature (setnz cs:UxKirSetCbtHardeningAsMedium at 0x1C017684B, read back at 0x1C01768A4 for a WPP trace). The patch drops the feature-staging write; the gate no longer exists.

Struct-version bump and recompilation artifacts (not security-relevant) The remaining functions reflect a connection/property info structure growing by 8 bytes (size constants shifting from 0xc8 to 0xd0 and field offsets shifting by +8), plus routine ETW/WPP trace renumbering. * WPP_SF_qiqi (0x1C00B57EC), UxpQuicParseHeadersFrame (0x1C0054E58), UxpQuicParseDataFrame (0x1C0054CE0), UxpQuicParseCancelPushFrame (0x1C0054C0C): ETW trace GUID/message-ID renumbering and register-allocation differences. * UxQuicGetStatsV2MinimumSize (0x1C001FF60), UxQuicConnectionQueryRemoteCertificates (0x1C0048C6C): QUIC size-negotiation/property handlers where stack/local layouts and sizes accommodate the new 0xd0 structure size. * UxQuicParseFrame (0x1C00542D4), UlQueryRequestProperty (0x1C012DA28), UcQueryClientConnectionFastIo (0x1C0070E40), UlQueryRequestFastIo (0x1C00392F0), UlCopyAuthConfigToIrp (0x1C0066CD0), UcQueryClientConnectionProperty (0x1C0071284), UxQuicStreamConnectionControl (0x1C0056FB0): Shifted struct-version offsets (+8) and size validation gates moved in lockstep with the struct growth. No new bounds checks or security logic.


9. Unmatched Functions

The diff pairing reports 0 unmatched functions. The legacy routines UlpAdjustSspChannelBindParameters_Old (0x1C00C69DC) and UlCopyChannelBindConfigToIrp_Old (0x1C00C6400) exist only in the unpatched build; in the patched build those addresses are occupied by unrelated code (address reuse: 0x1C00C6400 is UlConstructFileName in the patched binary), so the legacy channel-binding routines are removed rather than relocated. Both routines sit within the WIL feature-staging code cluster (wil_* functions) in the unpatched build, consistent with their role as the rolled-back side of a KIR gate.


10. Confidence & Caveats

  • Confidence: High that this is a Known-Issue-Rollback gate graduation and struct-version bump, not a security fix. The gate is a UxKir* global materialized from WIL feature-staging at init, alongside sibling UxKir* servicing gates; the old path was retained in the else branch; the patch simply removes the rollback capability.
  • What was verified: The gate materialization (0x1C0176844-0x1C017684B), the two setup routines (UlpAdjustSspChannelBindParameters at 0x1C01552AC and UlpAdjustSspChannelBindParameters_Old at 0x1C00C69DC) and their functional equivalence (both call UlQueryChannelBindToken and set the same channel-binding flag bits), the unpatched and patched Digest-challenge callers (0x1C01553B8 / 0x1C0154B88), the config reader gate (0x1C01285D4), and the removal of the _Old routines in the patched build.
  • Not established: No attacker-reachable trigger and no differential authentication decision attributable to client input are demonstrable from these binaries. Any claim of an authentication bypass or relay primitive would require the KIR to be in its rolled-back state and a demonstration, absent here, that the legacy routine accepts what the new routine rejects.