1. Overview

  • Unpatched Binary: ci_unpatched.dll
  • Patched Binary: ci_patched.dll
  • Overall Similarity Score: 98.87%
  • Diff Statistics: 2531 matched functions, 11 changed functions, 2520 identical functions, and 0 unmatched functions in either direction.
  • Verdict: No delivered, attacker-reachable Code Integrity signature-verification fix is present in this diff. The 11 changed functions split into two groups: (a) statically-linked SymCrypt cryptographic library functions (RSA/EC key handling, modular-arithmetic dispatch, parallel hashing) that changed because SymCrypt was rebuilt, and (b) Windows Code Integrity policy functions whose new logic is gated behind WIL feature-staging flags (Feature_CodeIntegrity_TrustedLaunch*, Feature_Servicing_EndpointSec_AppIDTagging*) with the pre-existing code paths retained. Neither group adds an un-gated accept/reject check on the PE Authenticode verification path.

2. Change Summary

Item 1: SymCrypt RSA private-field computation - added key consistency check (No security-relevant CI change)

  • Severity: None (SymCrypt library churn)
  • Nature: Cryptographic library key-validation hardening, not signature verification
  • Affected Function: SymCryptRsakeyCalculatePrivateFields (unpatched 0x180002D0C, patched 0x18000E1E4)

What actually changed: This is the SymCrypt routine that derives the private CRT parameters of an RSA key from its prime factors (SymCryptCrtGenerateForTwoCoprimes, SymCryptIntExtendedGcd, SymCryptFdefRawDivMod). The patched build adds a 7th parameter a7 (a flags word) and two behaviors: - An entry guard if ((a7 & 0xFFFFFDFF) != 0) return 32782; (only flag bit 0x200 is accepted). - When bit 0x200 is clear and the key has exactly two prime factors (*(a1+0x1c) == 2), a post-loop consistency check that multiplies the two primes and compares the product to the modulus: SymCryptFdefIntMulMixedSize(*(a1+0x80)+0x60, *(a1+0x88)+0x60, a4) then SymCryptFdefIntIsEqual(a4, *(a1+0x78)+0x60). On failure the routine returns 32782 (0x800E), a SymCrypt error code, not the NTSTATUS STATUS_INVALID_IMAGE_HASH (0xC0000428).

The +0x80/+0x88 pointers are the key's two prime integers and +0x78 is its modulus integer; +0x60 is the SymCrypt integer object's data field. This is an RSA keypair self-consistency check (n == p·q), consistent with SymCrypt's key-validation flags. It operates on the key's own material, not on any catalog, certificate, or PE hash.

Reachability: SymCryptRsakeyCalculatePrivateFields computes private CRT fields and requires two supplied primes. Authenticode signature verification in ci.dll uses only the RSA public key (modulus + exponent, no private primes), so this routine is not on the PE signature-verification path and is not driven by attacker-supplied signature blobs.

Item 2: CiGetActionsForImageWithToken - TrustedLaunch trust-flag computation (feature-staged)

  • Severity: None (WIL feature-staging / feature development)
  • Affected Function: CiGetActionsForImageWithToken (unpatched 0x18005589C, patched 0x18005689C)

What actually changed: The patched build adds two new flag-computation branches after the existing 0x20000 bit handling: - If the signing-level argument a5 == 3, it calls CipIsDeveloperModeEnabled and conditionally sets result bit 0x100000. - If (a4 & 0xE41FFF87) == 0 and the policy table entry *((_DWORD*)g_CipWhichLevelComparisons + (a5 & 0xF)) & 4 is set, it recomputes result bit 0x80000 from g_CiPolicyState & 0x2000; the sub-branch that sets/merges 0x80000 is gated behind Feature_CodeIntegrity_TrustedLaunch_TrustedRootStore__private_IsEnabledDeviceUsageNoInline().

These are additive TrustedLaunch trust-flag computations tied to developer-mode and a WIL feature flag; the unpatched behavior is preserved when the new conditions do not apply. The globals data_180045210/data_180045e70 are g_CipWhichLevelComparisons/g_CiPolicyState.

