212 lines
12 KiB
Markdown
212 lines
12 KiB
Markdown
# Code Integrity and mincrypt contracts
|
||
|
||
The default header retains the existing structure layouts and calling shapes.
|
||
Parameter names and ownership comments now distinguish the known controls from
|
||
the verification result. `VerificationStatus` is an `NTSTATUS` with the same
|
||
32-bit storage. The additional `Domito.MinCrypt.26100.h` header is an **opt-in
|
||
description of ci.dll 10.0.26100.8328 x64**. It neither adds imports nor extends
|
||
the default structures on every Windows target.
|
||
|
||
The evidence is static REA/Ghidra analysis of SHA-256
|
||
`c7d23814f3202c51fb23e3ca3d4328855787383cee5d14c4a986a13ec2aeda0b`, with
|
||
Microsoft ci.pdb GUID `1CAE574B-CF1F-3564-5F74-49567A128629`, age 1 supplying the
|
||
address/name map. The companion CodeIntegrity-RE repository retains the full
|
||
records and readable `evidence/` files. These observations do not establish
|
||
older-Windows or ARM64 ABI compatibility. Compile-target `NTDDI_VERSION` is not
|
||
runtime layout negotiation.
|
||
|
||
## Results and trust policy
|
||
|
||
Check the API's returned `NTSTATUS` first. `PolicyInfo.VerificationStatus`,
|
||
`PolicyBits`, signer details, and timestamps supply additional observations.
|
||
Successful chain processing does not imply that the caller's file digest matched
|
||
the signed digest; the comparison can occur afterward. Diagnostic policy data
|
||
may exist on failure. An extracted publisher name is display information and
|
||
does not establish publisher identity or signer trust.
|
||
|
||
Define the policy the application needs: embedded signature validation,
|
||
membership in an accepted installed catalog, an expected publisher/key, or a
|
||
Windows signing scenario. Preserve which path produced a result. A nonzero
|
||
`PolicyBits` value is not automatically failure, and any one root bit is not a
|
||
complete trust decision. File bytes and object identity must remain tied to the
|
||
bytes the application will subsequently use.
|
||
|
||
The [file-object flag document](ci-validate-file-object.md) covers `0x20`, hash
|
||
capacity, signing level, and its pending VM experiment. Other observed flag
|
||
effects on this build are:
|
||
|
||
- `0x1` selects hash scenario 5.
|
||
- Otherwise `0x2` selects scenario 4 for signing levels <=4; higher levels use
|
||
the level mapping.
|
||
- Otherwise `0x80000000` selects scenario 5 for levels <=4; higher levels use
|
||
the level mapping. It takes precedence over `0x4`.
|
||
- Otherwise `0x4` selects scenario 1; without these choices the signing level
|
||
maps to a scenario.
|
||
- `0x00400000` suppresses accepted-root bit `0x8` when the relevant helper mode
|
||
is enabled; CI can add this flag internally. Bits `0x2` and `0x8` also affect
|
||
later signing-level/EKU restrictions.
|
||
|
||
The scenarios resolve through initialized signing-policy state. Static defaults
|
||
are not a guarantee of a running system's algorithm, and the flags should not
|
||
be named as universal fixed SHA selectors. Unknown bits remain unknown. Evidence:
|
||
`CipGetHashAlgorithmForLegacyScenario`, `CipGetAcceptedRootKeys`, and
|
||
`CipMinCryptToSigningLevel` in the research bundle.
|
||
|
||
## Ownership and initialization
|
||
|
||
Zero-initialize both `MINCRYPT_POLICY_INFO` output objects before their first
|
||
use. Free both with `CiFreePolicyInfo` after use and before reuse, including
|
||
failure paths. Never call the destructor on arbitrary uninitialized stack data.
|
||
Do not clear a nonzero `Size` while a chain is still owned: the observed cleanup
|
||
helper skips an object whose `Size` is zero.
|
||
|
||
`ChainInfo` owns one allocation. Public-key blobs, names, encoded certificates,
|
||
EKUs, attributes, and chain elements are borrowed views into it. Do not free
|
||
children separately or retain them after releasing the owner. Copying the policy
|
||
structure with `memcpy` creates two aliases to one allocation, not independent
|
||
ownership. CI's internal deep-copy helper uses the total allocation size and
|
||
rebases interior pointers; Domito does not expose that helper.
|
||
|
||
The default `CiFreePolicyInfo` declaration retains its legacy `PVOID` return to
|
||
avoid a broad source compatibility change. **Ignore that value**: observed x64
|
||
assembly provides no defined pointer result. The opt-in build-specific function
|
||
pointer type uses `VOID`.
|
||
|
||
`CatalogName` is a separate `RtlDuplicateUnicodeString` allocation in the observed
|
||
catalog export. Initialize it empty, release a returned buffer with
|
||
`RtlFreeUnicodeString`, and release both policies separately. Publisher-name
|
||
storage is allocated through the caller's callback and must be released with
|
||
that allocator's matching free operation, including an allocated buffer on a
|
||
later conversion failure. It is not owned by either policy object.
|
||
|
||
Evidence: `MincryptFreePolicyInfo`, `MincryptCopyPolicyInfo`,
|
||
`I_MincryptAddChainInfo`, `CiVerifyHashInCatalog`, and `CiGetCertPublisherName`.
|
||
|
||
## Digest-based verification inputs
|
||
|
||
`CiCheckSignedFile` consumes a caller-computed file digest. It does not reopen
|
||
the file or compute its Authenticode hash. On the observed build it accepts
|
||
SHA-1/256/384/512 with exactly 20/32/48/64 bytes respectively. A wrong algorithm,
|
||
length, or digest fails the final comparison.
|
||
|
||
Its signature input points to a `WIN_CERTIFICATE` header. The observed wrapper
|
||
requires 4-byte pointer alignment, supplied size >=12, revision `0x200`, type 2,
|
||
and `8 < dwLength <= supplied size`. The PKCS#7 payload begins at header+8.
|
||
The wrapper selects the first certificate-table entry. Multiple table entries
|
||
and nested signatures are separate concepts; this is not a complete signature
|
||
enumeration interface.
|
||
|
||
These exports remain implemented on the analyzed Windows 11 build. The older
|
||
header's “Win7–8.1 only” comments described a workflow, not verified export
|
||
availability. Exact first-available builds and pre/post-KB3033929 ABI changes
|
||
still require matching historical binaries. Import-library symbol names and
|
||
stdcall stack-byte counts alone do not settle parameter semantics.
|
||
|
||
## Catalog controls
|
||
|
||
`ReloadCatalogs` controls catalog-store reload and related context setup.
|
||
`RequireTrustedCatalog` is a nonzero gate requiring an observed catalog extension
|
||
marker: its matching bytes include `PE`, integer `0x10001`, and `TRUSTED`.
|
||
The marker is an additional catalog condition, not proof of a Microsoft signer.
|
||
|
||
`AcceptedCatalogPolicyBits` accepts a candidate when
|
||
`(RequestedMask & Catalog.PolicyInfo.PolicyBits) != 0`: **any overlap** is enough.
|
||
It neither enables policy flags nor requires every requested bit. The existing
|
||
Domito compatibility value is named `DOMITO_CI_LEGACY_CATALOG_POLICY_BITS`
|
||
(`0x2007F`). It includes test/DMD categories and diagnostic bit `0x20000`, while
|
||
omitting other categories. It is retained as an existing behavior, not promoted
|
||
as a universally safe trust mask. Establish and test the application's accepted
|
||
signer policy before changing it.
|
||
|
||
Observed file-hash lookup supports SHA-1 and SHA-256; the internal path has no
|
||
SHA-384/512 file-hash case. Embedded-signature algorithm support and catalog
|
||
lookup support are different capabilities. Catalog-only files need an explicit,
|
||
tested algorithm strategy rather than assuming SHA-1 from an absent embedded
|
||
table. Catalog stores, caches, silo/process context, and signing policy affect
|
||
results. The new `DomitoValidateFileLegacyModeEx` forwards a caller-selected mask and
|
||
marker gate and reports embedded/catalog origin. Its compatibility wrapper retains
|
||
the existing mask. See [digest and workflow details](authenticode-digests.md).
|
||
|
||
Domito's older-Windows helper keeps its system-process attachment until
|
||
historical runtime evidence supports changing it.
|
||
|
||
Evidence: `I_FindFileOrHeaderHashInLoadedCatalogs` and its assembly
|
||
`ev_c42e7ee76099d55e7cf7cdd3d9f79e99030ba637680365612afb4906694ae3a8`,
|
||
`I_ParseCatalog`, and marker matcher
|
||
`ev_a1d3a5844a2b6be6c16175e307956c9819d0b64a4fe70216f292c12e4ea38d82`.
|
||
|
||
## Opt-in x64 26100 structure observations
|
||
|
||
The version header has distinct type names and 14 compile-time layout assertions.
|
||
Use it only after independently establishing the matching runtime build and
|
||
architecture. A large `ChainInfo->Size` alone does not identify the header: it
|
||
includes variable trailing data, so an older allocation could also be large.
|
||
Only after layout identity is established may a size check protect a field read.
|
||
|
||
- `DOMITO_CI26100_NAME_BLOB`: pointer at +0, byte length at +8, ASN.1 string tag
|
||
at +10. Names are not necessarily NUL-terminated. Observed conversion handles
|
||
UTF8String tag `0x0C` and BMPString `0x1E`; `PCHAR` in the default declaration
|
||
must not be read as a guarantee of ANSI encoding.
|
||
- `DOMITO_CI26100_CHAIN_INFO`: `Size` is total allocation bytes; fixed header is
|
||
104 bytes. Root ID at +0x34 is an opaque integer, not policy bits. The 32 bytes
|
||
at +0x48 are a platform-manifest binary ID from an authenticated attribute,
|
||
not a file digest or certificate thumbprint. Zero/nonzero does not by itself
|
||
prove trust. Require the matching layout and `Size >=104` before reading it.
|
||
- `DOMITO_CI26100_POLICY_INFO`: 48 bytes; `NotBefore`/`NotAfter` come from the
|
||
signer certificate validity fields, not an intersection of the whole chain.
|
||
Interpret `RevocationTime` only with the relevant result. A zero signing time
|
||
is not a real timestamp to display as a signing date.
|
||
|
||
Construction/copy evidence: `ev_d7110cfc711074a4af1874b5c8e701c13bcc3dc58a1596c64dca4ca98966c79e`,
|
||
`ev_858afecf2170484325379b6a8bc307fb72dad2f9a5a765169192c7363a3386af`,
|
||
`ev_f82cbee977715399323bb398781c125a9d19b4ea29acd05063ec55b711cfc670`.
|
||
|
||
## Partial policy-bit dictionary
|
||
|
||
Names in the opt-in header are Domito descriptions of observed 26100 branches,
|
||
not official Microsoft constants or a universal trusted-root enumeration.
|
||
|
||
Root/provenance categories include `0x1` self-issued terminal path, `0x2` selected
|
||
built-in Microsoft roots, `0x4` test roots, `0x8` the built-in root associated with
|
||
embedded picroot handling, `0x10` tolerated partial/unresolved chain, `0x20` DMD,
|
||
`0x40` DMD test, `0x80` another built-in Microsoft root, `0x400` flight root,
|
||
`0x2000` third-party marketplace, `0x4000` explicit supplied certificate/key
|
||
acceptance, and `0x8000` Microsoft Identity Root 2020. `0x800` and `0x1000` are
|
||
additional discriminators on some root entries; their independent meanings
|
||
remain unresolved. These categories do not establish a full trust result.
|
||
|
||
Observed diagnostic branches set `0x20000` chain failure, `0x40000` invalid
|
||
cryptographic/signature data, `0x80000` unmet EKU, `0x100000` page-hash failure,
|
||
`0x200000` revocation, `0x400000` certificate validity failure (including
|
||
not-yet-valid), `0x08000000` path-length constraints, `0x10000000` generic fallback,
|
||
and `0x20000000` CA/BasicConstraints failure. `0x04000000` marks a no-usable-
|
||
timestamp branch during revoked-certificate recovery, not universal timestamp
|
||
absence. `0x50000` and `0x60000` are observed composites; do not infer a unique
|
||
meaning for every constituent bit. Timestamp-before-revocation processing can
|
||
clear a revocation bit. Internal checks of high bits are not a replacement for
|
||
the API status and application policy.
|
||
|
||
## Remaining runtime and API coverage
|
||
|
||
The missing page-hash exports have recovered argument shapes in the research
|
||
report. Their normalized allocation uses a 24-byte x64 header and 24-byte entries
|
||
even for stronger source algorithms. Field meaning, truncation intent, source
|
||
terminal-entry handling, and a supported destructor remain open. `CiFreePolicyInfo`
|
||
is not a demonstrated destructor for that allocation. No universal page-hash SDK
|
||
or static import is added until those ownership and cross-version contracts can
|
||
be tested.
|
||
|
||
`CiGetPEInformation` is a protected-media dispatcher, not a general PE parser.
|
||
`CiInitialize` is initialization machinery. `CiSetTrustedOriginClaimId` writes a
|
||
CI global; it is not an ordinary per-file trust selector. Ordinal-only exports
|
||
and other initialization interfaces require their own evidence and callers.
|
||
|
||
Still required: historical/ARM64 layout and parameter verification, real CI
|
||
output lifetimes and retry behavior under failures, timestamp propagation,
|
||
revocation freshness/network policy, weak-crypto selection, multiple/nested
|
||
signatures, catalog-only algorithms, and extension of the bounded digest subset to additional PE layouts.
|
||
The explicit digest helper now has local Windows SIP and real embedded-digest
|
||
comparisons; this does not close those historical or unusual-layout cases. Run the prepared probes in disposable VMs with exact OS,
|
||
CI binary, configuration, and fixture identities. Compilation and host mocks do
|
||
not close those runtime questions.
|