12 KiB
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 covers 0x20, hash
capacity, signing level, and its pending VM experiment. Other observed flag
effects on this build are:
0x1selects hash scenario 5.- Otherwise
0x2selects scenario 4 for signing levels <=4; higher levels use the level mapping. - Otherwise
0x80000000selects scenario 5 for levels <=4; higher levels use the level mapping. It takes precedence over0x4. - Otherwise
0x4selects scenario 1; without these choices the signing level maps to a scenario. 0x00400000suppresses accepted-root bit0x8when the relevant helper mode is enabled; CI can add this flag internally. Bits0x2and0x8also 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.
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 tag0x0Cand BMPString0x1E;PCHARin the default declaration must not be read as a guarantee of ANSI encoding.DOMITO_CI26100_CHAIN_INFO:Sizeis 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 andSize >=104before reading it.DOMITO_CI26100_POLICY_INFO: 48 bytes;NotBefore/NotAftercome from the signer certificate validity fields, not an intersection of the whole chain. InterpretRevocationTimeonly 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.