Item 3: SymCryptFdefDecideModulusType - modulus-descriptor flag mask (No security-relevant CI change)

  • Severity: None (SymCrypt library churn)
  • Affected Function: SymCryptFdefDecideModulusType (unpatched 0x18000E700, patched 0x18000E760)

What actually changed: This SymCrypt routine selects a modular-reduction implementation for a modulus based on its value and available CPU features. The patched build changes the descriptor-flags test from if ((v17[1] & g_SymCryptCpuFeaturesNotPresent) == 0) to if ((v17[1] & 0xC000) == 0 && (v17[1] & g_SymCryptCpuFeaturesNotPresent) == 0), i.e. it also skips descriptor-table entries whose flag word has bits 0xC000 set. v17 walks an internal SymCrypt modulus-type table; this is arithmetic-implementation dispatch, not a signature rule table or a certificate deny list.


3. Pseudocode Diff

SymCryptRsakeyCalculatePrivateFields - unpatched 0x180002D0C vs patched 0x18000E1E4

// ================= UNPATCHED (0x180002D0C) =================
__int64 SymCryptRsakeyCalculatePrivateFields(a1, a2, a3, a4, a5, a6) {   // 6 params
    ...
    if (*(a1 + 0x1c) != 2) return 32782;          // 0x800E, SymCrypt error
    SymCryptCrtGenerateForTwoCoprimes(...);
    // loops computing CRT private fields via
    // SymCryptFdefIntCopyMixedSize / SymCryptIntExtendedGcd / SymCryptFdefRawDivMod
    return v13;                                    // returns last op status
}

// ================= PATCHED (0x18000E1E4) =================
__int64 SymCryptRsakeyCalculatePrivateFields(a1, a2, a3, a4, a5, a6, a7) { // NEW 7th param
    if ((a7 & 0xFFFFFDFF) != 0) return 32782;      // NEW: only flag 0x200 allowed
    if (*(a1 + 0x1c) != 2) return 32782;
    SymCryptCrtGenerateForTwoCoprimes(...);
    // ... same CRT loops ...
    if ((a7 & 0x200) != 0) return v15;             // NEW: skip check when flag set
    if (v20 == 2) {                                // NEW: two-prime keys only
        // NEW: verify n == p * q on the key's own components
        SymCryptFdefIntMulMixedSize(*(a1+0x80)+0x60, *(a1+0x88)+0x60, a4);
        if (SymCryptFdefIntIsEqual(a4, *(a1+0x78)+0x60) != 0) return v15;
    }
    return 32782;
}

CiGetActionsForImageWithToken - unpatched 0x18005589C vs patched 0x18005689C

// ================= UNPATCHED (0x18005589C) =================
v15 = ... | CiGetActionsForImage(a1, a3, a4, v13, a5) & 0xFFFAFFFF;
if (Feature_CodeIntegrity_TrustedLaunchPolicy_...IsEnabled() != 0 && v7 != 0) {
    v19[0] = 0;
    if (CiIsTrustedLaunchPolicyEnabled(a2, v19) >= 0)
        v15 ^= (v15 ^ (v19[0] << 17)) & 0x20000;
}
*a6 = v15;

// ================= PATCHED (0x18005689C) =================
v14 = ... | CiGetActionsForImage(a1, a3, a4, v12, a5) & 0xFFFAFFFF;
// existing 0x20000 handling preserved, then:
if (a5 == 3) {                                     // NEW: developer-mode branch
    v17[0] = 0;
    if (CipIsDeveloperModeEnabled(v17) >= 0)
        v14 ^= (v14 ^ (v17[0] << 20)) & 0x100000;  // NEW flag bit
}
if ((a4 & 0xE41FFF87) == 0 && (*((_DWORD*)g_CipWhichLevelComparisons + (a5 & 0xF)) & 4) != 0) {
    v14 = v14 & 0xFFF7FFFF | ((g_CiPolicyState & 0x2000) << 6);
    if (v7 != 0) {                                 // gated below by a WIL feature flag
        if (Feature_CodeIntegrity_TrustedLaunch_TrustedRootStore_...IsEnabled() != 0)
            v14 |= 0x80000u;
        else
            v14 = v14 & 0xFFF7FFFF ^ (v14 | (v14 >> 1)) & 0x80000;
    }
}
*a6 = v14;

