Files
Domito/docs/ci-validate-file-object.md
T

198 lines
11 KiB
Markdown

# CiValidateFileObject: validation flag 0x00000020
Issue #3 is **documented; kernel runtime verification is pending**. The probe
builds and its plumbing is covered by host tests, but those tests do not execute
Windows Code Integrity. Do not close the runtime-testing portion on that basis.
## 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:
```text
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) and `10.0.15063.0` (1703), the recovered
`CipGetAcceptedRootKeys` bodies have no direct `flag & 0x20` broadening 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) and `10.0.17134.1130` (1803), when the
helper's main global mode is zero,
`flag & 0x20` selects mask `0x224FF` (assignment), rather than the modern
`mask |= 0xA4FF` operation. A signing-level predicate can select the same mask.
Other global additions occur afterward. The other global mode returns
`0x204FF` directly.
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](https://github.com/Ido-Moshe-Github/CiDllDemo/issues/5#issuecomment-2725851295)
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 evidence
`ev_ea763b9be7643a6241b0c805eae5f735a4b4a0a82df1383b1e68da4207f85fb4`.
- `evidence/CipGetAcceptedRootKeys.c`: decompilation
`ev_c905f03db9470c8b2d89a6295080143f25d5780ca0909de0ca554e40d078a3f3`.
- `evidence/CipGetHashAlgorithmForLegacyScenario.c`:
`ev_addf8f1bc18da58db80db54eeab0dd78ec5ab88b219ece3e082ebd9a8e50b257`.
- `evidence/CipDisallowEmbeddedPicrootIfTrusted.c`:
`ev_2475a5804fc87683a54b06acebd3785e5e8eb31f4f542f699fc2ff414c1ab7fe`.
- `evidence/CipValidateFileObjectByFileOrPageHash.c`: capacity contract
`ev_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:
1. Flags `0`, then `0x20`, then `0x20`, then `0`, with 64-byte capacity each time.
2. Flags `0`, then `0x20`, 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.
## Checks available without the VM
From an x64 Visual Studio developer shell:
```powershell
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.