152 lines
8.3 KiB
Markdown
152 lines
8.3 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.
|
|
|
|
## 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.
|