4. Assembly Analysis

SymCryptRsakeyCalculatePrivateFields patched additions (0x18000E1E4)

000000018000E20A  test    [rsp+arg_30], 0FFFFFDFFh   ; NEW entry guard on a7
...
000000018000E4A7  test    [rsp+arg_30], 200h         ; NEW: skip when flag 0x200 set
...
000000018000E4D4  call    SymCryptFdefIntMulMixedSize ; NEW: compute p * q
000000018000E4E6  call    SymCryptFdefIntIsEqual      ; NEW: compare product to modulus
000000018000E4EB  test    eax, eax
000000018000E4EF  mov     edi, 800Eh                  ; SymCrypt error code on mismatch

The unpatched function at 0x180002D0C has neither the 0xFFFFFDFF entry guard nor the SymCryptFdefIntMulMixedSize/SymCryptFdefIntIsEqual product-vs-modulus check. 0x800E here is a SymCrypt status value, not an NTSTATUS.


5. Trigger Conditions

No security-relevant Code Integrity condition is triggerable by attacker input in this diff:

  • The SymCryptRsakeyCalculatePrivateFields check validates an RSA key's own primes against its modulus during private-key derivation; Authenticode verification never imports attacker-supplied private primes, so the new check is not reached from PE signature validation.
  • The CiGetActionsForImageWithToken, CipMinCryptToSigningLevel, CipMinCryptToAuthRoot2, SIPolicyObjectValidationEngine, and CipApplySiPolicyEx additions are gated behind WIL feature-staging flags with the prior code paths retained; behavior on shipping configurations is unchanged until the corresponding feature is enabled.

6. Exploit Primitive & Development Notes

No exploit primitive is established by this diff. The changes do not add or remove any un-gated accept/reject decision on the signature-verification path, so there is no signature-bypass, no memory-safety, and no privilege boundary crossed. The SymCrypt changes are a library rebuild (RSA key consistency check, modular-arithmetic dispatch flag, EC key FIPS self-test bookkeeping); the Code Integrity changes are feature-staged TrustedLaunch and AppID-tagging development.


7. Debugger PoC Playbook

No proof-of-concept applies, because no attacker-reachable security-relevant change was delivered. For reference during further review, the real (non-security) code change in SymCryptRsakeyCalculatePrivateFields can be observed at these patched addresses:

ci!SymCryptRsakeyCalculatePrivateFields (0x18000E1E4)      ; patched entry, now takes a7
ci!SymCryptRsakeyCalculatePrivateFields+0x26 (0x18000E20A) ; test arg7, 0xFFFFFDFF entry guard
ci!SymCryptRsakeyCalculatePrivateFields+0x2F0 (0x18000E4D4); SymCryptFdefIntMulMixedSize (p*q)
ci!SymCryptRsakeyCalculatePrivateFields+0x302 (0x18000E4E6); SymCryptFdefIntIsEqual (== modulus)

The unpatched counterpart is at 0x180002D0C and lacks these instructions. This documents a SymCrypt key-consistency check, not a Code Integrity signature check.


