CI.sys Signature Validation Gaps
I took a legitimately signed kernel driver, zeroed every byte of its RSA signature, and loaded it. Secure Boot enabled, HVCI enabled, test signing disabled. The driver loaded without issues.
The RSA signature is supposed to be the cryptographic proof that someone with the private key signed the file. Without valid signature bytes, verification should fail. It doesn’t—at least not for drivers signed with certificates issued before July 29, 2015.
Background
The grandfather date is hardcoded in CI.sys:
FILETIME: 0x01D0C6D5B9A10000 (July 29, 2015 00:00:00 UTC)
Little-endian bytes: 00 00 A1 B9 D5 C6 D0 01
Microsoft’s documentation says signature validation checks the certificate chain, expiration, timestamp, RSA/ECDSA correctness, authenticode hash, and code signing EKU. I wanted to know which of those CI.sys actually enforces for grandfathered signatures.
Approach
Rather than just reversing CI.sys, I took an empirical approach. Start with a legitimately signed driver that loads, systematically break parts of the signature, observe what still works.
The signature lives in the PE’s Security Directory inside a WIN_CERTIFICATE structure. Inside that is a PKCS#7 blob containing the authenticode hash, certificate chain, and SignerInfo—which includes the RSA signature in EncryptedDigest.
I built tools to analyze signature structures, apply specific mutations, and transplant signatures between binaries. Test environment was Windows 10/11 with Secure Boot and HVCI enabled, test signing disabled.
Mutation results
Applied systematic mutations to a signed driver:
Zeroing the RSA signature bytes: driver loads. Zeroing certificate signatures: fails. Truncating the PKCS#7 structure: fails. Changing WIN_CERTIFICATE revision: fails. Changing certificate type: fails. Removing the timestamp: fails. Backdating the timestamp: fails. Corrupting certificate data: fails. Corrupting the authenticode hash: fails.
Structure, certificates, hashes, timestamps—all validated. The RSA signature itself isn’t checked.
The zero-signature mutation
The code finds OCTET STRING tags with long-form length encoding (RSA signatures are typically 128-512 bytes) and zeros the content:
bool mutZeroEncryptedDigest(BYTE* pkcs7, DWORD len) {
int found = 0;
for (DWORD i = 0; i < len - 4; i++) {
if (pkcs7[i] == 0x04 && pkcs7[i+1] == 0x82) {
DWORD sigLen = (pkcs7[i+2] << 8) | pkcs7[i+3];
if (sigLen >= 128 && sigLen <= 512 && i + 4 + sigLen <= len) {
memset(&pkcs7[i+4], 0, sigLen);
found++;
}
}
}
return found > 0;
}
The signature bytes go from 256 bytes of RSA data to 256 zeros. The driver loads.
Signature transplantation
To confirm the findings, I transplanted a signature from one binary to another. The process: calculate authenticode hashes for both files, extract the signature from the donor, replace the hash inside the signature with the target’s hash, zero the RSA signature (can’t re-sign without the private key), attach to the target.
The target driver loads. This confirms the authenticode hash is validated (it has to match), the RSA signature isn’t validated (we zeroed it), and the structure is validated (malformed signatures fail).
Probable cause
Best hypothesis: the timestamp provides the trust anchor for grandfathered signatures. If a valid timestamp from a trusted TSA exists and the certificate chain predates the grandfather cutoff, CI.sys may use the timestamp’s signature for trust rather than fully validating the main signature.
The grandfather code path appears to check for signature presence without verifying cryptographic correctness. RSA verification is computationally expensive—there may be optimization paths that skip it under certain conditions.
CI.sys internals
Key functions for anyone wanting to dig deeper:
CiValidateImageHeader is the entry point for driver validation, called from nt!SeValidateImageHeader. CiCheckSignedFile handles main signature validation and PKCS#7 parsing. MinCryptVerifySignedDataLMode does ASN.1 decoding and certificate chain building. I_MinCryptVerifyTimeStampSignature handles timestamp validation and the grandfather date comparison.
Search for the grandfather date bytes in CI.sys to find the relevant code paths:
00 00 A1 B9 D5 C6 D0 01
Mitigation status
Testing was done with Secure Boot enabled and HVCI enabled. Both passed. The only thing that stops this is the driver blocklist—if the specific certificate or file hash is blocklisted, loading fails.
This differs from typical DSE bypasses that require disabling Secure Boot. The grandfather code path appears to skip RSA validation regardless of system security configuration.
Microsoft probably won’t change this behavior. Thousands of legitimate enterprise drivers depend on it. The policy is intentional for legacy support. Their mitigation strategy is blocklisting specific abused certificates and gradually tightening requirements with each Windows release.
Summary
CI.sys validates: WIN_CERTIFICATE structure fields, PKCS#7 structural integrity, certificate chain, authenticode hash, timestamp presence and date.
CI.sys doesn’t validate (for grandfathered signatures with valid timestamps): the RSA signature bytes in SignerInfo.EncryptedDigest.
The timestamp is the trust anchor. Without a valid timestamp, most mutations fail. With a valid timestamp and grandfathered certificate chain, you can zero the cryptographic signature and the driver loads. Keep your driver blocklist updated—it’s the only mitigation that works here.