ci.dll — SymCrypt library rebuild and WIL feature-staged TrustedLaunch development
KB5094126
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(unpatched0x180002D0C, patched0x18000E1E4)
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(unpatched0x18005589C, patched0x18005689C)
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(unpatched0x18000E700, patched0x18000E760)
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
SymCryptRsakeyCalculatePrivateFieldscheck 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, andCipApplySiPolicyExadditions 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(unpatched0x180002D0C, patched0x18000E1E4; Sim 0.701, SymCrypt library churn): RSA CRT private-field derivation. Added a 7th flags parameter, an entry guard rejecting flags outside0x200, and ann == p·qkey-consistency check (SymCryptFdefIntMulMixedSize/SymCryptFdefIntIsEqual) returning SymCrypt error0x800Eon mismatch. Not on the PE signature-verification path (private-key derivation only). No security-relevant CI change.CiGetActionsForImageWithToken(unpatched0x18005589C, patched0x18005689C; Sim 0.7607, feature-staged): Added a developer-mode branch (a5 == 3→ bit0x100000viaCipIsDeveloperModeEnabled) and ag_CipWhichLevelComparisons/g_CiPolicyStatebranch whose0x80000computation is gated behindFeature_CodeIntegrity_TrustedLaunch_TrustedRootStore. TrustedLaunch feature development; prior behavior retained.CipMinCryptToSigningLevel(unpatched0x1800E1E70, patched0x1800E32A0; Sim 0.8895, feature-staged): Restructured aroundFeature_CodeIntegrity_TrustedLaunchPolicywith the old path kept in theelsebranch.CipMinCryptToAuthRoot2is now called with an added output parameter, and a newCiCatDbVerifyCertInStorefallback (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(unpatched0x18000E700, patched0x18000E760; Sim 0.9700, SymCrypt library churn): Added a0xC000mask to the internal modulus-descriptor flags test used to pick a modular-reduction implementation. Arithmetic dispatch, not signature-rule matching.SymCryptRsakeySetValueInternal(unpatched0x18001E648, patched0x18001E798; Sim 0.9728, SymCrypt library churn): Propagates the new flags argument down intoSymCryptRsakeyCalculatePrivateFields. SymCrypt key-import plumbing.SymCryptEckeySetValue(unpatched0x18001FE18, patched0x18001FF40; Sim 0.9849, SymCrypt library churn): Reworked the ECDSA FIPS self-test bookkeeping so that when the EC key flag0x1000is 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(unpatched0x1800E0664, patched0x1800E1A64; Sim 0.9814, feature-staged): Broadened a policy-marking gate fromFeature_Servicing_EndpointSec_AppIDTaggingto also acceptFeature_Servicing_EndpointSec_AppIDTagging_6D_CFR; remainder is register/label/address renumbering.SymCryptEcurveInitialize(unpatched0x18001F074, patched0x18001F1B8; Sim 0.9719, SymCrypt library churn): Register renaming and address renumbering; no behavioral security change.SIPolicyObjectValidationEngine(unpatched0x1800747D8, patched0x180075BD8; Sim 0.9200, feature-staged): OR-ed the newFeature_Servicing_EndpointSec_AppIDTagging_6D_CFRflag into the two existing AppID-tagging gates; remainder is churn.SymCryptParallelHashProcess(unpatched0x180006910, patched0x180006630; Sim 0.9914, cosmetic): Address renumbering only; no logical change.CipMinCryptToAuthRoot2(unpatched0x180055CFC, patched0x180056D88; 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 behindFeature_CodeIntegrity_TrustedLaunchPolicyand 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·qinSymCryptRsakeyCalculatePrivateFields) is a SymCrypt key-material consistency validation reached only during private-key derivation, not during PE Authenticode verification.