8. Changed Functions - Full Triage

  • SymCryptRsakeyCalculatePrivateFields (unpatched 0x180002D0C, patched 0x18000E1E4; Sim 0.701, SymCrypt library churn): RSA CRT private-field derivation. Added a 7th flags parameter, an entry guard rejecting flags outside 0x200, and an n == p·q key-consistency check (SymCryptFdefIntMulMixedSize/SymCryptFdefIntIsEqual) returning SymCrypt error 0x800E on mismatch. Not on the PE signature-verification path (private-key derivation only). No security-relevant CI change.
  • CiGetActionsForImageWithToken (unpatched 0x18005589C, patched 0x18005689C; Sim 0.7607, feature-staged): Added a developer-mode branch (a5 == 3 → bit 0x100000 via CipIsDeveloperModeEnabled) and a g_CipWhichLevelComparisons/g_CiPolicyState branch whose 0x80000 computation is gated behind Feature_CodeIntegrity_TrustedLaunch_TrustedRootStore. TrustedLaunch feature development; prior behavior retained.
  • CipMinCryptToSigningLevel (unpatched 0x1800E1E70, patched 0x1800E32A0; Sim 0.8895, feature-staged): Restructured around Feature_CodeIntegrity_TrustedLaunchPolicy with the old path kept in the else branch. CipMinCryptToAuthRoot2 is now called with an added output parameter, and a new CiCatDbVerifyCertInStore fallback (gated by *(a1+2712) flag bits and the new AuthRoot status) can grant signing level 3. Feature-gated staged rollout, not a delivered check tightening.
  • SymCryptFdefDecideModulusType (unpatched 0x18000E700, patched 0x18000E760; Sim 0.9700, SymCrypt library churn): Added a 0xC000 mask to the internal modulus-descriptor flags test used to pick a modular-reduction implementation. Arithmetic dispatch, not signature-rule matching.
  • SymCryptRsakeySetValueInternal (unpatched 0x18001E648, patched 0x18001E798; Sim 0.9728, SymCrypt library churn): Propagates the new flags argument down into SymCryptRsakeyCalculatePrivateFields. SymCrypt key-import plumbing.
  • SymCryptEckeySetValue (unpatched 0x18001FE18, patched 0x18001FF40; Sim 0.9849, SymCrypt library churn): Reworked the ECDSA FIPS self-test bookkeeping so that when the EC key flag 0x1000 is set the key object is marked (*v8 |= 1) regardless of whether the one-time self-test ran; relocated an internal jump table. No CI signature logic.
  • CipApplySiPolicyEx (unpatched 0x1800E0664, patched 0x1800E1A64; Sim 0.9814, feature-staged): Broadened a policy-marking gate from Feature_Servicing_EndpointSec_AppIDTagging to also accept Feature_Servicing_EndpointSec_AppIDTagging_6D_CFR; remainder is register/label/address renumbering.
  • SymCryptEcurveInitialize (unpatched 0x18001F074, patched 0x18001F1B8; Sim 0.9719, SymCrypt library churn): Register renaming and address renumbering; no behavioral security change.
  • SIPolicyObjectValidationEngine (unpatched 0x1800747D8, patched 0x180075BD8; Sim 0.9200, feature-staged): OR-ed the new Feature_Servicing_EndpointSec_AppIDTagging_6D_CFR flag into the two existing AppID-tagging gates; remainder is churn.
  • SymCryptParallelHashProcess (unpatched 0x180006910, patched 0x180006630; Sim 0.9914, cosmetic): Address renumbering only; no logical change.
  • CipMinCryptToAuthRoot2 (unpatched 0x180055CFC, patched 0x180056D88; Sim 0.7953, feature-staged): Gained a fifth output parameter. The pre-existing certificate public-key and CTL-revocation checks are unchanged (now written through renamed pointers); all newly added writes emit informational TrustedLaunch status codes gated behind Feature_CodeIntegrity_TrustedLaunchPolicy and do not change the accept/reject return.

9. Unmatched Functions

There were 0 unmatched functions added or removed between the unpatched and patched binaries.


10. Confidence & Caveats

  • Confidence Level: High. Every changed function was diffed in both the decompilation and the disassembly and matched by content across the address relocations.
  • Basis: Six of the eleven changed functions are statically-linked SymCrypt routines (RSA/EC key setup, modular-arithmetic dispatch, parallel hashing) that moved and changed as a consequence of a SymCrypt rebuild. The five Code Integrity functions add logic that is either gated behind WIL feature-staging flags (Feature_CodeIntegrity_TrustedLaunch*, Feature_Servicing_EndpointSec_AppIDTagging*) with the prior path retained, or is register/address renumbering.
  • Conclusion: This diff does not deliver an attacker-reachable Code Integrity signature-verification fix. The one genuinely new cryptographic check (n == p·q in SymCryptRsakeyCalculatePrivateFields) is a SymCrypt key-material consistency validation reached only during private-key derivation, not during PE Authenticode verification.