13 KiB
CiValidateFileObject: validation flag 0x00000020
Issue #3 is documented; its TESTSIGNING-off runtime experiment is pending. A related signer-selection experiment has now run successfully on Windows 11 22631 with TESTSIGNING enabled (below). That run does not close the requested 19041/19045 flag 0 versus 0x20 TESTSIGNING-off comparison.
Meaning supported by the evidence
DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE is Domito's descriptive name for
0x00000020 in the second argument, ValidationFlags. It is not a recovered
Microsoft macro name. On the analyzed ci.dll 10.0.26100.8328 x64, the flag
causes CipGetAcceptedRootKeys to OR 0xA4FF into its accepted-root mask in the
branch where the global at RVA 0x45A88 is zero. Other global state, flags, and
the requested signing level also affect the result.
The assembly at preferred image base 0x180000000 establishes the operation:
0x180089e40: MOV EDX, 0xa4ff
0x180089e4c: BT ECX, EDI ; ECX = 0x8c in this branch; EDI = signing level
0x180089e4f: JC 0x180089e57
0x180089e51: TEST SIL, 0x20 ; saved ValidationFlags
0x180089e55: JZ 0x180089e59
0x180089e57: OR EBX, EDX ; broaden accepted-root mask
For requested level 0, flags 0, the branch above, and all other optional global
additions disabled, the mask is 0x80AA; setting only 0x20 makes it 0xA4FF
(additional bits 0x2455). This is an arithmetic witness, not a measurement of
the running OS's initialized policy. Levels 2, 3, and 7 already trigger the OR
through 0x8C. When the global at RVA 0x45A88 is nonzero, the helper returns
0xA4FF directly, so the flag need not change its result.
Later processing can still remove bit 0x8 when the helper's third argument is
nonzero and validation flag 0x00400000 is present. The surrounding
CipDisallowEmbeddedPicrootIfTrusted path can add that latter flag itself.
Thus 0xA4FF is not a promise about every final policy result.
The widened mask includes categories associated with test roots and unresolved chains as well as other roots. It must not be named "trust any Authenticode signer", "require Microsoft", or "enable TESTSIGNING". It changes the acceptance policy for this operation; it does not change the system boot setting. Further signature, chain, signing-level, and policy checks still matter. A successful call alone does not establish equivalence with WinVerifyTrust or the caller's intended publisher trust policy.
CipGetHashAlgorithmForLegacyScenario does not test 0x20: the flag is not a
fixed SHA-1/SHA-256 selector. Also distinguish this input flag from the
unrelated MINCRYPT_POLICY_INFO.PolicyBits output bit 0x20, associated with
the built-in DMD root category in this build.
Historical generation differences
The same numeric flag does not imply the same internal root mask across Windows versions. Static analysis of the extracted x64 corpus adds these bounded facts:
- On sampled
10.0.10240.16463(1507),10.0.10586.0(1511),10.0.14393.2189(1607) and10.0.15063.0(1703), the recoveredCipGetAcceptedRootKeysbodies have no directflag & 0x20broadening branch. They still test larger masks containing that bit, so this is not evidence that the flag has no effect anywhere in those verifiers. - On sampled
10.0.16299.125(1709) and10.0.17134.1130(1803), when the helper's main global mode is zero,flag & 0x20selects mask0x224FF(assignment), rather than the modernmask |= 0xA4FFoperation. A signing-level predicate can select the same mask. Other global additions occur afterward. The other global mode returns0x204FFdirectly.
The observed helper masks include branch-specific diagnostics/categories and must not be interpreted as an official stable root enum. The descriptive Domito constant names the intended broadening use without promising a single mask or identical behavior on every build.
The ISO-derived 10.0.19041.1 x64 helper provides direct static evidence for
the reported build family: in its main global mode it uses mask |= 0x24FF
when flag 0x20 or the signing-level predicate applies; the alternate mode
returns 0x24FF. With requested level zero and optional global additions
disabled, 0xAA becomes 0x24FF (added bits 0x2455). This is an arithmetic
witness, not a measurement of initialized OS policy. The exact CI SHA-256 is
2955710d34db26e352e62b20861ba338f0216cc36b378d128057be955e184282.
CodeIntegrity-RE retains matched-PDB pseudocode and assembly in
historical/ci-19041.1-contracts.json and historical/ci-19041.1-assembly.json,
with batch evidence ID
ev_13f89502777ee38ea98410f7a07f078bb934e78d792b662ab242f7b11d27e747.
Other 19041/19045 servicing revisions and the TESTSIGNING-off runtime workaround
still require their own evidence.
Historical records in CodeIntegrity-RE:
historical/ci-14393-contracts.json
ev_e63a4c4b7613e231795051b784b9571605b9bf81258b5dcd04d32ddc681abb41,
historical/ci-15063-contracts.json
ev_338658fb82559fa6daa8ae5487b90fe84b8e408ea7578feed6d8dff5d590e114,
historical/ci-16299-contracts.json
ev_fe5731175f0726f0d46af2f78ca615d284f8598939296815e26b5589db3001cf,
and historical/ci-17134-contracts.json
ev_0c3e3359b8b7486318006afa6b95eb2257a18abeec9041a86a32db487bcbe996.
Evidence and compatibility limits
The upstream report
describes success with flags 0x20, signing level 0, and TESTSIGNING off on
Windows builds 19041 and 19045. That is third-party runtime evidence, not a
reproduction by this project. Its behavior is consistent with broader root
acceptance; it does not establish identical internals across those builds.
Our static evidence uses REA 6.8.0 / Ghidra 12.1.3 and the binary SHA-256
c7d23814f3202c51fb23e3ca3d4328855787383cee5d14c4a986a13ec2aeda0b.
Public-symbol mapping uses ci.pdb GUID 1CAE574B-CF1F-3564-5F74-49567A128629, age 1.
The companion CodeIntegrity-RE repository retains:
ci-flag20-assembly-evidence.json: assembly evidenceev_ea763b9be7643a6241b0c805eae5f735a4b4a0a82df1383b1e68da4207f85fb4.evidence/CipGetAcceptedRootKeys.c: decompilationev_c905f03db9470c8b2d89a6295080143f25d5780ca0909de0ca554e40d078a3f3.evidence/CipGetHashAlgorithmForLegacyScenario.c:ev_addf8f1bc18da58db80db54eeab0dd78ec5ab88b219ece3e082ebd9a8e50b257.evidence/CipDisallowEmbeddedPicrootIfTrusted.c:ev_2475a5804fc87683a54b06acebd3785e5e8eb31f4f542f699fc2ff414c1ab7fe.evidence/CipValidateFileObjectByFileOrPageHash.c: capacity contractev_3897aa76c17cc64a30f60e72b994e695b88c8eda2ae6f246766bdc9314f06cca.
No complete enumeration of downstream uses of the flag, historical Microsoft name, or cross-version/architecture guarantee is claimed. The public declaration keeps the existing parameter widths. Names now describe the known inputs; on 26100 x64 the third argument is consumed as a byte. Narrowing the ABI everywhere requires separate evidence.
Kernel experiment prepared for the VM
Add tests/ci-file-object-probe.cpp to an existing signed test driver and add
include/ to its include path. Link the existing CI imports (ci.lib). Include
ntifs.h, Domito.MinCrypt.h, and tests/ci-file-object-probe.h at the call site.
Invoke DomitoProbeCiValidateFileObject at PASSIVE_LEVEL with a referenced
file object for the fixture and an array of DOMITO_CI_PROBE_RESULT_COUNT results.
Keep the file bytes stable and hold the object reference for the entire probe.
The surrounding driver owns opening/referencing/closing the file and exporting
the result records. The probe has no DriverEntry or installation side effects.
It makes six calls, always at requested level 0:
- Flags
0, then0x20, then0x20, then0, with 64-byte capacity each time. - Flags
0, then0x20, with declared capacity 63 (64-byte backing storage).
Each call uses fresh policies, hash capacity, algorithm, and signing-time storage. Both policy objects are freed after capture, including on failure. The records capture status, both policy statuses/bits, chain presence, signing time, digest algorithm/length/bytes, and a trailing buffer guard. The digest is a file hash, not a certificate thumbprint. Failed-call outputs are raw diagnostics, not valid verification results. Chain internals are deliberately not dereferenced.
The repeated order helps reveal cache/order effects but does not eliminate them.
Also run the reversed initial order in a fresh VM snapshot when comparing policy.
For the analyzed 26100 helper, both 63-byte-capacity calls should return
STATUS_BUFFER_TOO_SMALL and set length 64, regardless of the eventual algorithm.
Treat older-build differences as observations to investigate, not silently as
test success. Every guard must remain intact; successful hash sizes must be at
most 64. Inspect non-success statuses rather than treating all failures as
invalid signatures (I/O and resource errors are possible).
Run on Windows 10 19041 and 19045 and a current Windows 11 build. The decisive reproduction uses a driver loadable with TESTSIGNING off. Record that setting, Secure Boot, HVCI/WDAC policy, OS build, CI version/hash, driver identity, fixture SHA-256, and each complete result. Keep policy constant between paired calls. Use these fixtures:
- A known third-party RSA Authenticode executable (ideally the reported fixture).
- A Microsoft-signed executable and a catalog-signed file as controls.
- An unsigned executable and a copy of the signed file with a byte changed in a hashed section, leaving its certificate table unchanged.
- A self-signed/test-root fixture to characterize the broader acceptance policy.
- If available, timestamped, expired, revoked, and ECC variants, with expectations recorded in advance and revocation/configuration differences retained.
Compare with signtool verify /pa /v as a separate user-mode policy observation,
not an equality oracle. The report is reproduced only when the same valid
third-party fixture changes from rejection at 0 to success at 0x20, under the
reported environment, with usable outputs. Unchanged success can mean the
existing policy already accepts it. Unchanged failure is a result to investigate.
Unsigned and tampered fixtures must not become accepted as valid signatures.
Self-signed acceptance must be reported explicitly, not confused with public-CA
publisher trust. Do not use this flag as a new production default based only on
one positive fixture.
Multiple signatures: a separate kernel experiment
0x20 is an accepted-root control, not a recovered "PE image" type flag.
In the examined 22621.2792 and modern 26100 x64 bodies it leaves the
preferred-signing-level search active. PolicyInfo can therefore describe a
nested Microsoft signature even when the primary company signature also verifies.
The first-success guard tests aggregate masks 0xE47FFF87 (22621.2792) and
0xE41FFF87 (modern 26100). 0x20 intersects neither; 0x80 intersects both.
Compare flags 0x20 versus 0xA0, requested signing level zero, on the same
stable dual-signed file. Record the actual selected leaf/thumbprint, both policy
statuses and signing time. Do not interpret status success alone as selection
of the company certificate. First successful does not necessarily mean first
listed: CI may reject the primary and continue.
0x80 has additional policy and fallback effects. Domito exposes it only as
DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL and through the opt-in
DomitoCiSignatureSelectionFirstSuccessfulExperimental mode in
DomitoValidateFileObjectEx. It is not a production default or a stable signer
index. See the validation helper contracts. The companion research
SIGNATURE-SELECTION.md provides the full static evidence and an alternative
primary-signature path using CiCheckSignedFile.
A user-run kernel probe on Windows 11 10.0.22631 x64, CI options 0x00080207
(TESTSIGNING enabled), confirms the selection difference for the exact dual-signed
BthPS3 fixture: 0x20 returns Microsoft, while 0xA0 returns the Nefarius leaf
FF0CCF318FD8C14775023A0B52C28FFCCA59D7F7 and signing time
2026-10-02T18:32:01.000Z. Direct primary SHA-256 CiCheckSignedFile returns
the same Nefarius leaf, signer/timestamp policies and signing time. All three
calls return STATUS_SUCCESS; all policy VerificationStatus fields also succeed.
The file digest is identical across calls, before/after file hashes match, and
TraceFmt reports 149 events with no losses, format errors or unknown formats.
The companion signature-selection/runtime-22631-bthps3.json records exact
trace hashes and the on-disk CI hash. This is one test-signing-enabled run;
TESTSIGNING-off, other builds/policies and general signature enumeration remain
unverified. 0xA0 does not mean "select any desired publisher".
Checks available without the VM
From an x64 Visual Studio developer shell:
python tests\run-ci-probe-tests.py
This compiles the actual probe body with mock CI calls and constants extracted from the public header. It verifies call order, input forwarding, fresh capacity and outputs, result capture, and freeing both policies after success and failure. It does not emulate the flag's trust semantics. Kernel compilation checks the real headers separately; neither compilation nor mock tests substitute for the VM run above.