Add explicit Authenticode workflows and correct CI helper contracts
This commit is contained in:
1 parent
1026c48324
commit
7aa23f50be
18 files changed
+1952
-185
No files matched your search
@@ -0,0 +1,211 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user