234 lines
13 KiB
Markdown
234 lines
13 KiB
Markdown
# 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:
|
|
|
|
```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.
|
|
|
|
## 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. It has not been published as
|
|
a supported Domito constant or a production default. 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:
|
|
|
|
```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.
|