Compare commits
4
Commits
a491bbd3f8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dd000e2f7 | ||
|
|
edf3eeb4ac | ||
|
|
527430e2b0 | ||
|
|
3fbfe31d15 |
No files matched your search
@@ -0,0 +1,55 @@
|
||||
# Domito contributor guidance
|
||||
|
||||
Applies to this repository and its subdirectories.
|
||||
|
||||
## Purpose and boundaries
|
||||
|
||||
- Domito is a kernel-mode SDK for Windows Code Integrity and mincrypt APIs. Preserve usable C exports and types, source compatibility and native ABI accuracy.
|
||||
- Implementation sources may use modest C++ conveniences. Public headers and examples must remain consumable from kernel-mode C without WDF/DMF dependencies.
|
||||
- Reuse CI and Windows cryptographic primitives. Keep signature verification separate from application authorization, publisher allowlists, root pins, request caching, developer bypasses and expiry policy.
|
||||
- Never copy private consumer implementations, identifiers, paths, revisions, publisher strings or pins into source, tests, documentation or commit messages. Use independently written generic code and synthetic/public fixtures.
|
||||
- Keep changes focused. Additional kernel experiments and broader research belong in follow-up work unless requested.
|
||||
|
||||
## Code style and notation
|
||||
|
||||
- Follow the existing library style in `src/Domito.cpp`, `src/Domito.Memory.cpp` and surrounding declarations. Apply it to new helpers, tests and C documentation examples as well.
|
||||
- Use four spaces, Allman braces and braces for every control-flow body. Expand conditionals and cleanup; do not put multiple statements or returns on one line.
|
||||
- Put return types on their own line, followed by the function name. Use multiline parameter lists with one parameter per line and the closing parenthesis on its own line. Keep SAL annotations, linkage and return types on separate lines.
|
||||
- Declare variables separately, use descriptive local names and separate logical operations with blank lines. Avoid compressed declaration lists, opaque abbreviations and unnecessary `auto` where the concrete type clarifies ownership.
|
||||
- Use `Domito` function names, `DOMITO_*` public type/constant names, PascalCase parameters and lowerCamelCase locals. Preserve native `MINCRYPT_*` declarations and existing public names rather than introducing cosmetic ABI/API churn.
|
||||
- Match nearby pointer, cast and initialization conventions. C examples must use C-compatible syntax. Preserve UTF-8, CRLF and existing BOMs when editing files.
|
||||
- Write comments about contracts, ownership and non-obvious reasoning. Distinguish recovered analyst names from official Windows terminology; avoid repetitive implementation narration.
|
||||
- Prefer readable, normally formatted Python runners. Do not compress setup, subprocess invocation or assertions into one-liners.
|
||||
|
||||
## API and ownership contracts
|
||||
|
||||
- Declare accurate SAL/IRQL requirements in public headers. Use `_Use_decl_annotations_` for corresponding definitions when appropriate. Validation, file I/O and certificate helpers run at PASSIVE_LEVEL; synchronous I/O also needs special kernel APCs enabled.
|
||||
- Return useful NTSTATUS values. Initialize outputs before fallible work, publish owning outputs only when valid, and handle partially populated outputs on failure.
|
||||
- Zero-initialize owning CI results before first use. Release both signer and timestamp policies, including diagnostic allocations on failure, and clean old results before reuse. Never copy an owning result into a second owner.
|
||||
- Borrowed certificate, SPKI and chain views expire with their backing buffer or policy. Do not free child blobs separately. CI hash outputs are file digests, not certificate thumbprints.
|
||||
- Pair each allocation with its documented destructor. Configurable Domito allocator pairs must remain compatible and unchanged while calls or callback-backed allocations are outstanding; replacement is not synchronized.
|
||||
- Keep caller-owned handles open and balance object references. KernelMode handle references require trusted handles; never feed unchecked user handles into them. Path/name lookup does not prove original process-image or in-memory identity.
|
||||
- Module/export lookup does not pin a module. Require valid loader-mapped input and caller-provided module lifetime; reject unsupported forwarders.
|
||||
- Preserve legacy entry points where compatibility requires them. Mark superseded functions/types with `__declspec(deprecated("migration guidance"))` when an actual safer replacement exists. Name the replacement and semantic changes. Do not deprecate raw native ABI types just because a convenience view exists.
|
||||
- Suppress C4996 only locally for intentional compatibility code/tests, never globally. Keep deprecation compiler checks effective for consumers.
|
||||
|
||||
## CI, PE and certificate correctness
|
||||
|
||||
- Consult `docs/mincrypt-contracts.md`, `docs/ci-api-compatibility.md`, `docs/ci-validate-file-object.md`, `docs/authenticode-digests.md` and the helper contracts before changing native assumptions.
|
||||
- Export presence and compile-time NTDDI guards do not establish runtime ABI compatibility. Chain allocation `Size` is not a layout version. Historical and architecture-specific views need explicit evidence; unknown layouts must not be guessed.
|
||||
- `0x20` broadens accepted roots in examined CI builds; it is not a generic PE-image marker. `0x80`/first-success selection remains experimental, has other policy effects and does not select an arbitrary publisher/signature index.
|
||||
- Preserve explicit algorithm, flag, signing-level, catalog and selection choices. Do not silently add root-acceptance flags, retry weaker algorithms, fall back to catalogs or change signer-selection policy.
|
||||
- Check API status separately from policy verification status and application trust requirements. A nonzero signing time alone does not establish the intended signer or trust policy.
|
||||
- Bound PE/DER parsing and arithmetic before reading or allocating. Preserve the documented supported PE subset instead of claiming a universal Authenticode/SIP parser. Exact reads must complete before cleanup and must reject short reads.
|
||||
- Make certificate hash representation explicit: entire certificate DER, complete SPKI DER TLV, or legacy SPKI content excluding its outer tag/length. They produce different pins. Reject malformed hex; do not map invalid characters to zero.
|
||||
- Bounded chain access is not independent cryptographic chain validation. Do not promise that the last represented certificate is a trusted/self-signed root or impose bytewise issuer/subject equality as a universal CI invariant.
|
||||
|
||||
## Validation and evidence
|
||||
|
||||
- Follow `tests/README.md` for the six host runners. Preserve extraction of actual production code; formatting changes must not silently replace it with a copied implementation in tests.
|
||||
- Use meaningful negative, truncation, capacity, ownership and failure-path checks. For deprecation changes verify C4996 and modern controls in C/C++ on x64/ARM64.
|
||||
- Compile affected library sources and public C clients with the WDK for x64/ARM64; retain `/kernel /W4 /WX` and check relevant layouts. Run static analysis when contracts or cleanup change.
|
||||
- Host doubles, Windows cryptographic comparisons and compilation do not establish real kernel CI policy, runtime ABI, failure ownership or enforcement behavior. Report those limits explicitly.
|
||||
- Use the separate research repository/probe for runtime evidence. Record exact OS/CI/fixture identities, enforcement settings, both policy statuses, selected certificate, signing time and trace health. Do not generalize one TESTSIGNING-enabled run to other builds or TESTSIGNING-off systems.
|
||||
- Keep binaries, PDBs, private keys, raw machine traces and temporary build outputs out of source commits. Do not load a driver or change trust stores, boot policy or enforcement on a development host as part of ordinary build/test work.
|
||||
- Keep `docs/implementation-status.md` current when evidence changes a support claim. Summarize completed checks and remaining runtime work without presenting pending experiments as verified behavior.
|
||||
+3
-2
@@ -4,14 +4,15 @@
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_PropertySheetDisplayName>Domito Kernel Library</_PropertySheetDisplayName>
|
||||
<DomitoRootPath Condition="'$(DomitoRootPath)' == ''">$(SolutionDir)..\Domito</DomitoRootPath>
|
||||
<DomitoRootPath Condition="'$(DomitoRootPath)' == ''">$(MSBuildThisFileDirectory)</DomitoRootPath>
|
||||
<DomitoLibraryPlatform Condition="'$(DomitoLibraryPlatform)' == ''">$(Platform)</DomitoLibraryPlatform>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(DomitoRootPath)\include;$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies Condition="'$(DriverType)'=='KMDF'">$(DomitoRootPath)\lib\$(DDKPlatform)\$(Configuration)\Domito.lib;$(DomitoRootPath)\lib\$(DDKPlatform)\ci.lib;$(DDK_LIB_PATH)cng.lib;%(AdditionalDependencies);</AdditionalDependencies>
|
||||
<AdditionalDependencies Condition="'$(ConfigurationType)' != 'StaticLibrary' And ('$(DriverType)' == 'KMDF' Or '$(DriverType)' == 'WDM')">$(DomitoRootPath)\lib\$(DomitoLibraryPlatform)\$(Configuration)\Domito.lib;$(DomitoRootPath)\lib\$(DomitoLibraryPlatform)\ci.lib;cng.lib;%(AdditionalDependencies);</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup />
|
||||
|
||||
@@ -1,16 +1,76 @@
|
||||
# CI APIs
|
||||
# CI API availability observations
|
||||
|
||||
## Available starting with 1511
|
||||
See the [CI API compatibility matrix](docs/ci-api-compatibility.md) for the
|
||||
per-API tables covering 20 VM, ISO and modern x64/ARM64 samples, with exact
|
||||
versions, file hashes and machine-readable results. The six-sample notes below
|
||||
describe the original VM corpus and its subsequent ISO follow-up.
|
||||
|
||||
- CiCheckSignedFile
|
||||
- CiFindPageHashesInCatalog
|
||||
- CiFindPageHashesInSignedFile
|
||||
- CiFreePolicyInfo
|
||||
- CiGetPEInformation
|
||||
- CiInitialize
|
||||
- CiValidateFileObject
|
||||
- CiVerifyHashInCatalog
|
||||
These are export-table observations from six extracted x64 `ci.dll` files, not
|
||||
claims about every servicing revision or a proof of the original introduction
|
||||
build. The companion CodeIntegrity-RE `corpus-index.json` records identities,
|
||||
extraction aliases, manifest checks, and metadata. VM directory names are labels;
|
||||
versions below come from the PE version resources. No target was executed.
|
||||
|
||||
## Added in 1709
|
||||
## Named exports common to all six samples
|
||||
|
||||
- CiSetTrustedOriginClaimId
|
||||
- `CiCheckSignedFile`
|
||||
- `CiFindPageHashesInCatalog`
|
||||
- `CiFindPageHashesInSignedFile`
|
||||
- `CiFreePolicyInfo`
|
||||
- `CiGetPEInformation`
|
||||
- `CiInitialize`
|
||||
- `CiValidateFileObject`
|
||||
- `CiVerifyHashInCatalog`
|
||||
|
||||
`CiValidateFileObject` is already present in sampled Windows 10 **1507**
|
||||
`10.0.10240.16463`. The old “starting with 1511” list was therefore too narrow.
|
||||
Export presence does not establish a complete compatible parameter/structure
|
||||
contract; consult the per-build static analysis before using it.
|
||||
|
||||
## Additional names and absent names
|
||||
|
||||
`CiSetTrustedOriginClaimId` is present in sampled `10.0.15063.0` (**1703**) and
|
||||
in the sampled 1709/1803 files. It is absent from the sampled 1507,1511,1607
|
||||
export tables. This corrects the old “added in 1709” note, without claiming the
|
||||
first Windows build on which the API ever existed.
|
||||
|
||||
`CiGetCertPublisherName` is absent from **all six** sampled 1507–1803 export
|
||||
tables. The ISO follow-up also finds it absent in x64 `10.0.19041.1`, present
|
||||
in x64 `10.0.19041.1865` and `10.0.22000.318`, and the original investigation
|
||||
finds it present in `10.0.26100.8328`. This brackets Windows 10 servicing
|
||||
observations without identifying the first update adding the export. The
|
||||
CodeIntegrity-RE `iso-corpus-index.json` records the exact files and hashes.
|
||||
The Windows 10 ARM64 21H2 sample additionally exports it in CI
|
||||
`10.0.19041.1566`; this does not establish x64 availability at that revision.
|
||||
The matrix keeps architecture-specific observations separate.
|
||||
A header declaration,
|
||||
import-library symbol, or `NTDDI_VERSION >= NTDDI_WIN10` alone does not make an
|
||||
unconditional static import loadable on the older samples. Callers supporting
|
||||
those samples must avoid that import or use optional resolution with an
|
||||
explicit unavailable/fallback path. `CiSetTrustedOriginClaimId` likewise cannot
|
||||
be assumed available on every Windows 10 build; it is a CI-global setter.
|
||||
|
||||
The original modern x64 sample has display `FileVersion` **26100.8328**, but
|
||||
its numeric fixed version is **26100.9444**. The matrix records both under the
|
||||
same SHA-256 identity and uses numeric fixed versions in its tables.
|
||||
|
||||
## Exact sampled files
|
||||
|
||||
- Windows 10 1507: `10.0.10240.16463` x64, SHA-256
|
||||
`e83d900f4675c6e6ffc3fb7dcd3074610876a1398e495796769988df810fd3bc`.
|
||||
- Windows 10 1511: `10.0.10586.0` x64, SHA-256
|
||||
`f949bfa614addea6174b28c3818c56b327c9290fd41bd91dfc545414b3df6783`.
|
||||
- Windows 10 1607: `10.0.14393.2189` x64, SHA-256
|
||||
`ef0c02ecbd0818e84be9f61421f919825f96a3c59199460cab08548eee2881df`.
|
||||
- Windows 10 1703: `10.0.15063.0` x64, SHA-256
|
||||
`3705aaf408f05326e028621380aa5c96a1f3583a2de8a139b188da4b82577863`.
|
||||
- Windows 10 1709: `10.0.16299.125` x64, SHA-256
|
||||
`1db7ae5a08bd2361fc7fcdd373fb980e8d680035e67b02e5967095db0443effd`.
|
||||
- Windows 10 1803: `10.0.17134.1130` x64, SHA-256
|
||||
`8ce3b8fe38c23b699f30b045383640019a429d3a9d513285c655bffe03a796d9`.
|
||||
|
||||
Matching Microsoft public PDB GUIDs/ages were verified in
|
||||
`historical/ci-<build>-symbols.json` in CodeIntegrity-RE. Public names help map
|
||||
functions; they are not a full typed SDK. The default Domito declarations remain
|
||||
conservative. See [current contracts and limits](docs/mincrypt-contracts.md) and
|
||||
[implementation status](docs/implementation-status.md) for ABI and runtime work.
|
||||
@@ -24,12 +24,28 @@ Built for and tested on **Windows 10 version 1507 (or newer) x64/ARM64**. 32-Bit
|
||||
- I do not want to force any consumer of the library to drag C++ paradigms into their project. I do expect the user to utilize a modern compiler though, so the library sources themselves may have some 'C++-ish touches' here and there, for my own convenience 😉
|
||||
- Compatibility with every Windows 10 version.
|
||||
- APIs not available on older builds will give you a `STATUS_NOT_IMPLEMENTED` instead of hard-linking and therefore making your driver fail to load 🤞
|
||||
- **Caution:** this claim comes with an asterisk though; due to PatchGuard (or some other security mechanism) I couldn't get run-time dynamic linking for `ci.dll` exports to work, so you need to actively avoid implementing code depending on exports that do not exist on earlier versions of Windows. Once I have a compatibility matrix for all of them I'll update the documentation accordingly.
|
||||
- CI entry points are currently static imports from `ci.lib`; consumers must avoid imports unavailable on their target Windows builds. The [CI API compatibility matrix](docs/ci-api-compatibility.md) records all ten named exports across 20 exact Windows 10/11 x64 and ARM64 CI files. Export lookup uses the correct name-ordinal mapping and rejects forwarders. Runtime ABI verification remains separate; see the remaining-work ledger below.
|
||||
- No conflicts with WDF or DMF
|
||||
- The consuming driver *may* (but doesn't have to) utilize Microsoft WDF or DMF in addition without having to fear any incompatibilities 💪
|
||||
- Reliable SAL annotations.
|
||||
- I made sure to enrich the majority of the code with correct, tested annotations for Code Analysis to help you spot potential accidental API misuse 😎
|
||||
|
||||
## Authenticode validation helpers
|
||||
|
||||
The [CI validation helpers](docs/ci-validation-helpers.md) provide shared result
|
||||
ownership/cleanup, file-object validation with explicit selection policy, and
|
||||
algorithm-explicit embedded verification using CI. First-success selection is
|
||||
an opt-in x64 experiment; existing APIs retain their behavior. Always enforce
|
||||
the expected signer/trust policy separately from a successful CI return status.
|
||||
|
||||
## Consumer helpers
|
||||
|
||||
[Migration and contracts](docs/consumer-helpers.md) cover file-handle/path CI
|
||||
validation, bounded x64 chain views, optional publisher-name extraction,
|
||||
certificate/SPKI hashing (including explicit legacy content-only pins), strict
|
||||
hex conversion, process-name cleanup and combined module/export lookup.
|
||||
Application authorization and trust policies remain caller-provided.
|
||||
|
||||
## How to use
|
||||
|
||||
- Add the `include` directory to your project's headers search path.
|
||||
@@ -38,10 +54,13 @@ Built for and tested on **Windows 10 version 1507 (or newer) x64/ARM64**. 32-Bit
|
||||
- Add includes (preferably in the provided order):
|
||||
```c
|
||||
#include <ntddk.h>
|
||||
#include <ntintsafe.h>
|
||||
#include <ntimage.h>
|
||||
#include <bcrypt.h>
|
||||
#include <Domito.h>
|
||||
```
|
||||
`Domito.h` also includes the consumer helper declarations; no separate
|
||||
`Domito.Consumer.h` include is required.
|
||||
- Call `DomitoInit()` in your `DriverEntry` once to bootstrap internals.
|
||||
|
||||
### Manual linker settings
|
||||
@@ -55,7 +74,11 @@ Built for and tested on **Windows 10 version 1507 (or newer) x64/ARM64**. 32-Bit
|
||||
|
||||
### Using the property sheet
|
||||
|
||||
Copy the provided `Domito.props` file into the directory of your `.vcxproj` file and customize it like below:
|
||||
Import `Domito.props` from the Domito package, or set `DomitoRootPath` explicitly
|
||||
if copying the sheet elsewhere. Its default root is its own directory. The sheet
|
||||
links both WDM and KMDF final drivers; static-library consumers must also import
|
||||
it into the final driver project. `DomitoLibraryPlatform` defaults to `$(Platform)`.
|
||||
For example:
|
||||
|
||||
```xml
|
||||
<!-- The rest of the project file -->
|
||||
@@ -71,6 +94,13 @@ Copy the provided `Domito.props` file into the directory of your `.vcxproj` file
|
||||
<!-- The rest of the project file -->
|
||||
```
|
||||
|
||||
## Code Integrity API notes
|
||||
|
||||
- [Algorithm-explicit Authenticode hashing and legacy catalog policy](docs/authenticode-digests.md)
|
||||
- [Mincrypt contracts, lifetimes, flags and opt-in x64 26100 layouts](docs/mincrypt-contracts.md)
|
||||
- [Implemented corrections and remaining VM/binary work](docs/implementation-status.md)
|
||||
- [CiValidateFileObject flag 0x20: evidence, limitations, and kernel test procedure](docs/ci-validate-file-object.md)
|
||||
|
||||
## Sources & 3rd party credits
|
||||
|
||||
This library benefits from these awesome projects ❤ (appearance in no special order):
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# Authenticode digests and legacy verification
|
||||
|
||||
Prefer `DomitoCalculatePortableExecutableDigestEx` when computing a PE digest.
|
||||
It takes an explicit SHA-1/256/384/512 algorithm, validates its supported PE layout,
|
||||
and returns a digest independently of whether the file has an embedded signature.
|
||||
This allows SHA-256 catalog lookup for a file without an embedded certificate.
|
||||
Hash calculation is separate from signature verification and signer acceptance.
|
||||
|
||||
The older `DomitoCalculatePortableExecutableDigest` and
|
||||
`DomitoGetPortableExecutableDigestKind` retain their compatibility behavior:
|
||||
linear byte hashing and heuristic pattern discovery with a SHA-1 fallback. They
|
||||
remain bounded, but should not be used to discover a general signature policy.
|
||||
The new API is additive so existing callers do not silently acquire different
|
||||
selection or rejection rules.
|
||||
|
||||
## Supported PE layout
|
||||
|
||||
The explicit API validates DOS/NT/optional-header spans, PE32 and PE32+ magic,
|
||||
declared data-directory count, the complete section table, `SizeOfHeaders`, every
|
||||
nonempty section's raw interval, and the certificate table. It sorts section
|
||||
descriptors by raw file offset before validating their sequence. At most 96
|
||||
sections are accepted, keeping stack storage and sorting work bounded.
|
||||
|
||||
Nonempty sections must form a contiguous sequence starting at `SizeOfHeaders`.
|
||||
Section-header order can differ from raw-file order. Zero-raw-size sections
|
||||
contribute no bytes. An overlay between the raw sections and the certificate
|
||||
table, or EOF for an unsigned file, is included. The checksum and physical
|
||||
certificate-directory slot are excluded. A present certificate table must be
|
||||
8-byte aligned and end at EOF; every entry must have a bounded length, version
|
||||
`0x200`, type PKCS#7 SignedData, and complete 8-byte alignment storage.
|
||||
|
||||
For this subset, sorted section bytes coincide with linear file order, so the
|
||||
implementation can stream validated ranges directly to BCrypt without copying
|
||||
or allocating a whole-file scratch buffer. Digest allocation is the only pool
|
||||
allocation in the hashing path and is released on every failure.
|
||||
|
||||
Gaps, overlapping raw sections, certificates in the middle of a file, and absent
|
||||
certificate-directory slots are unsupported. Some malformed inputs return a
|
||||
format error; ambiguous or excluded layouts return `STATUS_NOT_SUPPORTED`.
|
||||
The missing-slot boundary is deliberate: Windows SIP produced a different hash
|
||||
than the old linear helper on a fixture with zero declared directories. Do not
|
||||
infer Windows parity from the older helper's successful return on that input.
|
||||
|
||||
This is a validated subset, not a claim that every PE accepted by every Windows
|
||||
SIP is supported. Microsoft's [PE format documentation](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format)
|
||||
describes section ordering and exclusions, but unusual overlay behavior requires
|
||||
measurements against the relevant Windows implementation. The local tests use
|
||||
the actual [CryptSIPCreateIndirectData](https://learn.microsoft.com/en-us/windows/win32/api/mssip/nf-mssip-cryptsipcreateindirectdata)
|
||||
provider as a differential oracle, rather than treating a prose summary as the
|
||||
complete algorithm. SIP hash agreement does not establish CI trust-policy parity.
|
||||
|
||||
## Discovering the first embedded content's digest algorithm
|
||||
|
||||
`DomitoGetPortableExecutableDigestKindEx` navigates a bounded DER subset:
|
||||
PKCS#7 ContentInfo / SignedData / encapsulated `SpcIndirectDataContent` /
|
||||
`DigestInfo.AlgorithmIdentifier`. It reads the algorithm associated with the
|
||||
file digest, avoiding unrelated algorithm patterns in certificates, attributes,
|
||||
timestamps, or signer information. SHA-1/256/384/512 are recognized. Algorithm
|
||||
parameters may be absent or ASN.1 NULL; the digest OCTET STRING must have the
|
||||
corresponding length. DER lengths must be bounded, definite, and minimally
|
||||
encoded. Up to seven zero bytes of aligned padding after the outer DER are
|
||||
accepted when included in `WIN_CERTIFICATE.dwLength`, as observed on Microsoft
|
||||
system binaries. Other trailing bytes are rejected.
|
||||
|
||||
An absent certificate table returns `STATUS_NOT_FOUND`, an unsupported algorithm
|
||||
or certificate type returns `STATUS_NOT_SUPPORTED`, and malformed supported
|
||||
structure returns a format error. The output algorithm is zero on failure; no
|
||||
default algorithm is silently manufactured.
|
||||
|
||||
The caller supplies an array containing the security-directory entry and a
|
||||
stable input buffer. The helper inspects the first table entry. It does not
|
||||
enumerate nested/multiple signatures, validate the entire ASN.1 document,
|
||||
validate signerInfos, verify cryptographic signatures, or establish trust.
|
||||
Indefinite-length BER is outside this supported DER subset. Choose explicit
|
||||
algorithms when handling catalog-only files or application-defined lookup order.
|
||||
|
||||
## Explicit legacy catalog policy
|
||||
|
||||
`DomitoValidateFileLegacyModeEx` takes a caller-selected
|
||||
`AcceptedCatalogPolicyBits` mask, a `RequireTrustedCatalog` marker gate, and a
|
||||
`ValidationSource` output. Source is `DOMITO_CI_SOURCE_EMBEDDED` or
|
||||
`DOMITO_CI_SOURCE_CATALOG` on success and zero on failure.
|
||||
|
||||
This helper accepts SHA-1 with exactly 20 bytes or SHA-256 with exactly 32 bytes.
|
||||
Unsupported algorithms return `STATUS_NOT_SUPPORTED`; mismatched lengths return
|
||||
`STATUS_INVALID_PARAMETER`. The underlying observed embedded API supports more
|
||||
algorithms than this helper's catalog workflow, as documented in
|
||||
[the mincrypt contracts](mincrypt-contracts.md).
|
||||
|
||||
Both policy objects must be zero-initialized before first use, or own valid CI
|
||||
results. The helper frees previous contents on entry and before each retry and
|
||||
clears signing time between attempts. The caller owns both final policy results,
|
||||
including any returned on failure, and frees them with `CiFreePolicyInfo`.
|
||||
Embedded-to-catalog fallback and catalog reload occur only for
|
||||
`STATUS_INVALID_IMAGE_HASH`; resource, I/O, and other policy errors propagate.
|
||||
|
||||
Before certificate allocation, the helper references the file object and checks
|
||||
the supplied table interval against the current file size. It then uses the
|
||||
exact-read `DomitoReadFileAt`, which waits on a private completion event. An EOF
|
||||
short read fails before CI sees any uninitialized bytes. The caller must keep
|
||||
file identity/bytes stable between hashing and consumption; a size check cannot
|
||||
prevent concurrent mutation. Handles must be valid in the current process
|
||||
context and satisfy the documented kernel I/O/APC requirements.
|
||||
|
||||
The older `DomitoValidateFileLegacyMode` delegates with the existing
|
||||
`DOMITO_CI_LEGACY_CATALOG_POLICY_BITS` (`0x2007F`) and a false marker gate. That
|
||||
value is a compatibility default with broad categories, not an application trust
|
||||
recommendation. The explicit mask uses **any-overlap** catalog filtering.
|
||||
|
||||
For a catalog-only file, choose an allowed algorithm sequence as application
|
||||
policy (for example SHA-256 followed by SHA-1 when legacy acceptance is intended).
|
||||
For each algorithm, call `DomitoCalculatePortableExecutableDigestEx`, then
|
||||
`DomitoValidateFileLegacyModeEx` with a zero security directory. Free the digest
|
||||
and both policy objects before retrying. Continue to another algorithm only on
|
||||
the specifically intended lookup/signature-mismatch result, not on all failures.
|
||||
Preserve the actual algorithm and validation source in the application's result.
|
||||
This supports an explicit search strategy; it does not reproduce every native
|
||||
CI scenario-selection rule or WinVerifyTrust policy.
|
||||
|
||||
## Validation performed
|
||||
|
||||
On the local Windows 26100 system, host tests compare all four digest algorithms
|
||||
against Windows SIP for PE32/PE32+, unsigned/signed fixtures, overlays, reverse
|
||||
section-header order, multiple certificate entries, and certificate alignment.
|
||||
The installed `ci.dll`, `ntdll.dll`, `kernel32.dll`, and `notepad.exe` form a
|
||||
real-file corpus. Embedded digest algorithms are independently extracted with
|
||||
Crypt32, and computed file digests are compared with the signed
|
||||
`SpcIndirectDataContent` values where an embedded signature exists. This reads
|
||||
files and hashes bytes; it does not load or execute them as code.
|
||||
|
||||
Guard-page truncations, malformed offsets/lengths, unsupported layouts,
|
||||
algorithm selection, injected CNG/allocation failures, retry ownership, exact
|
||||
read failures, and explicit catalog controls are also covered. Actual CI calls
|
||||
are mocked in those host tests. The kernel translation units and layout
|
||||
assertions compile for x64/ARM64 with warnings treated as errors; the opt-in
|
||||
26100 runtime-layout observations remain x64-specific.
|
||||
|
||||
Still pending are historical/ARM64 CI runtime compatibility, Driver Verifier,
|
||||
real catalog-only acceptance across algorithms, nested/multiple-signature
|
||||
selection, weak-crypto/revocation/timestamp policy, and extension of supported PE
|
||||
layouts. Use the [remaining-work ledger](implementation-status.md) and the
|
||||
[flag probe](ci-validate-file-object.md) to plan the VM run.
|
||||
@@ -0,0 +1,651 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"scope": "Observed named PE exports in exact architecture-specific ci.dll samples; not runtime or ABI certification",
|
||||
"source_hash_encoding": "Canonical JSON: UTF-8, sorted keys, comma/colon separators, ensure_ascii=false",
|
||||
"generated_from": {
|
||||
"corpus-index.json": "d2c6507794314ca38a2015c74f3b66e81a4bd82d982be0ed1911dcff1d79475a",
|
||||
"iso-corpus-index.json": "ba526a8548f3be100e8d13019aed57de9703a142615c509870720572bc636217",
|
||||
"manifest.json": "bd215ebd3deb480a39b5d71a6ed709dad20368cbe90879a52070b56b023284d4",
|
||||
"ci-26100.8328-evidence.json": "ea0f939df9d8b7abde40e3402c01b2ac398253728f329e8c2b7134de8df6305f"
|
||||
},
|
||||
"apis": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"samples": [
|
||||
{
|
||||
"label": "Windows 10 1507",
|
||||
"ci_file_version": "10.0.10240.16463",
|
||||
"architecture": "x64",
|
||||
"sha256": "e83d900f4675c6e6ffc3fb7dcd3074610876a1398e495796769988df810fd3bc",
|
||||
"source": "corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": false,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1511",
|
||||
"ci_file_version": "10.0.10586.0",
|
||||
"architecture": "x64",
|
||||
"sha256": "f949bfa614addea6174b28c3818c56b327c9290fd41bd91dfc545414b3df6783",
|
||||
"source": "corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": false,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1607",
|
||||
"ci_file_version": "10.0.14393.2189",
|
||||
"architecture": "x64",
|
||||
"sha256": "ef0c02ecbd0818e84be9f61421f919825f96a3c59199460cab08548eee2881df",
|
||||
"source": "corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": false,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1703",
|
||||
"ci_file_version": "10.0.15063.0",
|
||||
"architecture": "x64",
|
||||
"sha256": "3705aaf408f05326e028621380aa5c96a1f3583a2de8a139b188da4b82577863",
|
||||
"source": "corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1709",
|
||||
"ci_file_version": "10.0.16299.125",
|
||||
"architecture": "x64",
|
||||
"sha256": "1db7ae5a08bd2361fc7fcdd373fb980e8d680035e67b02e5967095db0443effd",
|
||||
"source": "corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1803",
|
||||
"ci_file_version": "10.0.17134.1130",
|
||||
"architecture": "x64",
|
||||
"sha256": "8ce3b8fe38c23b699f30b045383640019a429d3a9d513285c655bffe03a796d9",
|
||||
"source": "corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1809 initial x64 ISO",
|
||||
"ci_file_version": "10.0.17763.1",
|
||||
"architecture": "x64",
|
||||
"sha256": "eb177481f2aea3cae0c2f97f7058e7447c0695e4489428406598f168d4dcc5e8",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1809 October v2 x64 ISO",
|
||||
"ci_file_version": "10.0.17763.348",
|
||||
"architecture": "x64",
|
||||
"sha256": "da8282173cbce7ea38f8fca64db0972c3d9614ddbca98f6b96d11a4cadde92ad",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1903 v1 x64 ISO",
|
||||
"ci_file_version": "10.0.18362.1",
|
||||
"architecture": "x64",
|
||||
"sha256": "5d01bb84fa516e23c25def8ea03fc6b696082ccc67a61575fd00d1fe7c273d72",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1903 v2 x64 ISO",
|
||||
"ci_file_version": "10.0.18362.329",
|
||||
"architecture": "x64",
|
||||
"sha256": "d12131f14f781f8c314a795384e02afc3c2902bf67da9c2e75d8163c891bdd89",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 1909 x64 ISO",
|
||||
"ci_file_version": "10.0.18362.418",
|
||||
"architecture": "x64",
|
||||
"sha256": "c3c5d32da42fa69b0795cd0f8c57252670d673fb036685db5c7d6392ffee0293",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 2004 ISO",
|
||||
"ci_file_version": "10.0.19041.1",
|
||||
"architecture": "x64",
|
||||
"sha256": "2955710d34db26e352e62b20861ba338f0216cc36b378d128057be955e184282",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": false,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 22H2 consumer ISO",
|
||||
"ci_file_version": "10.0.19041.1865",
|
||||
"architecture": "x64",
|
||||
"sha256": "d0408a9f64eafb72e8aa1cf127d07d919f0cab38a6bdf2b406255909c85e3a8d",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": true,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 11 English x64 v1 ISO",
|
||||
"ci_file_version": "10.0.22000.318",
|
||||
"architecture": "x64",
|
||||
"sha256": "301ae49ac5b87cf1cbe07d9d3c6486a066d045b6bf55fc8eb392d52951ec69ff",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": true,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 11 23H2 English ISO",
|
||||
"ci_file_version": "10.0.22621.2792",
|
||||
"architecture": "x64",
|
||||
"sha256": "9ed77b8a53946ced8c3540edb26a765060dff908544634e7b069aa49f2b41559",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": true,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 11 24H2 sample",
|
||||
"ci_file_version": "10.0.26100.9444",
|
||||
"architecture": "x64",
|
||||
"sha256": "c7d23814f3202c51fb23e3ca3d4328855787383cee5d14c4a986a13ec2aeda0b",
|
||||
"source": "ci-26100.8328-evidence.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": true,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
},
|
||||
"display_file_version": "10.0.26100.8328"
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 21H2 ARM64 ISO (archive Raspberry Pi folder)",
|
||||
"ci_file_version": "10.0.19041.1566",
|
||||
"architecture": "ARM64",
|
||||
"sha256": "a35f651261691cc67acc9c8bfd00bba7a64dd9f16d2c4b6fe1d183e34aeead78",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": true,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 10 22H2.7 ARM64 ISO",
|
||||
"ci_file_version": "10.0.19041.2965",
|
||||
"architecture": "ARM64",
|
||||
"sha256": "33f2d1256f60392bbb84c4251d6435461dfa852a100e065376f83dc8a4dd2ec6",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": true,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 11 22H2.9 ARM64 ISO",
|
||||
"ci_file_version": "10.0.22621.1702",
|
||||
"architecture": "ARM64",
|
||||
"sha256": "4164ac08fd19628c5ab6cbca6234265ac2bf7f39d5149f57d3845646d8a8ad60",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": true,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Windows 11 24H2 ARM64 ISO",
|
||||
"ci_file_version": "10.0.26100.1591",
|
||||
"architecture": "ARM64",
|
||||
"sha256": "a0eba64a9a3f2006d5832079412eca42cc1b9ed66d19b9b0adb7e59ea354415d",
|
||||
"source": "iso-corpus-index.json",
|
||||
"named_exports": [
|
||||
"CiCheckSignedFile",
|
||||
"CiFindPageHashesInCatalog",
|
||||
"CiFindPageHashesInSignedFile",
|
||||
"CiFreePolicyInfo",
|
||||
"CiGetCertPublisherName",
|
||||
"CiGetPEInformation",
|
||||
"CiInitialize",
|
||||
"CiSetTrustedOriginClaimId",
|
||||
"CiValidateFileObject",
|
||||
"CiVerifyHashInCatalog"
|
||||
],
|
||||
"ordinal_only_export_count": 3,
|
||||
"api_presence": {
|
||||
"CiCheckSignedFile": true,
|
||||
"CiFindPageHashesInCatalog": true,
|
||||
"CiFindPageHashesInSignedFile": true,
|
||||
"CiFreePolicyInfo": true,
|
||||
"CiGetCertPublisherName": true,
|
||||
"CiGetPEInformation": true,
|
||||
"CiInitialize": true,
|
||||
"CiSetTrustedOriginClaimId": true,
|
||||
"CiValidateFileObject": true,
|
||||
"CiVerifyHashInCatalog": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
# CI API compatibility matrix
|
||||
|
||||
This matrix records **named export presence in 20 exact `ci.dll` files**,
|
||||
with architecture stated separately for each sample.
|
||||
**Yes** means the complete PE export directory contains the name; **No** means
|
||||
it does not. Neither result is inferred from a header, import library or PDB.
|
||||
Export presence alone does not establish a compatible ABI or successful verification.
|
||||
|
||||
Versions in the tables are **CI file versions**, not necessarily the kernel version.
|
||||
In particular, the Windows 10 22H2 consumer ISO contains CI **19041.1865**.
|
||||
Do not extrapolate a cell to every servicing revision of that Windows release.
|
||||
|
||||
[Machine-readable matrix](ci-api-compatibility.json) includes exact SHA-256 identities,
|
||||
all per-API results and canonical JSON hashes of the retained source inventories.
|
||||
Source hashes ignore whitespace and line-ending differences between checkouts;
|
||||
the binary SHA-256 identities always cover the exact original bytes.
|
||||
|
||||
The tables use numeric `VS_FIXEDFILEINFO` versions. The original modern x64
|
||||
sample has **fixed version 26100.9444** but its string `FileVersion` reports
|
||||
**26100.8328**. Original research/header labels retain that string version;
|
||||
they identify the same SHA-256 file, not two different CI samples.
|
||||
|
||||
## Windows 10 1507–1803 x64 samples
|
||||
|
||||
| API | 10240.16463 | 10586.0 | 14393.2189 | 15063.0 | 16299.125 | 17134.1130 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| `CiCheckSignedFile` | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiFindPageHashesInCatalog` | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiFindPageHashesInSignedFile` | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiFreePolicyInfo` | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiGetCertPublisherName` | No | No | No | No | No | No |
|
||||
| `CiGetPEInformation` | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiInitialize` | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiSetTrustedOriginClaimId` | No | No | No | Yes | Yes | Yes |
|
||||
| `CiValidateFileObject` | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiVerifyHashInCatalog` | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
|
||||
## Later Windows 10 x64 samples
|
||||
|
||||
| API | 17763.1 | 17763.348 | 18362.1 | 18362.329 | 18362.418 | 19041.1 | 19041.1865 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| `CiCheckSignedFile` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiFindPageHashesInCatalog` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiFindPageHashesInSignedFile` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiFreePolicyInfo` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiGetCertPublisherName` | No | No | No | No | No | No | Yes |
|
||||
| `CiGetPEInformation` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiInitialize` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiSetTrustedOriginClaimId` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiValidateFileObject` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `CiVerifyHashInCatalog` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
|
||||
## Windows 11 x64 samples
|
||||
|
||||
| API | 22000.318 | 22621.2792 | 26100.9444 |
|
||||
| --- | --- | --- | --- |
|
||||
| `CiCheckSignedFile` | Yes | Yes | Yes |
|
||||
| `CiFindPageHashesInCatalog` | Yes | Yes | Yes |
|
||||
| `CiFindPageHashesInSignedFile` | Yes | Yes | Yes |
|
||||
| `CiFreePolicyInfo` | Yes | Yes | Yes |
|
||||
| `CiGetCertPublisherName` | Yes | Yes | Yes |
|
||||
| `CiGetPEInformation` | Yes | Yes | Yes |
|
||||
| `CiInitialize` | Yes | Yes | Yes |
|
||||
| `CiSetTrustedOriginClaimId` | Yes | Yes | Yes |
|
||||
| `CiValidateFileObject` | Yes | Yes | Yes |
|
||||
| `CiVerifyHashInCatalog` | Yes | Yes | Yes |
|
||||
|
||||
## Windows 10 ARM64 samples
|
||||
|
||||
| API | 19041.1566 | 19041.2965 |
|
||||
| --- | --- | --- |
|
||||
| `CiCheckSignedFile` | Yes | Yes |
|
||||
| `CiFindPageHashesInCatalog` | Yes | Yes |
|
||||
| `CiFindPageHashesInSignedFile` | Yes | Yes |
|
||||
| `CiFreePolicyInfo` | Yes | Yes |
|
||||
| `CiGetCertPublisherName` | Yes | Yes |
|
||||
| `CiGetPEInformation` | Yes | Yes |
|
||||
| `CiInitialize` | Yes | Yes |
|
||||
| `CiSetTrustedOriginClaimId` | Yes | Yes |
|
||||
| `CiValidateFileObject` | Yes | Yes |
|
||||
| `CiVerifyHashInCatalog` | Yes | Yes |
|
||||
|
||||
## Windows 11 ARM64 samples
|
||||
|
||||
| API | 22621.1702 | 26100.1591 |
|
||||
| --- | --- | --- |
|
||||
| `CiCheckSignedFile` | Yes | Yes |
|
||||
| `CiFindPageHashesInCatalog` | Yes | Yes |
|
||||
| `CiFindPageHashesInSignedFile` | Yes | Yes |
|
||||
| `CiFreePolicyInfo` | Yes | Yes |
|
||||
| `CiGetCertPublisherName` | Yes | Yes |
|
||||
| `CiGetPEInformation` | Yes | Yes |
|
||||
| `CiInitialize` | Yes | Yes |
|
||||
| `CiSetTrustedOriginClaimId` | Yes | Yes |
|
||||
| `CiValidateFileObject` | Yes | Yes |
|
||||
| `CiVerifyHashInCatalog` | Yes | Yes |
|
||||
|
||||
## Exact sample identities
|
||||
|
||||
- Windows 10 1507: **10.0.10240.16463 x64**, SHA-256
|
||||
`e83d900f4675c6e6ffc3fb7dcd3074610876a1398e495796769988df810fd3bc`.
|
||||
- Windows 10 1511: **10.0.10586.0 x64**, SHA-256
|
||||
`f949bfa614addea6174b28c3818c56b327c9290fd41bd91dfc545414b3df6783`.
|
||||
- Windows 10 1607: **10.0.14393.2189 x64**, SHA-256
|
||||
`ef0c02ecbd0818e84be9f61421f919825f96a3c59199460cab08548eee2881df`.
|
||||
- Windows 10 1703: **10.0.15063.0 x64**, SHA-256
|
||||
`3705aaf408f05326e028621380aa5c96a1f3583a2de8a139b188da4b82577863`.
|
||||
- Windows 10 1709: **10.0.16299.125 x64**, SHA-256
|
||||
`1db7ae5a08bd2361fc7fcdd373fb980e8d680035e67b02e5967095db0443effd`.
|
||||
- Windows 10 1803: **10.0.17134.1130 x64**, SHA-256
|
||||
`8ce3b8fe38c23b699f30b045383640019a429d3a9d513285c655bffe03a796d9`.
|
||||
- Windows 10 1809 initial x64 ISO: **10.0.17763.1 x64**, SHA-256
|
||||
`eb177481f2aea3cae0c2f97f7058e7447c0695e4489428406598f168d4dcc5e8`.
|
||||
- Windows 10 1809 October v2 x64 ISO: **10.0.17763.348 x64**, SHA-256
|
||||
`da8282173cbce7ea38f8fca64db0972c3d9614ddbca98f6b96d11a4cadde92ad`.
|
||||
- Windows 10 1903 v1 x64 ISO: **10.0.18362.1 x64**, SHA-256
|
||||
`5d01bb84fa516e23c25def8ea03fc6b696082ccc67a61575fd00d1fe7c273d72`.
|
||||
- Windows 10 1903 v2 x64 ISO: **10.0.18362.329 x64**, SHA-256
|
||||
`d12131f14f781f8c314a795384e02afc3c2902bf67da9c2e75d8163c891bdd89`.
|
||||
- Windows 10 1909 x64 ISO: **10.0.18362.418 x64**, SHA-256
|
||||
`c3c5d32da42fa69b0795cd0f8c57252670d673fb036685db5c7d6392ffee0293`.
|
||||
- Windows 10 2004 ISO: **10.0.19041.1 x64**, SHA-256
|
||||
`2955710d34db26e352e62b20861ba338f0216cc36b378d128057be955e184282`.
|
||||
- Windows 10 22H2 consumer ISO: **10.0.19041.1865 x64**, SHA-256
|
||||
`d0408a9f64eafb72e8aa1cf127d07d919f0cab38a6bdf2b406255909c85e3a8d`.
|
||||
- Windows 11 English x64 v1 ISO: **10.0.22000.318 x64**, SHA-256
|
||||
`301ae49ac5b87cf1cbe07d9d3c6486a066d045b6bf55fc8eb392d52951ec69ff`.
|
||||
- Windows 11 23H2 English ISO: **10.0.22621.2792 x64**, SHA-256
|
||||
`9ed77b8a53946ced8c3540edb26a765060dff908544634e7b069aa49f2b41559`.
|
||||
- Windows 11 24H2 sample: **10.0.26100.9444 x64**, SHA-256
|
||||
`c7d23814f3202c51fb23e3ca3d4328855787383cee5d14c4a986a13ec2aeda0b`.
|
||||
String FileVersion: **10.0.26100.8328** (original research label).
|
||||
- Windows 10 21H2 ARM64 ISO (archive Raspberry Pi folder): **10.0.19041.1566 ARM64**, SHA-256
|
||||
`a35f651261691cc67acc9c8bfd00bba7a64dd9f16d2c4b6fe1d183e34aeead78`.
|
||||
- Windows 10 22H2.7 ARM64 ISO: **10.0.19041.2965 ARM64**, SHA-256
|
||||
`33f2d1256f60392bbb84c4251d6435461dfa852a100e065376f83dc8a4dd2ec6`.
|
||||
- Windows 11 22H2.9 ARM64 ISO: **10.0.22621.1702 ARM64**, SHA-256
|
||||
`4164ac08fd19628c5ab6cbca6234265ac2bf7f39d5149f57d3845646d8a8ad60`.
|
||||
- Windows 11 24H2 ARM64 ISO: **10.0.26100.1591 ARM64**, SHA-256
|
||||
`a0eba64a9a3f2006d5832079412eca42cc1b9ed66d19b9b0adb7e59ea354415d`.
|
||||
|
||||
## What callers can rely on
|
||||
|
||||
- `CiValidateFileObject` is already exported in the sampled Windows 10 1507 file.
|
||||
- `CiSetTrustedOriginClaimId` is present in the sampled 1703 file and absent in
|
||||
the three earlier samples. This does not prove its first introduction build.
|
||||
- On sampled x64 files, `CiGetCertPublisherName` is absent in 19041.1
|
||||
and present in 19041.1865.
|
||||
Its first Windows 10 servicing update remains unknown. It is also present
|
||||
in the sampled 22000.318 and 26100.9444 fixed-version files.
|
||||
- The Windows 10 ARM64 21H2 sample already exports the publisher-name API
|
||||
in CI 19041.1566. That does not establish x64 availability at that revision.
|
||||
- CI entry points currently use static imports from `ci.lib`. An unavailable
|
||||
import can prevent driver loading; this matrix does not add an automatic
|
||||
`STATUS_NOT_IMPLEMENTED` fallback. Supporting these older samples requires
|
||||
avoiding unavailable imports or implementing optional resolution.
|
||||
- Choose structures using a verified runtime profile. The sampled x64 chain
|
||||
headers differ: 1507 uses 56 bytes, 1511 uses 72, and 1607–1803 use 104.
|
||||
Export presence, compile-target `NTDDI_VERSION` and total allocation `Size`
|
||||
do not select a compatible layout. See [mincrypt contracts](mincrypt-contracts.md).
|
||||
|
||||
## Coverage gaps and evidence
|
||||
|
||||
**Unknown / not sampled:** x86 export availability; pre-Windows-10;
|
||||
earlier Windows 10 ARM64 releases; and other servicing revisions
|
||||
or architecture/release combinations absent from these tables.
|
||||
These are gaps, not negative export results. No interval between
|
||||
two samples is certified. Runtime verification and cross-version ABI checks
|
||||
remain in the [implementation ledger](implementation-status.md).
|
||||
|
||||
The tables cover every named export found in these samples, including
|
||||
`CiInitialize`; inclusion is not a recommendation to call initialization or
|
||||
global-policy routines. Each sample also has three ordinal-only exports,
|
||||
which are outside this named-API matrix. Private `Mincrypt*` PDB symbols are
|
||||
not automatically callable exports.
|
||||
|
||||
Source inventories are retained in the companion
|
||||
[CodeIntegrity-RE repository](https://git.nefarius.at/nefarius/CodeIntegrity-RE):
|
||||
`corpus-index.json` (VM samples), `iso-corpus-index.json` (ISO samples), and
|
||||
`ci-26100.8328-evidence.json` with `manifest.json` (original 26100 sample).
|
||||
Regenerate with its `generate_compatibility_matrix.py --output <Domito/docs>`.
|
||||
ARM64 cells establish export presence only; the historical x64 layout views
|
||||
must not be selected for ARM64 solely because the same API name is exported.
|
||||
No Windows binary or PDB is distributed with the matrix.
|
||||
@@ -0,0 +1,236 @@
|
||||
# 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. Domito exposes it only as
|
||||
`DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL` and through the opt-in
|
||||
`DomitoCiSignatureSelectionFirstSuccessfulExperimental` mode in
|
||||
`DomitoValidateFileObjectEx`. It is not a production default or a stable signer
|
||||
index. See the [validation helper contracts](ci-validation-helpers.md). 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.
|
||||
@@ -0,0 +1,118 @@
|
||||
# CI validation helpers
|
||||
|
||||
These additive APIs are declared in `Domito.h` for Windows 10 compile targets.
|
||||
They reuse CI for cryptographic verification. They do not implement RSA/ECDSA,
|
||||
certificate-chain building or timestamp-signature verification themselves.
|
||||
Raw CI declarations and existing digest/legacy helpers are unchanged.
|
||||
|
||||
## Result lifetime
|
||||
|
||||
Zero-initialize `DOMITO_CI_VALIDATION_RESULT` before first use. Each validation
|
||||
call frees its previous signer/timestamp policies before reuse and resets the
|
||||
result. Do not pass uninitialized storage or copy an owning result as a second
|
||||
owner. On every exit, including a failed call, release it with
|
||||
`DomitoFreeCiValidationResult`. The destructor accepts NULL and empty results.
|
||||
All three APIs require PASSIVE_LEVEL. Referenced input objects/buffers must stay
|
||||
valid and file bytes must stay stable throughout validation and consumption.
|
||||
|
||||
On success, `Hash`/`HashSize`/`HashAlgId` describe a known SHA-1/256/384/512 file
|
||||
digest with a consistent length. Unused digest bytes are zero. This is not a
|
||||
certificate thumbprint. On failure, digest bytes/length/algorithm and signing
|
||||
time are zero; policy objects may retain owned diagnostic allocations. Input
|
||||
flags/level forwarded to the file-object export remain recorded if it was called.
|
||||
Those fields are not CI's final internally normalized policy flags.
|
||||
|
||||
Check the returned NTSTATUS first. Policy verification status, policy bits,
|
||||
certificate identity, timestamps and the application's intended trust policy
|
||||
remain separate observations/checks. Returning STATUS_SUCCESS does not pin a
|
||||
company signer, reproduce WinVerifyTrust policy or prove driver-load acceptance.
|
||||
No helper traverses the private chain layout or matches a certificate for you.
|
||||
|
||||
## File-object path
|
||||
|
||||
`DomitoValidateFileObjectEx` takes a referenced FILE_OBJECT, caller-selected
|
||||
validation flags, a byte-sized requested signing level and a selection mode.
|
||||
It supplies fresh 64-byte hash capacity and initialized policy/time outputs on
|
||||
every call. It does not read, duplicate or acquire ownership of the file object.
|
||||
|
||||
- `DomitoCiSignatureSelectionNative` forwards the caller's flags unchanged.
|
||||
This is native policy, not a guarantee of preferred-level selection: the
|
||||
caller's other flags may already change the search/exit path.
|
||||
- `DomitoCiSignatureSelectionFirstSuccessfulExperimental` additionally sets
|
||||
`DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL` (0x80). On examined x64
|
||||
builds this changes a successful-candidate early-exit predicate. It also
|
||||
affects other policy/fallback paths. This explicit mode returns
|
||||
STATUS_NOT_SUPPORTED on non-x64 builds. No runtime behavior on ARM64 is implied.
|
||||
|
||||
Neither mode automatically adds `0x20`. Broadening accepted roots is a separate
|
||||
caller decision. Neither mode selects an arbitrary signer index or guarantees
|
||||
that signature zero succeeds. A rejected primary may still lead to another signer.
|
||||
|
||||
```c
|
||||
DOMITO_CI_VALIDATION_RESULT result = {0};
|
||||
NTSTATUS status = DomitoValidateFileObjectEx(
|
||||
fileObject,
|
||||
DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE,
|
||||
0,
|
||||
DomitoCiSignatureSelectionFirstSuccessfulExperimental,
|
||||
&result
|
||||
);
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
// Apply your expected-certificate/trust policy to result.PolicyInfo.
|
||||
// result.SigningTime belongs to that selected verification result.
|
||||
}
|
||||
DomitoFreeCiValidationResult(&result);
|
||||
```
|
||||
|
||||
The native kernel experiment on the supplied dual-SHA256 BthPS3 fixture returned
|
||||
Microsoft for flags 0x20 and Nefarius for flags 0xA0 on a Windows 11 22631 x64
|
||||
TESTSIGNING-enabled machine. It does not establish all older builds, TESTSIGNING-off
|
||||
behavior or every side effect of 0x80. See [the flag evidence](ci-validate-file-object.md).
|
||||
Do not rename this mode "require our company" or "select primary signature".
|
||||
|
||||
## Embedded PE-buffer path
|
||||
|
||||
`DomitoValidatePortableExecutableEmbeddedSignature` accepts a stable PE buffer
|
||||
and an explicit CALG_SHA1/CALG_SHA_256/CALG_SHA_384/CALG_SHA_512. It calls
|
||||
`DomitoCalculatePortableExecutableDigestEx` and supplies the resulting digest
|
||||
and original first WIN_CERTIFICATE to `CiCheckSignedFile`. The digest allocation
|
||||
is always freed, and only copied digest bytes/owned CI policies remain in the
|
||||
result. Unsupported algorithm/layout and CI errors propagate without trying a
|
||||
different algorithm or a catalog. An absent certificate returns
|
||||
STATUS_INVALID_IMAGE_HASH; the helper cannot turn an unsigned file into a
|
||||
successful catalog verification.
|
||||
|
||||
```c
|
||||
DOMITO_CI_VALIDATION_RESULT result = {0};
|
||||
NTSTATUS status = DomitoValidatePortableExecutableEmbeddedSignature(stablePeBytes, peSize, CALG_SHA_256, &result);
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
// Enforce the expected certificate and intended trust policy here.
|
||||
}
|
||||
DomitoFreeCiValidationResult(&result);
|
||||
```
|
||||
|
||||
This uses the explicit digest helper's [supported PE layout subset](authenticode-digests.md):
|
||||
bounded headers, contiguous nonoverlapping raw sections in file order and an EOF
|
||||
certificate table. It is not an arbitrary PE/SIP parser. CI can select nested
|
||||
signatures according to its algorithm rules, so the helper is deliberately named
|
||||
**embedded**, not **force primary**. The matching SHA-256 request selected the
|
||||
company primary in the tested BthPS3 layout; another layout may select another
|
||||
signer or fail. General enumeration remains separate work.
|
||||
|
||||
## Support and validation
|
||||
|
||||
The functions are C-compatible and compile for x64/ARM64. The experimental
|
||||
file-object mode is deliberately gated to x64; native forwarding/embedded code
|
||||
compilation does not certify every architecture/build's CI runtime ABI. CI
|
||||
imports are static and must exist on the target OS, as described in the
|
||||
[availability matrix](ci-api-compatibility.md). Structure/chain interpretation
|
||||
remains subject to the [mincrypt contracts](mincrypt-contracts.md).
|
||||
|
||||
The new host suite checks production code forwarding, hash invariants, failures,
|
||||
partial allocations, cleanup, result reuse and the non-x64 conditional branch.
|
||||
The existing digest suite independently compares Windows SIP and Crypt32 results.
|
||||
Kernel compilation covers the full library, public C clients and layout checks.
|
||||
CodeIntegrity-RE probe v2 now calls the new APIs; a further kernel run is needed
|
||||
to validate these wrappers rather than merely their previously tested raw calls.
|
||||
@@ -0,0 +1,247 @@
|
||||
# Migrating consumer Authenticode helpers
|
||||
|
||||
These helpers centralize file validation, certificate inspection, hashing and
|
||||
allocation cleanup. The contracts keep application identity and trust policy
|
||||
explicit. Host tests and kernel compilation do not establish new CI runtime
|
||||
guarantees.
|
||||
|
||||
## Verified fixes and existing coverage
|
||||
|
||||
- **Implementation defect fixed:** `DomitoGetProcessImageName` freed its allocation
|
||||
on a failed second query but left that freed address in the caller's output.
|
||||
It also left old caller output untouched on earlier failures. It now clears
|
||||
the output first, publishes only a successful bounded result, and frees its
|
||||
local allocation on all other paths. The process handle uses limited-query
|
||||
access (`0x1000`) instead of `PROCESS_ALL_ACCESS`. A PID is resolved once to a
|
||||
process handle; no later query re-resolves that PID.
|
||||
- **Output-contract defect fixed:** `DomitoFindModuleBaseAddress` could preserve
|
||||
an old address when lookup failed. It now clears the optional output first and
|
||||
validates the counted module name. The export-by-module wrapper preserves the
|
||||
underlying status, including missing exports and unsupported forwarders.
|
||||
- **Annotation defect fixed:** the `DomitoReadFileAt` definition's single-object
|
||||
output annotation disagreed with its public byte-count annotation. Both now
|
||||
describe `BufferSize` writable bytes.
|
||||
- **Already addressed before this review:** exact file reads return
|
||||
`STATUS_END_OF_FILE` on short reads and wait for their own pending request.
|
||||
Explicit digest helpers bound PE32/PE32+ headers, sections, certificate spans
|
||||
and DER navigation. Certificate pointers borrow the stable input buffer.
|
||||
[CI validation results](ci-validation-helpers.md) initialize outputs and own
|
||||
both policies, including diagnostic allocations on failure. The current raw
|
||||
CI declaration names the flags and byte-sized signing level; `0x20` broadens
|
||||
root acceptance in examined builds, rather than declaring a file to be PE.
|
||||
|
||||
Applications should avoid truncating file lengths to `EndOfFile.LowPart`,
|
||||
reparsing fixed-kind PE headers, consuming uninitialized CI policies, skipping
|
||||
cleanup on failure, or accepting invalid hexadecimal digits. The helpers below
|
||||
provide reusable primitives without defining application authorization policy.
|
||||
|
||||
## File object, handle and path validation
|
||||
|
||||
`DomitoValidateFileObjectEx`, `DomitoValidateFileHandleEx` and
|
||||
`DomitoValidateFilePathEx` expose the same explicit flags, requested signing
|
||||
level, selection mode and owning `DOMITO_CI_VALIDATION_RESULT`. None adds flags
|
||||
or falls back to a different validation policy. They require PASSIVE_LEVEL and
|
||||
Windows 10 compile targets; the CI imports must exist on the target OS.
|
||||
They do not require `DomitoInit`.
|
||||
|
||||
The handle wrapper requires a trusted **kernel handle** with `FILE_READ_DATA`.
|
||||
It references its FILE_OBJECT, releases that reference on every exit and leaves
|
||||
the caller's handle open. Never pass an unchecked user handle: the reference is
|
||||
made using KernelMode. The caller must keep the handle valid during validation.
|
||||
|
||||
The path wrapper accepts a counted absolute native path, for example
|
||||
`\??\C:\Program Files\Example\app.exe`. It creates a read-only kernel handle,
|
||||
shares only reads, references that same opened file for CI, and closes its own
|
||||
handle. Existing incompatible writers cause an open failure. Special kernel APCs
|
||||
must be enabled for synchronous I/O. Reparse points may redirect the open;
|
||||
validation describes the opened object. It does not establish the identity of
|
||||
a previously looked-up process image or certify in-memory process contents.
|
||||
File-object/handle callers must independently provide stable file bytes.
|
||||
|
||||
```c
|
||||
DOMITO_CI_VALIDATION_RESULT result = {0};
|
||||
NTSTATUS status = DomitoValidateFilePathEx(&nativePath, selectedCiFlags, 0, DomitoCiSignatureSelectionNative, &result);
|
||||
if (NT_SUCCESS(status) && NT_SUCCESS(result.PolicyInfo.VerificationStatus))
|
||||
{
|
||||
// Apply caller-provided certificate/publisher/pin policy here.
|
||||
// A nonzero SigningTime is a separate consumer requirement, if desired.
|
||||
}
|
||||
DomitoFreeCiValidationResult(&result); // Also on CI or application-policy failure.
|
||||
```
|
||||
|
||||
Modern file-object verification can select a preferred Microsoft nested signer
|
||||
where direct embedded verification selected the company signer. Changing paths
|
||||
therefore requires an explicit migration decision. Keep
|
||||
`DomitoValidatePortableExecutableEmbeddedSignature` for the stable PE-buffer,
|
||||
explicit-algorithm route. Its supported PE subset and nested-selection limitations
|
||||
still apply. It verifies the original first WIN_CERTIFICATE through CI, with no
|
||||
algorithm retry or catalog fallback. Existing legacy/catalog APIs remain
|
||||
available with explicit catalog policy; new convenience wrappers do not invoke
|
||||
them implicitly. An absent optional API is not permission to relax trust policy.
|
||||
|
||||
## Certificate fingerprints and SPKI pins
|
||||
|
||||
`DomitoGetCertificateSpki` extracts two borrowed views from stable certificate
|
||||
DER: `Encoded` contains the complete SPKI SEQUENCE TLV; `Content` omits that
|
||||
sequence's tag and length. It handles optional X.509 version fields and bounded,
|
||||
minimal definite DER lengths. It validates navigation/framing and the SPKI field
|
||||
shape, rather than every X.509 field, extension, name, algorithm parameter or
|
||||
signature. It does not verify trust. Failed extraction clears both views.
|
||||
|
||||
`DomitoHashBuffer` hashes exact bytes using SHA-1/256/384/512 and Windows CNG.
|
||||
`HashSize` is capacity on entry, actual length on success, required length on
|
||||
`STATUS_BUFFER_TOO_SMALL`, and zero on other failures. A NULL hash buffer queries
|
||||
the required size. Operational CNG failures erase the selected digest bytes.
|
||||
Input/output buffers must not overlap. Only consume a digest after success.
|
||||
|
||||
`DomitoHashCertificate` takes an explicit `DOMITO_CERTIFICATE_HASH_KIND`:
|
||||
|
||||
- `DomitoCertificateHashDer`: exact complete certificate bytes, suitable for a
|
||||
DER thumbprint. This mode hashes bytes; it does not parse or validate them.
|
||||
- `DomitoCertificateHashSpkiDer`: complete SPKI encoding, including outer TLV.
|
||||
- `DomitoCertificateHashSpkiContentLegacy`: **preserves the consumers' existing
|
||||
content-only convention**. Existing pins must use this mode. Switching to the
|
||||
complete encoding requires recomputing pins from the intended certificates;
|
||||
do not silently treat hashes from the two representations as interchangeable.
|
||||
|
||||
```c
|
||||
UCHAR pin[MINCRYPT_SHA256_LENGTH];
|
||||
ULONG pinSize = sizeof(pin);
|
||||
status =
|
||||
DomitoHashCertificate(cert.Data, cert.Size, DomitoCertificateHashSpkiContentLegacy, CALG_SHA_256, pin, &pinSize);
|
||||
```
|
||||
|
||||
`DomitoDecodeHex` accepts an exact counted Unicode hex string for a caller-selected
|
||||
binary length, rejects invalid digits/odd or mismatched lengths, and zeroes its
|
||||
destination on malformed input. `DomitoEncodeHex` writes uppercase Unicode hex
|
||||
with a NUL terminator; its capacity is in WCHARs including the terminator.
|
||||
Decode configuration pins once into binary and compare like representations.
|
||||
The library embeds no publisher names, root pins, or certificate allowlists.
|
||||
Hashing does not prove that a certificate is a root or trusted issuer.
|
||||
|
||||
## Chain inspection and publisher ownership
|
||||
|
||||
`DomitoGetCiChainView` requires a known, readable, initialized CI-owned policy
|
||||
and an explicit `DomitoCiChainLayoutObservedX64` argument. This asserts the
|
||||
sampled x64 common prefix (+0x28 elements, +0x30 count, +0x34 opaque RootId,
|
||||
minimum 56-byte header, 120-byte elements). It is **not** automatic OS/layout
|
||||
detection. Non-x64 builds return `STATUS_NOT_SUPPORTED`; allocation size and
|
||||
compile-target NTDDI cannot establish runtime layout compatibility.
|
||||
|
||||
The accessor bounds the element array and every exposed certificate/name blob
|
||||
within CI's owned allocation before returning a view. It rejects oversized hash
|
||||
lengths and absent certificate bytes. It exposes neither trailing attributes nor
|
||||
EKU/public-key arrays. It cannot make a forged or freed kernel pointer safe;
|
||||
allocation extent/readability and the asserted layout remain caller preconditions.
|
||||
All returned pointers expire when the policy is freed or its result reused.
|
||||
|
||||
Sampled CI results are leaf-first. Inspect `Elements[0]` for that selected leaf.
|
||||
`Elements[Count-1]` is the last represented certificate; this API does **not**
|
||||
promise it is self-signed, the configured trust anchor, or that the chain includes
|
||||
every certificate through a root. `RootId` is opaque and does not establish those
|
||||
properties. A consumer requiring a specific root must establish its intended CI
|
||||
policy and represented-anchor assumptions instead of inferring trust from position.
|
||||
|
||||
The accessor deliberately imposes no bytewise issuer/next-subject equality.
|
||||
CI name blobs are not an independent chain verifier;
|
||||
their encoding and representation are version-sensitive. Current evidence does
|
||||
not establish universal byte equality as a CI API contract. Preserve such a check
|
||||
as an explicit downstream restriction if compatibility demands it, and assess its
|
||||
false-rejection risk. CI return status, policy verification status and application
|
||||
identity/pin requirements remain separate checks.
|
||||
|
||||
`DomitoGetCertificatePublisherName` dynamically resolves the optional CI export
|
||||
instead of adding a static import. It requires `DomitoInit`; missing modules,
|
||||
exports and unsupported export forwarders propagate their lookup status.
|
||||
The caller passes an empty `UNICODE_STRING`, and uses
|
||||
`DomitoFreeCertificatePublisherName` to release/reset it. Partial allocations on
|
||||
failure are cleaned internally; the destructor is safe again afterward. Its
|
||||
private pool allocator is independent of configurable Domito callbacks. Never
|
||||
free this string through `G_Memory`, RtlFreeUnicodeString or the process-name free
|
||||
API. Display-name comparison remains caller policy, not certificate identity.
|
||||
|
||||
## Process names, allocators and optional exports
|
||||
|
||||
`DomitoGetProcessImageName` returns one Domito allocation containing the
|
||||
`UNICODE_STRING` and its buffer. `DomitoFreeProcessImageName` frees the entire
|
||||
allocation once, without querying callbacks in consumer code. Its output is
|
||||
NULL on failure. The existing ULONG PID interface remains; a PID lookup can race
|
||||
process exit/reuse before the initial open and is not a durable process identity.
|
||||
|
||||
`DomitoGetProcessImageNameEx` instead accepts a caller-referenced PEPROCESS.
|
||||
It uses `SeLocateProcessImageName`, copies the name into the same Domito-owned
|
||||
representation and frees the OS allocation internally. The caller must keep the
|
||||
process referenced for the call. No Domito initialization is needed for this Ex
|
||||
API. The PID API still requires `DomitoInit` for its query routine.
|
||||
Neither name API proves that a subsequent path open addresses the original
|
||||
image file. WDF/requestor-process lifetime and authorization remain downstream.
|
||||
|
||||
**Allocator contract:** the existing callback API does not attach allocator
|
||||
provenance to each result and is not synchronized for concurrent replacement.
|
||||
Configure a compatible allocate/free pair during initialization, before concurrent
|
||||
calls, and keep it unchanged until all callback-backed allocations and in-flight
|
||||
calls are released. A dedicated free name does not make changing callback pairs
|
||||
with outstanding allocations safe. Do not retrofit a hidden prefix onto existing
|
||||
allocations: callers may already free the public base address with their callback.
|
||||
CI policies and the publisher helper use their dedicated free APIs instead.
|
||||
|
||||
`DomitoFindModuleExportedFunctionAddress` combines the existing full-module-path
|
||||
and export-name lookups, clears its output and preserves useful failure status.
|
||||
It requires `DomitoInit`, and does not pin the module. The caller must guarantee
|
||||
that the module remains mapped throughout lookup and use of its returned address.
|
||||
|
||||
## Integration and consumer boundary
|
||||
|
||||
Import `Domito.props` from the Domito package location or override
|
||||
`DomitoRootPath`. The default root is the sheet's own directory. x64/ARM64 and
|
||||
Debug/Release remain selected by the project; `DomitoLibraryPlatform` can override
|
||||
the library platform directory. Both WDM and KMDF final driver projects receive
|
||||
Domito, CI and CNG dependencies. Importing a sheet into a static library does not
|
||||
make link dependencies transitive: import it into the final driver as well.
|
||||
|
||||
WDF caches, PID caches, administrator checks, developer bypasses, request allowlists,
|
||||
build expiry and selected-signing-time requirements stay in consumer code.
|
||||
No default trust evaluator is added: that would silently choose policy absent
|
||||
from the generic SDK. WPP diagnostics can enumerate the bounded chain view without
|
||||
fixed-depth indentation arrays. Report the returned NTSTATUS and local policy
|
||||
failure reason separately rather than mapping all failures inside the SDK.
|
||||
|
||||
The new host suite compiles the production code unchanged apart from its kernel
|
||||
include, uses deterministic kernel doubles for ownership/failure paths, compares
|
||||
SPKI bytes and fingerprints with Crypt32, and uses real CNG for hashing. It tests
|
||||
both architecture conditional branches, which is not ARM64 runtime emulation.
|
||||
Real WDK compilation separately covers x64 and ARM64. These checks do not certify
|
||||
kernel CI behavior, every chain layout, or full X.509 validation.
|
||||
|
||||
References: [SeLocateProcessImageName ownership](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-selocateprocessimagename),
|
||||
[BCryptCreateHash memory management and kernel requirements](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptcreatehash).
|
||||
|
||||
## Deprecated compatibility entry points
|
||||
|
||||
The public header uses MSVC `__declspec(deprecated("migration guidance"))` for
|
||||
four older functions. Calling or taking their address produces C4996 in C and
|
||||
C++; declarations and binary entry points remain available. Projects treating
|
||||
warnings as errors must migrate, or suppress C4996 locally around a deliberate
|
||||
compatibility call. Do not disable the warning globally to conceal accidental use.
|
||||
|
||||
- `DomitoGetPortableExecutableDigestKind` →
|
||||
`DomitoGetPortableExecutableDigestKindEx`: check NTSTATUS; unrecognized input
|
||||
no longer silently chooses SHA-1. Neither function validates signature trust.
|
||||
- `DomitoCalculatePortableExecutableDigest` →
|
||||
`DomitoCalculatePortableExecutableDigestEx`: select the algorithm explicitly
|
||||
and review the supported layout/rejection rules. This is not a drop-in semantic
|
||||
change for arbitrary files previously accepted by linear hashing.
|
||||
- `DomitoValidateFileLegacyMode` → `DomitoValidateFileLegacyModeEx`: select catalog
|
||||
policy and trusted-catalog requirements explicitly, and consume the validation
|
||||
source. The Ex legacy path remains available where catalog/older-OS behavior
|
||||
is required; migration does not implicitly switch to modern signer selection.
|
||||
- `DomitoGetProcessImageName` → `DomitoGetProcessImageNameEx`: acquire and hold the
|
||||
intended PEPROCESS reference, then use the dedicated name destructor. Looking
|
||||
up a PID immediately before the Ex call cannot eliminate an earlier PID-reuse
|
||||
race; obtain the process reference from the operation's intended identity.
|
||||
This migration does not solve subsequent path-reopen identity races.
|
||||
|
||||
No old type has an unconditional replacement. Raw CI/mincrypt types still
|
||||
describe the native ABI; the owning validation result and borrowed chain view
|
||||
serve different purposes. Raw Windows CI exports, allocator callbacks, module
|
||||
lookup primitives, exact file reads and digest cleanup remain supported.
|
||||
@@ -0,0 +1,127 @@
|
||||
# CI SDK implementation status
|
||||
|
||||
This ledger tracks the source audit S01–S18 and the remaining binary research.
|
||||
The original CodeIntegrity-RE audit remains a snapshot of commit `7985b23`;
|
||||
it should not be read as describing the corrected source unchanged.
|
||||
|
||||
## Implemented with host tests or compilation
|
||||
|
||||
- Additive CI result/selection helpers now package native file-object validation,
|
||||
opt-in x64 first-success selection and explicit embedded verification, with
|
||||
shared policy/digest cleanup. Existing APIs retain their behavior. See the
|
||||
[helper contracts](ci-validation-helpers.md). Host failure/forwarding tests and
|
||||
x64/ARM64 builds cover the wrappers; WPP probe v2 consumes them and awaits a
|
||||
returned kernel trace. The previous v1 trace establishes the raw native routes.
|
||||
|
||||
- **S01–S04:** subtraction-safe PE ranges, no fixed 4 MiB copy, bounded certificate
|
||||
intervals, deterministic failure outputs, and digest cleanup. Compatibility
|
||||
and explicit paths share the bounded hashing core.
|
||||
- **S05:** actual certificate read length checked; allocation bounded against
|
||||
file size; per-operation completion waits through the exact-read helper.
|
||||
- **S06:** `DomitoReadFileAt` uses an explicit offset and private kernel event;
|
||||
`DomitoReadFile` reads exactly from offset zero. Pending completion cannot
|
||||
outlive its stack state or caller buffer. Short reads return EOF.
|
||||
- **S10:** export lookup uses the name ordinal to index the EAT, bounds tables,
|
||||
names and resolved RVAs, and rejects forwarders. A valid loader-mapped module
|
||||
remains a precondition because the API has no externally supplied image size.
|
||||
- **S11–S12:** corrected policy pointer typedef and publisher return/ownership docs.
|
||||
- **S14:** policy cleanup before embedded/catalog/reload retries, with host tests
|
||||
allocating results even on failures. Real CI failure ownership still needs
|
||||
the VM tests below.
|
||||
- **S17:** legacy hash algorithm/length combinations validated and calculation
|
||||
versus verification capabilities documented.
|
||||
- **S18:** byte-count SAL, buffer-capacity rules, scalar input annotations, and
|
||||
kernel read context/APC requirements documented.
|
||||
|
||||
## Implemented within an explicit support boundary
|
||||
|
||||
- **S07:** new bounded DER discovery reads the first outer SPC file-digest
|
||||
AlgorithmIdentifier; new explicit digest API needs no discovery. The old
|
||||
heuristic entry point retains compatibility behavior. Arbitrary BER, nested
|
||||
signatures, and full signature enumeration remain outside this parser.
|
||||
- **S08:** unsigned/catalog-only files can be hashed with a caller-chosen
|
||||
SHA-1 or SHA-256 strategy. The policy-explicit legacy helper forwards catalog
|
||||
controls and reports the successful source. Real catalog lookup comparisons
|
||||
across OS versions are pending; no implicit algorithm retry policy is imposed.
|
||||
- **S09:** the explicit digest path enforces a contiguous raw-section subset and
|
||||
EOF certificate tables, with Windows SIP and real embedded-digest comparisons.
|
||||
Gaps, overlaps, missing directory slots and nonterminal certificates are
|
||||
rejected. The old linear compatibility entry point is not a universal hasher.
|
||||
- **S13/S15/S16:** recovered fields, parameter meanings, policy categories, sizes,
|
||||
name encoding, borrowed lifetimes, and allocator ownership are documented.
|
||||
The opt-in x64 26100 header provides distinct observed layouts and assertions.
|
||||
Default declarations retain source compatibility; later chain fields require
|
||||
a confirmed runtime layout. The sampled 1507 chain header is 56 bytes and has
|
||||
no attributes descriptor at +0x38; 1511 uses 72 bytes; 1607–1803 samples use 104.
|
||||
Distinct views are available in `Domito.MinCrypt.Historical.h`. Neither
|
||||
`NTDDI_VERSION` nor total allocation Size identifies the layout.
|
||||
|
||||
## Requires the test VM or additional matching binaries
|
||||
|
||||
The first user-run kernel trace is available for Windows 11 22631 x64 with
|
||||
TESTSIGNING enabled. It confirms the BthPS3 signer-selection comparison and
|
||||
primary-signature CI path below. That one run, extracted artifacts, host mocks
|
||||
and compilation do not close the remaining work:
|
||||
|
||||
1. Run the paired `CiValidateFileObject` flag `0`/`0x20` probe on 19041/19045
|
||||
with TESTSIGNING off and a loadable signed test driver. Preserve complete
|
||||
statuses, policies and fixture/environment identities (issue #3).
|
||||
2. Complete historical and ARM64 ABI/structure verification and runtime tests.
|
||||
The [published export matrix](ci-api-compatibility.md) now covers all ten
|
||||
named APIs across 20 exact Windows 10/11 x64 and ARM64 CI files.
|
||||
ISO coverage includes Windows 10 1809/1903 revisions, 1909, 21H2/22H2 ARM64,
|
||||
and Windows 11 23H2 x64 plus 22H2/24H2 ARM64.
|
||||
The ARM64 observations establish export presence; their structure layouts
|
||||
and runtime behavior remain unverified. This closes the initial export-presence
|
||||
matrix; additional releases, servicing revisions and architectures remain
|
||||
unsampled. It does not certify ABI or runtime behavior. The six 1507–1803
|
||||
sampled bodies establish the
|
||||
48-byte policy storage and 64-byte hash-capacity gate. Chain constructors
|
||||
establish 56/72/104-byte headers; opt-in views prevent treating 1507 payload
|
||||
as the later attributes descriptor. Unsampled layouts/runtime remain open.
|
||||
3. Test real CI policy ownership on every failure/retry path under Driver
|
||||
Verifier, including allocation failures and initialized outputs.
|
||||
4. Compare real embedded/catalog-only SHA-1/SHA-256 results, catalog cache/reload
|
||||
behavior, trust-mask choices, requested signing levels and flag combinations.
|
||||
5. Measure signing-time propagation, zero/sentinel values, timestamp expiry,
|
||||
revocation freshness/network policy and weak-crypto policy. Record OS policy,
|
||||
Secure Boot/HVCI/WDAC and exact files, not just a boolean result.
|
||||
6. Extend the observed multiple/nested selection result beyond the first run.
|
||||
On Windows 11 22631 x64 with TESTSIGNING enabled, `0x20` returns Microsoft;
|
||||
`0xA0` and direct primary SHA-256 `CiCheckSignedFile` return Nefarius with the
|
||||
same signing time and matching signer/timestamp policy observations. All
|
||||
calls and policy statuses succeed, the file snapshots match, and no trace
|
||||
events are lost. TESTSIGNING-off, general enumeration, full `0x80` semantics
|
||||
and cross-build/policy behavior remain open. Extend supported PE gaps/overlays
|
||||
only after differential evidence.
|
||||
7. Resolve page-hash allocation destruction, normalized-entry/truncation meaning,
|
||||
terminal-entry handling and cross-version layouts before publishing a general
|
||||
page-hash API. `CiFreePolicyInfo` is not an established destructor for it.
|
||||
8. Investigate protected-media/initialization and ordinal-only exports separately
|
||||
if they are needed. Recovered export names alone are insufficient contracts.
|
||||
|
||||
## Checks
|
||||
|
||||
Run the six host runners in `tests/README.md`. Kernel compilation also covers
|
||||
the default modern x64/ARM64 structures, the pre-Windows-10 x64 compile variant,
|
||||
and 14 assertions in the 26100 opt-in header plus 12 historical-view assertions. Those are build checks,
|
||||
not a claim that historical or ARM64 CI runtime ABI has been verified.
|
||||
|
||||
## Consumer-driven helpers
|
||||
|
||||
The [consumer migration review](consumer-helpers.md) separates verified defects,
|
||||
existing corrections, new helpers and downstream policy. Additions include
|
||||
handle/path native CI wrappers, explicit SPKI representation/hash helpers,
|
||||
strict hex conversion, an explicit-layout x64 chain view, optional publisher
|
||||
resolution with matching cleanup, a referenced-process name API and dedicated
|
||||
process-name cleanup. The PID path fixes dangling failure output and excessive
|
||||
process access; module-base lookup clears stale output. WDM/KMDF linking is
|
||||
covered by the package-relative property sheet. No default publisher/root trust
|
||||
policy, automatic legacy fallback, or process authorization/cache is introduced.
|
||||
|
||||
Four compatibility functions carry MSVC deprecation annotations with explicit
|
||||
migration messages: heuristic digest-kind discovery, compatibility PE hashing,
|
||||
implicit-policy legacy validation and PID-based process-name lookup. Native CI
|
||||
types/functions remain available for low-level callers. The compile-only
|
||||
deprecation suite checks actual public headers in C/C++, including modern API
|
||||
controls and expected C4996 diagnostics for each older entry point.
|
||||
@@ -0,0 +1,327 @@
|
||||
# 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. The historical corpus below adds
|
||||
sampled x64 Windows 10 observations; ARM64 and unsampled-build compatibility
|
||||
remain open. Compile-target `NTDDI_VERSION` is not runtime layout negotiation.
|
||||
|
||||
The original modern research and `Domito.MinCrypt.26100.h` use the string
|
||||
`FileVersion` label **26100.8328**. That same exact binary has numeric
|
||||
`VS_FIXEDFILEINFO` version **26100.9444**, which the export matrix uses. Its
|
||||
SHA-256 and recovered contracts are unchanged; these labels identify one file.
|
||||
|
||||
## Historical x64 corpus: availability and structure evolution
|
||||
|
||||
The [CI API compatibility matrix](ci-api-compatibility.md) lists observed named
|
||||
exports for 20 exact x64/ARM64 CI files, including the later ISO samples. Consult it
|
||||
for import availability, and the contracts below for layout and ownership limits.
|
||||
|
||||
[NOTES.md](../NOTES.md) lists the exact identities of six sampled Windows 10
|
||||
files: `10.0.10240.16463`, `10.0.10586.0`, `10.0.14393.2189`, `10.0.15063.0`,
|
||||
`10.0.16299.125`, and `10.0.17134.1130`. All export `CiValidateFileObject` and
|
||||
the digest/catalog functions. The global setter is already present in sampled
|
||||
1703, correcting the old 1709 note. The publisher-name export is absent from
|
||||
all six. ISO samples additionally show absence in 19041.1 and presence in
|
||||
19041.1865 and 22000.318 x64; the original 26100 file also exports it. The first
|
||||
Windows 10 servicing update adding it remains unresolved. See CodeIntegrity-RE
|
||||
`iso-corpus-index.json`. Header guards do not negotiate runtime import availability.
|
||||
|
||||
All six sampled x64 file-object exports transfer 48 bytes into each caller
|
||||
policy, with the owned chain pointer at `+0x10`; cleanup zeros `0x30` bytes. Their
|
||||
file-object helpers require hash capacity >=64, return `STATUS_BUFFER_TOO_SMALL`
|
||||
and required length 64 for a smaller buffer, and consume the third input as a
|
||||
byte. This supports the stable policy storage prefix on these exact artifacts;
|
||||
it does not establish all field semantics or every servicing build's behavior.
|
||||
|
||||
**The chain header evolves within Windows 10. The default header's
|
||||
`NTDDI_VERSION >= NTDDI_WIN10` condition cannot safely expose every later field
|
||||
on Windows 10 1507.** Constructor evidence establishes:
|
||||
|
||||
- `10.0.10240.16463` (1507): fixed header 56 bytes (`0x38`), root ID at `+0x34`,
|
||||
variable payload beginning at `+0x38`. There is **no AuthenticodeAttributes
|
||||
header field at `+0x38`**. Accessing the default structure's member on this
|
||||
layout would interpret variable payload bytes as a blob descriptor.
|
||||
- `10.0.10586.0` (1511): fixed header 72 bytes (`0x48`), attributes descriptor at
|
||||
`+0x38`, payload beginning at `+0x48`.
|
||||
- Sampled 1607/1703/1709/1803: fixed header 104 bytes (`0x68`), root ID still at
|
||||
`+0x34`, attributes at `+0x38`, payload beginning at `+0x68`. Constructor evidence
|
||||
establishes space for the 32-byte extension at `+0x48`, not its semantic
|
||||
contents on every build. The historical view leaves this region reserved.
|
||||
|
||||
`Domito.MinCrypt.Historical.h` supplies distinct opt-in x64 views for those
|
||||
observed profiles with 12 compile-time assertions. The common prefix ends after
|
||||
root ID at `+0x34`; chain elements use 120-byte strides in these constructors,
|
||||
but the prefix views expose their storage as opaque. Do not assume every element
|
||||
field is populated on every version. The 26100-specific header separately names
|
||||
its recovered binary-ID field.
|
||||
|
||||
Choose a view only after establishing the runtime artifact/layout. `Size` is
|
||||
**total allocation bytes including payload**: a 1507 allocation can be larger
|
||||
than 72 or 104 bytes, so `Size >= sizeof(later_header)` is not version detection.
|
||||
Once layout identity is established, a size check is an additional field-read
|
||||
bound, not a substitute for identity. The default declarations remain unchanged
|
||||
for source compatibility; their later fields require this explicit runtime scope.
|
||||
|
||||
Successful exported/helper records in CodeIntegrity-RE retain the evidence:
|
||||
|
||||
- `historical/1507-export-contracts.json`:
|
||||
`ev_489c3b57057b0bc5c86950d7bb4ea25108bbc7762abc363e1be7eb5cca034796`.
|
||||
- `historical/ci-10240-helpers.json`:
|
||||
`ev_6d602c57de9614e7c5c28b9532c139f750f0e014b151000ecb41ad36f54bf764`.
|
||||
- `historical/ci-10586-exports.json`:
|
||||
`ev_a09e04976cfd90e9ba69cc6618892f15c3da166c64fa9241efae0db85234fd6e`.
|
||||
- `historical/ci-10586-helpers.json`:
|
||||
`ev_3403d5b8bf7c322fd924880d4cd9b296b98e94238cac2e1c1e9605c4b4d27255`.
|
||||
- `historical/ci-14393-contracts.json`:
|
||||
`ev_e63a4c4b7613e231795051b784b9571605b9bf81258b5dcd04d32ddc681abb41`.
|
||||
- `historical/ci-15063-contracts.json`:
|
||||
`ev_338658fb82559fa6daa8ae5487b90fe84b8e408ea7578feed6d8dff5d590e114`.
|
||||
- `historical/ci-16299-contracts.json`:
|
||||
`ev_fe5731175f0726f0d46af2f78ca615d284f8598939296815e26b5589db3001cf`.
|
||||
- `historical/ci-17134-contracts.json`:
|
||||
`ev_0c3e3359b8b7486318006afa6b95eb2257a18abeec9041a86a32db487bcbe996`.
|
||||
|
||||
## 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 26100.8328 x64 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`.
|
||||
|
||||
The additive [validation helpers](ci-validation-helpers.md) package safe result
|
||||
ownership, explicit file-object selection and embedded CI verification. They do
|
||||
not change raw CI declarations or existing legacy-helper behavior.
|
||||
|
||||
## Multiple signatures and selected results
|
||||
|
||||
`CiValidateFileObject` returns one selected signature result and its timestamp
|
||||
policy. Those two policy outputs do not enumerate two file signatures. The
|
||||
output file digest and algorithm are not expected-certificate selectors.
|
||||
|
||||
REA tracing of x64 CI 22621.2792 and the original 26100 sample found a
|
||||
flag-dependent distinction: ordinary `0x20` can continue nested-signature
|
||||
verification and retain a policy-preferred signing level. Adding `0x80` enters
|
||||
an early-exit path after the first successful full candidate verification.
|
||||
`0xA0` is therefore a concrete kernel experiment, not a proved stable
|
||||
"select primary signature" flag: `0x80` also affects other policy/fallback paths.
|
||||
An arbitrary signer index, expected-certificate predicate and continuation
|
||||
interface are not exposed by these examined wrappers.
|
||||
|
||||
For the supplied BthPS3 fixture, host SignTool verifies a primary Nefarius
|
||||
SHA-256 signature and a nested Microsoft SHA-256 signature. On both examined CI
|
||||
builds, `CiCheckSignedFile` with the matching SHA-256 digest follows the primary
|
||||
signature directly. This gives a CI-based alternative worth testing without
|
||||
implementing RSA/ECDSA, chain or timestamp verification. A subsequent user-run
|
||||
probe on Windows 11 22631 x64 with TESTSIGNING enabled confirms that `0x20`
|
||||
returns Microsoft while `0xA0` and direct SHA-256 `CiCheckSignedFile` return the
|
||||
Nefarius leaf and its signing time. Every top-level and signer/timestamp policy
|
||||
status succeeds; the two company-signer routes have matching policy observations.
|
||||
The companion `signature-selection/runtime-22631-bthps3.json` retains exact
|
||||
fixture/trace identities. This does not establish TESTSIGNING-off or general
|
||||
cross-build behavior. A different layout may still require bounded signature
|
||||
framing/enumeration.
|
||||
|
||||
The companion CodeIntegrity-RE `SIGNATURE-SELECTION.md` retains fixture hashes,
|
||||
56 procedure bodies, assembly, flag side effects and the kernel test plan.
|
||||
The 23H2 batch is
|
||||
`ev_049bd7d3aa492a9e3e4d8fcbf365ae00a6057751b26f93f3c1663c9436952ff7`;
|
||||
its primary-selection helper is in
|
||||
`ev_c867c73ca7eae7e4fbca2f44c6f6fb5904fb00459d0b84f2da8294b5b05a3866`.
|
||||
|
||||
## 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 are present in all six sampled Windows 10 x64 files and the
|
||||
analyzed Windows 11 file. The older header's “Win7–8.1 only” comments described
|
||||
a workflow, not verified export availability. Earlier first-available builds
|
||||
and pre/post-KB3033929 ABI changes still require matching Windows 7/8 artifacts.
|
||||
Import-library names and stdcall stack-byte counts alone do not settle 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: complete sampled-build/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.
|
||||
@@ -0,0 +1,200 @@
|
||||
#pragma once
|
||||
#include "Domito.h"
|
||||
|
||||
// Consumer helpers: see docs/consumer-helpers.md for ownership and trust limits.
|
||||
typedef struct _DOMITO_BYTE_VIEW
|
||||
{
|
||||
const UCHAR* Data;
|
||||
ULONG Size;
|
||||
} DOMITO_BYTE_VIEW;
|
||||
|
||||
typedef struct _DOMITO_CERTIFICATE_SPKI
|
||||
{
|
||||
DOMITO_BYTE_VIEW Encoded; // Complete SPKI SEQUENCE, including tag and length.
|
||||
DOMITO_BYTE_VIEW Content; // Legacy consumer convention: excludes that header.
|
||||
} DOMITO_CERTIFICATE_SPKI;
|
||||
|
||||
typedef enum _DOMITO_CERTIFICATE_HASH_KIND
|
||||
{
|
||||
DomitoCertificateHashDer = 0,
|
||||
DomitoCertificateHashSpkiDer = 1,
|
||||
DomitoCertificateHashSpkiContentLegacy = 2
|
||||
} DOMITO_CERTIFICATE_HASH_KIND;
|
||||
|
||||
// Bounded positional DER extraction, not X.509 validation. Views borrow CertData.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoGetCertificateSpki(
|
||||
_In_reads_bytes_(CertSize) const UCHAR* CertData,
|
||||
_In_ ULONG CertSize,
|
||||
_Out_ DOMITO_CERTIFICATE_SPKI* Spki
|
||||
);
|
||||
|
||||
// SHA-1/256/384/512. HashSize is capacity on entry, bytes on success, required
|
||||
// size on BUFFER_TOO_SMALL, zero on other failures. Hash may be NULL to size.
|
||||
// Input/output must not overlap. Hash contains zero bytes on operational failure.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoHashBuffer(
|
||||
_In_reads_bytes_opt_(Size) const UCHAR* Bytes,
|
||||
_In_ ULONG Size,
|
||||
_In_ ALG_ID Algorithm,
|
||||
_Out_writes_bytes_to_opt_(*HashSize, *HashSize) UCHAR* Hash,
|
||||
_Inout_ ULONG* HashSize
|
||||
);
|
||||
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoHashCertificate(
|
||||
_In_reads_bytes_(CertSize) const UCHAR* CertData,
|
||||
_In_ ULONG CertSize,
|
||||
_In_ DOMITO_CERTIFICATE_HASH_KIND Kind,
|
||||
_In_ ALG_ID Algorithm,
|
||||
_Out_writes_bytes_to_opt_(*HashSize, *HashSize) UCHAR* Hash,
|
||||
_Inout_ ULONG* HashSize
|
||||
);
|
||||
|
||||
// Strict length-delimited hex: no spaces, prefixes, separators or invalid digits.
|
||||
// Nonoverlapping buffers. Decode clears its output on malformed input.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoDecodeHex(
|
||||
_In_ const UNICODE_STRING* Hex,
|
||||
_Out_writes_bytes_(ByteCount) UCHAR* Bytes,
|
||||
_In_ ULONG ByteCount
|
||||
);
|
||||
|
||||
// Writes uppercase hex plus NUL; HexChars includes terminator capacity.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoEncodeHex(
|
||||
_In_reads_bytes_opt_(ByteCount) const UCHAR* Bytes,
|
||||
_In_ ULONG ByteCount,
|
||||
_Out_writes_(HexChars) WCHAR* Hex,
|
||||
_In_ ULONG HexChars
|
||||
);
|
||||
|
||||
// Resolve optional exports without creating a static import. Requires DomitoInit.
|
||||
// ModuleName is the full loaded-module path used by FindModuleBaseAddress.
|
||||
// Caller must keep that module mapped; this does not acquire a loader reference.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoFindModuleExportedFunctionAddress(
|
||||
_In_ PANSI_STRING ModuleName,
|
||||
_In_ PANSI_STRING FunctionName,
|
||||
_Outptr_result_maybenull_ PVOID* FunctionAddress
|
||||
);
|
||||
|
||||
// Results from either process-name API use one Domito allocation. The memory
|
||||
// callback pair must remain unchanged until all allocations are released.
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
void
|
||||
DomitoFreeProcessImageName(
|
||||
_In_opt_ PUNICODE_STRING ImageName
|
||||
);
|
||||
|
||||
// Caller holds a referenced PEPROCESS throughout the call. No PID re-lookup.
|
||||
// Returns a copy owned by Domito; use DomitoFreeProcessImageName once.
|
||||
// Name lookup does not establish the identity of a later reopened file.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoGetProcessImageNameEx(
|
||||
_In_ PEPROCESS Process,
|
||||
_Outptr_result_maybenull_ PUNICODE_STRING* ImageName
|
||||
);
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
// These wrappers do not require DomitoInit. Handle must be a trusted kernel
|
||||
// handle with FILE_READ_DATA; it is borrowed, referenced for the call, not closed.
|
||||
// Same initialized-result/cleanup rules and explicit flags as FileObjectEx.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoValidateFileHandleEx(
|
||||
_In_ HANDLE FileHandle,
|
||||
_In_ UINT32 ValidationFlags,
|
||||
_In_ UINT8 RequestedSigningLevel,
|
||||
_In_ DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
_Inout_ DOMITO_CI_VALIDATION_RESULT* Result
|
||||
);
|
||||
|
||||
// Absolute native path (e.g. \??\C:\...). Opens read-only, shares only reads,
|
||||
// closes its own handle. The path can be reparsed; this validates the opened
|
||||
// file, not process memory or a previous path identity. No fallback is implicit.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoValidateFilePathEx(
|
||||
_In_ const UNICODE_STRING* Path,
|
||||
_In_ UINT32 ValidationFlags,
|
||||
_In_ UINT8 RequestedSigningLevel,
|
||||
_In_ DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
_Inout_ DOMITO_CI_VALIDATION_RESULT* Result
|
||||
);
|
||||
|
||||
typedef enum _DOMITO_CI_CHAIN_LAYOUT
|
||||
{
|
||||
// Explicit caller assertion: observed x64 +0x28 array/+0x30 count, 120-byte
|
||||
// elements and a minimum 56-byte header. NOT selected by allocation Size.
|
||||
DomitoCiChainLayoutObservedX64 = 1
|
||||
} DOMITO_CI_CHAIN_LAYOUT;
|
||||
|
||||
typedef struct _DOMITO_CI_CHAIN_VIEW
|
||||
{
|
||||
const MINCRYPT_CHAIN_ELEMENT* Elements;
|
||||
ULONG Count;
|
||||
ULONG RootId; // Opaque CI identifier, not proof the last element is a root.
|
||||
} DOMITO_CI_CHAIN_VIEW;
|
||||
|
||||
// Only accepts a readable, initialized CI-owned policy, never arbitrary pointers.
|
||||
// Checks every exposed name/certificate span against CI's owned allocation.
|
||||
// Leaf-first order is observed in sampled layouts. Last element is not promised
|
||||
// to be a self-signed/trusted root. No issuer/subject byte equality is imposed.
|
||||
// View borrows policy storage until CiFreePolicyInfo/result reuse. x64 only.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoGetCiChainView(
|
||||
_In_ const MINCRYPT_POLICY_INFO* Policy,
|
||||
_In_ DOMITO_CI_CHAIN_LAYOUT Layout,
|
||||
_Out_ DOMITO_CI_CHAIN_VIEW* View
|
||||
);
|
||||
|
||||
// Optional export, resolved at call time; requires DomitoInit. No static import
|
||||
// of CiGetCertPublisherName. Extracts a display name, not certificate trust.
|
||||
// Start with {0}; release even after failure using the dedicated free function.
|
||||
// Never pass an owning previous string: free it before making another call.
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoGetCertificatePublisherName(
|
||||
_In_ const MINCERT_BLOB* Certificate,
|
||||
_Out_ PUNICODE_STRING PublisherName
|
||||
);
|
||||
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
void
|
||||
DomitoFreeCertificatePublisherName(
|
||||
_Inout_ PUNICODE_STRING PublisherName
|
||||
);
|
||||
#endif
|
||||
@@ -0,0 +1,212 @@
|
||||
/* Opt-in observed-build declarations, not a universal Windows CI SDK.
|
||||
* Observed: ci.dll 10.0.26100.8328, x64, SHA-256 documented in docs/mincrypt-contracts.md.
|
||||
* Include after ntddk.h. Uses distinct names and function-pointer types so
|
||||
* including this file does not change the default layouts or add static imports.
|
||||
* Verify the actual CI build/architecture before use; Size alone cannot prove
|
||||
* layout identity. See docs/mincrypt-contracts.md for provenance and limits.
|
||||
* Field/parameter names are descriptive analyst names unless stated otherwise.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if !defined(_M_AMD64) && !defined(__x86_64__)
|
||||
#error This research layout is validated only for x64.
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 8)
|
||||
|
||||
typedef struct _DOMITO_CI26100_BLOB
|
||||
{
|
||||
ULONG Size;
|
||||
UCHAR* Data;
|
||||
} DOMITO_CI26100_BLOB;
|
||||
|
||||
typedef struct _DOMITO_CI26100_NAME_BLOB
|
||||
{
|
||||
UCHAR* Data; /* Borrowed, length-delimited encoded bytes. */
|
||||
USHORT Size; /* Bytes, not characters. */
|
||||
UCHAR Asn1StringTag; /* Observed: 0x0c UTF8String, 0x1e BMPString. */
|
||||
} DOMITO_CI26100_NAME_BLOB;
|
||||
|
||||
typedef struct _DOMITO_CI26100_CHAIN_ELEMENT
|
||||
{
|
||||
ULONG HashAlgId;
|
||||
ULONG HashSize;
|
||||
UCHAR Hash[64];
|
||||
DOMITO_CI26100_NAME_BLOB Subject;
|
||||
DOMITO_CI26100_NAME_BLOB Issuer;
|
||||
DOMITO_CI26100_BLOB Certificate;
|
||||
} DOMITO_CI26100_CHAIN_ELEMENT;
|
||||
|
||||
typedef struct _DOMITO_CI26100_CHAIN_INFO
|
||||
{
|
||||
ULONG Size; /* Entire allocation, including trailing data. */
|
||||
DOMITO_CI26100_BLOB* PublicKeys;
|
||||
ULONG NumberOfPublicKeys;
|
||||
DOMITO_CI26100_BLOB* EKUs;
|
||||
ULONG NumberOfEKUs;
|
||||
DOMITO_CI26100_CHAIN_ELEMENT* ChainElements;
|
||||
ULONG NumberOfChainElements;
|
||||
ULONG RootId; /* Opaque classification, NOT policy bits. */
|
||||
DOMITO_CI26100_BLOB AuthenticodeAttributes;
|
||||
UCHAR PlatformManifestBinaryId[32]; /* NOT a file or certificate hash. */
|
||||
} DOMITO_CI26100_CHAIN_INFO;
|
||||
|
||||
typedef struct _DOMITO_CI26100_POLICY_INFO
|
||||
{
|
||||
ULONG Size;
|
||||
NTSTATUS VerificationStatus; /* Does not replace the API return status. */
|
||||
ULONG PolicyBits;
|
||||
DOMITO_CI26100_CHAIN_INFO* ChainInfo;
|
||||
LARGE_INTEGER RevocationTime;
|
||||
LARGE_INTEGER NotBefore;
|
||||
LARGE_INTEGER NotAfter;
|
||||
} DOMITO_CI26100_POLICY_INFO, *PDOMITO_CI26100_POLICY_INFO;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
C_ASSERT(sizeof(DOMITO_CI26100_BLOB) == 16);
|
||||
C_ASSERT(sizeof(DOMITO_CI26100_NAME_BLOB) == 16);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_NAME_BLOB,
|
||||
Asn1StringTag
|
||||
) == 10
|
||||
);
|
||||
C_ASSERT(sizeof(DOMITO_CI26100_CHAIN_ELEMENT) == 120);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_CHAIN_ELEMENT,
|
||||
Certificate
|
||||
) == 104
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_CHAIN_INFO,
|
||||
RootId
|
||||
) == 52
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_CHAIN_INFO,
|
||||
AuthenticodeAttributes
|
||||
) == 56
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_CHAIN_INFO,
|
||||
PlatformManifestBinaryId
|
||||
) == 72
|
||||
);
|
||||
C_ASSERT(sizeof(DOMITO_CI26100_CHAIN_INFO) == 104);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_POLICY_INFO,
|
||||
ChainInfo
|
||||
) == 16
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_POLICY_INFO,
|
||||
RevocationTime
|
||||
) == 24
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_POLICY_INFO,
|
||||
NotBefore
|
||||
) == 32
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI26100_POLICY_INFO,
|
||||
NotAfter
|
||||
) == 40
|
||||
);
|
||||
C_ASSERT(sizeof(DOMITO_CI26100_POLICY_INFO) == 48);
|
||||
|
||||
/* Zero-initialize policies before first use. Free existing owned chains before
|
||||
* reuse. Never copy these structs as independently owned objects with memcpy.
|
||||
* Size is an output/initialization marker here, not runtime ABI negotiation.
|
||||
*/
|
||||
typedef VOID(NTAPI* DOMITO_CI26100_FREE_POLICY_INFO)(DOMITO_CI26100_POLICY_INFO* PolicyInfo);
|
||||
|
||||
typedef NTSTATUS(NTAPI* DOMITO_CI26100_VALIDATE_FILE_OBJECT)(
|
||||
PFILE_OBJECT FileObject,
|
||||
ULONG ValidationFlags,
|
||||
UCHAR RequestedSigningLevel,
|
||||
DOMITO_CI26100_POLICY_INFO* PolicyInfo,
|
||||
DOMITO_CI26100_POLICY_INFO* TimeStampPolicyInfo,
|
||||
LARGE_INTEGER* SigningTime,
|
||||
UCHAR* Hash, /* Provide 64-byte capacity, even for SHA-1. */
|
||||
ULONG* HashSize, /* In: capacity >=64. Out success: actual size. */
|
||||
ULONG* HashAlgId
|
||||
);
|
||||
|
||||
typedef NTSTATUS(NTAPI* DOMITO_CI26100_CHECK_SIGNED_FILE)(
|
||||
PVOID Hash,
|
||||
ULONG HashSize,
|
||||
ULONG HashAlgId,
|
||||
PVOID WinCertificate, /* WIN_CERTIFICATE header, not bCertificate. */
|
||||
ULONG SizeOfSecurityDirectory,
|
||||
DOMITO_CI26100_POLICY_INFO* PolicyInfo,
|
||||
LARGE_INTEGER* SigningTime,
|
||||
DOMITO_CI26100_POLICY_INFO* TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
/* The existing BOOLEAN reload parameter ABI was not independently resolved
|
||||
* beyond the full-width use in this build; retain Domito's declaration until
|
||||
* cross-architecture call-site evidence settles its formal type.
|
||||
* Fifth argument nonzero filters on the catalog TRUSTED marker.
|
||||
* Sixth argument requires ANY overlap with catalog PolicyInfo.PolicyBits.
|
||||
*/
|
||||
typedef NTSTATUS(NTAPI* DOMITO_CI26100_VERIFY_HASH_IN_CATALOG)(
|
||||
PVOID Hash,
|
||||
ULONG HashSize,
|
||||
ULONG HashAlgId,
|
||||
BOOLEAN ReloadCatalogs,
|
||||
ULONG RequireTrustedCatalog,
|
||||
ULONG AcceptedCatalogPolicyBits,
|
||||
DOMITO_CI26100_POLICY_INFO* PolicyInfo,
|
||||
UNICODE_STRING* CatalogName, /* Optional; separate RtlFreeUnicodeString. */
|
||||
LARGE_INTEGER* SigningTime,
|
||||
DOMITO_CI26100_POLICY_INFO* TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
typedef PVOID(NTAPI* DOMITO_CI26100_ALLOCATE)(SIZE_T ByteSize);
|
||||
typedef NTSTATUS(NTAPI* DOMITO_CI26100_GET_CERT_PUBLISHER_NAME)(
|
||||
DOMITO_CI26100_BLOB* Certificate,
|
||||
DOMITO_CI26100_ALLOCATE AllocateRoutine,
|
||||
UNICODE_STRING* PublisherName
|
||||
); /* Free with callback's allocator pair. */
|
||||
|
||||
/* Proposed descriptive diagnostic names. Not official Microsoft names, not
|
||||
* an exhaustive dictionary, and not accepted-input validation flags.
|
||||
* See the report for narrower branch conditions and composite values.
|
||||
*/
|
||||
#define DOMITO_CI26100_POLICY_SELF_ISSUED_TERMINAL 0x00000001UL
|
||||
// Also present on rgbMicrosoftRoot4 together with bit 0x800.
|
||||
#define DOMITO_CI26100_POLICY_MICROSOFT_ROOT_0_1_2 0x00000002UL
|
||||
#define DOMITO_CI26100_POLICY_TEST_ROOT 0x00000004UL
|
||||
#define DOMITO_CI26100_POLICY_MICROSOFT_ROOT_3 0x00000008UL
|
||||
#define DOMITO_CI26100_POLICY_DMD_ROOT 0x00000020UL
|
||||
#define DOMITO_CI26100_POLICY_DMD_TEST_ROOT 0x00000040UL
|
||||
#define DOMITO_CI26100_POLICY_MICROSOFT_ROOT_5 0x00000080UL
|
||||
#define DOMITO_CI26100_POLICY_FLIGHT_ROOT 0x00000400UL
|
||||
#define DOMITO_CI26100_POLICY_MARKETPLACE_ROOT 0x00002000UL
|
||||
#define DOMITO_CI26100_POLICY_SUPPLIED_CERT_OR_KEY 0x00004000UL
|
||||
#define DOMITO_CI26100_POLICY_IDENTITY_ROOT_2020 0x00008000UL
|
||||
#define DOMITO_CI26100_POLICY_PARTIAL_CHAIN 0x00000010UL
|
||||
#define DOMITO_CI26100_POLICY_CHAIN_FAILURE 0x00020000UL
|
||||
#define DOMITO_CI26100_POLICY_CRYPTO_FAILURE 0x00040000UL
|
||||
#define DOMITO_CI26100_POLICY_EKU_FAILURE 0x00080000UL
|
||||
#define DOMITO_CI26100_POLICY_PAGE_HASH_FAILURE 0x00100000UL
|
||||
#define DOMITO_CI26100_POLICY_CERT_REVOKED 0x00200000UL
|
||||
#define DOMITO_CI26100_POLICY_CERT_VALIDITY_FAILURE 0x00400000UL
|
||||
#define DOMITO_CI26100_POLICY_PATH_LENGTH_FAILURE 0x08000000UL
|
||||
#define DOMITO_CI26100_POLICY_GENERIC_FAILURE 0x10000000UL
|
||||
#define DOMITO_CI26100_POLICY_CA_CONSTRAINT_FAILURE 0x20000000UL
|
||||
|
||||
/* Narrow branch observation: no usable timestamp during revoked-cert recovery;
|
||||
* not a universal flag for all signatures without a timestamp.
|
||||
*/
|
||||
#define DOMITO_CI26100_POLICY_REVOCATION_NO_TIMESTAMP 0x04000000UL
|
||||
@@ -0,0 +1,120 @@
|
||||
/* Opt-in x64 chain views for exact observed Windows 10 CI artifacts.
|
||||
* Include after ntddk.h. This does not alter Domito's default declarations.
|
||||
* Select a view only after establishing the matching runtime layout from
|
||||
* build/artifact evidence. ChainInfo->Size is TOTAL allocation size; it cannot
|
||||
* identify the header version, because variable payload follows the header.
|
||||
* See docs/mincrypt-contracts.md and NOTES.md for exact sampled versions/hashes.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Domito.MinCrypt.h"
|
||||
|
||||
#if !defined(_M_AMD64) && !defined(__x86_64__)
|
||||
#error Historical CI views are validated only for the documented x64 artifacts.
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 8)
|
||||
|
||||
// Observed ci.dll 10.0.10240.16463. The variable payload begins at +0x38;
|
||||
// AuthenticodeAttributes does not exist at that offset in this header.
|
||||
typedef struct _DOMITO_CI10240_CHAIN_INFO
|
||||
{
|
||||
UINT32 Size;
|
||||
MINCERT_BLOB* PublicKeys;
|
||||
UINT32 NumberOfPublicKeys;
|
||||
MINCERT_BLOB* EKUs;
|
||||
UINT32 NumberOfEKUs;
|
||||
PVOID ChainElements; // Constructor copies 120-byte elements; borrowed storage.
|
||||
UINT32 NumberOfChainElements;
|
||||
UINT32 RootId; // Opaque identifier, not PolicyBits.
|
||||
} DOMITO_CI10240_CHAIN_INFO, *PDOMITO_CI10240_CHAIN_INFO;
|
||||
|
||||
// Observed ci.dll 10.0.10586.0. The variable payload begins at +0x48.
|
||||
typedef struct _DOMITO_CI10586_CHAIN_INFO
|
||||
{
|
||||
UINT32 Size;
|
||||
MINCERT_BLOB* PublicKeys;
|
||||
UINT32 NumberOfPublicKeys;
|
||||
MINCERT_BLOB* EKUs;
|
||||
UINT32 NumberOfEKUs;
|
||||
PVOID ChainElements;
|
||||
UINT32 NumberOfChainElements;
|
||||
UINT32 RootId;
|
||||
MINCRYPT_ATTR_BLOB AuthenticodeAttributes;
|
||||
} DOMITO_CI10586_CHAIN_INFO, *PDOMITO_CI10586_CHAIN_INFO;
|
||||
|
||||
// Observed 10.0.14393.2189, 15063.0, 16299.125 and 17134.1130 x64.
|
||||
// The variable payload begins at +0x68. Constructor evidence establishes the
|
||||
// 32-byte extension's space, not its semantic contents on every sampled build.
|
||||
typedef struct _DOMITO_CI14393_CHAIN_INFO
|
||||
{
|
||||
UINT32 Size;
|
||||
MINCERT_BLOB* PublicKeys;
|
||||
UINT32 NumberOfPublicKeys;
|
||||
MINCERT_BLOB* EKUs;
|
||||
UINT32 NumberOfEKUs;
|
||||
PVOID ChainElements;
|
||||
UINT32 NumberOfChainElements;
|
||||
UINT32 RootId;
|
||||
MINCRYPT_ATTR_BLOB AuthenticodeAttributes;
|
||||
UINT8 ReservedExtension[32];
|
||||
} DOMITO_CI14393_CHAIN_INFO, *PDOMITO_CI14393_CHAIN_INFO;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
C_ASSERT(sizeof(DOMITO_CI10240_CHAIN_INFO) == 56);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI10240_CHAIN_INFO,
|
||||
PublicKeys
|
||||
) == 8
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI10240_CHAIN_INFO,
|
||||
EKUs
|
||||
) == 24
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI10240_CHAIN_INFO,
|
||||
ChainElements
|
||||
) == 40
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI10240_CHAIN_INFO,
|
||||
RootId
|
||||
) == 52
|
||||
);
|
||||
C_ASSERT(sizeof(DOMITO_CI10586_CHAIN_INFO) == 72);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI10586_CHAIN_INFO,
|
||||
RootId
|
||||
) == 52
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI10586_CHAIN_INFO,
|
||||
AuthenticodeAttributes
|
||||
) == 56
|
||||
);
|
||||
C_ASSERT(sizeof(DOMITO_CI14393_CHAIN_INFO) == 104);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI14393_CHAIN_INFO,
|
||||
RootId
|
||||
) == 52
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI14393_CHAIN_INFO,
|
||||
AuthenticodeAttributes
|
||||
) == 56
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
DOMITO_CI14393_CHAIN_INFO,
|
||||
ReservedExtension
|
||||
) == 72
|
||||
);
|
||||
+129
-76
@@ -259,24 +259,20 @@ typedef unsigned int ALG_ID;
|
||||
#endif //(NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
|
||||
#include <pshpack8.h>
|
||||
|
||||
typedef struct _MINCRYPTOAPI_BLOB
|
||||
{
|
||||
UINT32 Size;
|
||||
_Field_size_bytes_(Size) UINT8* Data;
|
||||
|
||||
} MINCRYPT_INTEGER_BLOB, * PMINCRYPT_INTEGER_BLOB,
|
||||
MINCRYPT_UINT_BLOB, * PMINCRYPT_UINT_BLOB,
|
||||
MINCRYPT_OBJID_BLOB, * PMINCRYPT_OBJID_BLOB,
|
||||
MINCERT_NAME_BLOB, * PMINCERT_NAME_BLOB,
|
||||
MINCERT_RDN_VALUE_BLOB, * PMINCERT_RDN_VALUE_BLOB,
|
||||
MINCERT_BLOB, * PMINCERT_BLOB,
|
||||
MINCRL_BLOB, * PMINCRL_BLOB,
|
||||
MINCRYPT_DATA_BLOB, * PMINCRYPT_DATA_BLOB,
|
||||
MINCRYPT_HASH_BLOB, * PMINCRYPT_HASH_BLOB,
|
||||
MINCRYPT_DIGEST_BLOB, * PMINCRYPT_DIGEST_BLOB,
|
||||
MINCRYPT_DER_BLOB, * PMINCRYPT_DER_BLOB,
|
||||
MINCRYPT_ATTR_BLOB, * PMINCRYPT_ATTR_BLOB;
|
||||
} MINCRYPT_INTEGER_BLOB, *PMINCRYPT_INTEGER_BLOB, MINCRYPT_UINT_BLOB, *PMINCRYPT_UINT_BLOB, MINCRYPT_OBJID_BLOB,
|
||||
*PMINCRYPT_OBJID_BLOB, MINCERT_NAME_BLOB, *PMINCERT_NAME_BLOB, MINCERT_RDN_VALUE_BLOB, *PMINCERT_RDN_VALUE_BLOB,
|
||||
MINCERT_BLOB, *PMINCERT_BLOB, MINCRL_BLOB, *PMINCRL_BLOB, MINCRYPT_DATA_BLOB, *PMINCRYPT_DATA_BLOB,
|
||||
MINCRYPT_HASH_BLOB, *PMINCRYPT_HASH_BLOB, MINCRYPT_DIGEST_BLOB, *PMINCRYPT_DIGEST_BLOB, MINCRYPT_DER_BLOB,
|
||||
*PMINCRYPT_DER_BLOB, MINCRYPT_ATTR_BLOB, *PMINCRYPT_ATTR_BLOB;
|
||||
|
||||
// Encoded, length-delimited name bytes; not necessarily ANSI or NUL-terminated.
|
||||
// The observed 26100 x64 string tag occupies padding; see the opt-in version header.
|
||||
typedef struct _MINCRYPT_NAME_BLOB
|
||||
{
|
||||
_Field_size_bytes_(Size) PCHAR Data;
|
||||
@@ -287,6 +283,8 @@ typedef struct _MINCRYPT_NAME_BLOB
|
||||
#define MINCRYPT_MAX_HASH_LENGTH (64)
|
||||
#define MINCRYPT_SHA1_LENGTH (160 / 8)
|
||||
#define MINCRYPT_SHA256_LENGTH (256 / 8)
|
||||
#define MINCRYPT_SHA384_LENGTH (384 / 8)
|
||||
#define MINCRYPT_SHA512_LENGTH (512 / 8)
|
||||
|
||||
typedef struct _MINCRYPT_CHAIN_ELEMENT
|
||||
{
|
||||
@@ -301,9 +299,11 @@ typedef struct _MINCRYPT_CHAIN_ELEMENT
|
||||
|
||||
} MINCRYPT_CHAIN_ELEMENT, *PMINCRYPT_CHAIN_ELEMENT;
|
||||
|
||||
// CI owns one chain allocation; nested pointers borrow that allocation.
|
||||
// Do not free child blobs separately or use memcpy to create a second owner.
|
||||
typedef struct _MINCRYPT_CHAIN_INFO
|
||||
{
|
||||
UINT32 Size;
|
||||
UINT32 Size; // Observed 26100 x64: total allocation bytes, including trailing data.
|
||||
|
||||
MINCERT_BLOB* PublicKeys;
|
||||
UINT32 NumberOfPublicKeys;
|
||||
@@ -316,10 +316,17 @@ typedef struct _MINCRYPT_CHAIN_INFO
|
||||
PMINCRYPT_CHAIN_ELEMENT ChainElements;
|
||||
UINT32 NumberOfChainElement;
|
||||
|
||||
// ASN.1 blob of authenticated attributes - spcSpOpusInfo, contentType, etc.
|
||||
// REQUIRES a confirmed runtime layout (10586 and later sampled profiles).
|
||||
// Sampled 10240 has only 56 header bytes: +0x38 begins variable payload.
|
||||
// NTDDI_VERSION and allocation Size cannot establish field availability.
|
||||
// See Domito.MinCrypt.Historical.h for distinct observed x64 prefix views.
|
||||
MINCRYPT_ATTR_BLOB AuthenticodeAttributes;
|
||||
|
||||
// UINT8 Hash[32];
|
||||
// RootId occupies padding at +0x34 in all six historical x64 samples and
|
||||
// 26100. The historical views name it without extending this default layout.
|
||||
// 26100's 32-byte platform-manifest binary ID at +0x48 is named only in its
|
||||
// explicit version header. Earlier 104-byte views reserve that extension.
|
||||
// Do not infer field availability solely from NTDDI_VERSION or Size.
|
||||
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
|
||||
@@ -338,7 +345,7 @@ typedef struct _MINCRYPT_CHAIN_INFO
|
||||
typedef struct _MINCRYPT_POLICY_INFO
|
||||
{
|
||||
UINT32 Size;
|
||||
UINT32 VerificationStatus;
|
||||
NTSTATUS VerificationStatus; // Check API return status separately.
|
||||
UINT32 PolicyBits;
|
||||
|
||||
MINCRYPT_CHAIN_INFO* ChainInfo;
|
||||
@@ -347,46 +354,52 @@ typedef struct _MINCRYPT_POLICY_INFO
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
|
||||
LARGE_INTEGER NotBefore; // The certificate is not valid before this time
|
||||
LARGE_INTEGER NotAfter; // The certificate is not valid after this time
|
||||
LARGE_INTEGER NotBefore; // Observed: signer certificate validity start.
|
||||
LARGE_INTEGER NotAfter; // Observed: signer certificate validity end.
|
||||
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
|
||||
}MINCRYPT_POLICY_INFO, PMINCRYPT_POLICY_INFO;
|
||||
} MINCRYPT_POLICY_INFO, *PMINCRYPT_POLICY_INFO;
|
||||
|
||||
#include <poppack.h>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resets a PolicyInfo struct - frees the dynamically allocated buffer in PolicyInfo (ChainInfo) if not null.
|
||||
* Zeros the entire PolicyInfo struct.
|
||||
* Release a known initialized policy's owned ChainInfo allocation and reset it.
|
||||
* Zero-initialize policy objects before first use. Release both signer and TSA
|
||||
* policies after use, including failures, and before reusing output storage.
|
||||
* Borrowed child pointers become invalid when the owner is released. Never clear
|
||||
* Size while a chain is still owned. Size does not negotiate the runtime layout.
|
||||
*
|
||||
* @param PolicyInfo - the struct to reset.
|
||||
* @param PolicyInfo - initialized policy, not arbitrary uninitialized storage.
|
||||
*
|
||||
* @return the struct which was reset.
|
||||
* Ignore the return value. The legacy PVOID declaration is retained for source
|
||||
* compatibility; observed 26100 x64 code provides no defined pointer return.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
MINCRYPTAPI
|
||||
PVOID
|
||||
NTAPI
|
||||
PVOID NTAPI
|
||||
CiFreePolicyInfo(
|
||||
_Inout_ MINCRYPT_POLICY_INFO* PolicyInfo
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Win7SP1-Win8.1 only (KB3033929 installed). Use CiValidateFileObject on Win10!
|
||||
* Digest-based workflow, also implemented on observed Windows 11 build 26100.
|
||||
* Prefer the file-object workflow when its policy fits. Older-build ABI and
|
||||
* KB3033929 requirements need a separately verified compatibility matrix.
|
||||
*
|
||||
* Given a file digest and signature of a file, verify the signature and provide information regarding
|
||||
* the certificates that was used for signing (the entire certificate chain)
|
||||
*
|
||||
* @param Hash - buffer containing the digest
|
||||
* @param Hash[in] - caller-computed file digest; CI does not recompute the file bytes
|
||||
*
|
||||
* @param HashSize - size of the digest, e.g. 0x14(160bit) for SHA1, 0x20(256bit) for SHA256
|
||||
*
|
||||
* @param HashAlgId - digest algorithm identifier, e.g. CALG_SHA1(0x8004), CALG_SHA_256(0x800C)
|
||||
*
|
||||
* @param SecurityDirectory - pointer to the start of the security directory
|
||||
* @param SecurityDirectory - WIN_CERTIFICATE header, not the PKCS#7 payload.
|
||||
* Observed 26100 x64 checks 4-byte pointer alignment, revision 0x200,
|
||||
* type 2, supplied size >=12 and 8 < dwLength <= supplied size.
|
||||
* This wrapper selects the first entry; it does not enumerate the table.
|
||||
*
|
||||
* @param SizeOfSecurityDirectory - size the security directory
|
||||
*
|
||||
@@ -396,94 +409,109 @@ CiFreePolicyInfo(
|
||||
*
|
||||
* @param TimeStampPolicyInfo[out] - PolicyInfo containing information about the timestamping authority (TSA) certificate chain
|
||||
*
|
||||
* @return STATUS_SUCCESS if the file digest in the signature matches the given digest and the signer cetificate is verified.
|
||||
* @return Overall verification NTSTATUS. A successful policy VerificationStatus
|
||||
* does not replace this return value: the final file-digest comparison
|
||||
* can still fail. Both policy outputs may carry diagnostics on failure.
|
||||
* See docs/mincrypt-contracts.md for ownership and policy limits.
|
||||
* Various error values otherwise, for example:
|
||||
* STATUS_INVALID_IMAGE_HASH - the digest does not match the digest in the signature
|
||||
* STATUS_INVALID_IMAGE_HASH - digest/signature or selected CI policy rejected
|
||||
* STATUS_IMAGE_CERT_REVOKED - the certificate used for signing the file is revoked
|
||||
* STATUS_IMAGE_CERT_EXPIRED - the certificate used for signing the file has expired
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
MINCRYPTAPI
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
NTSTATUS NTAPI
|
||||
CiCheckSignedFile(
|
||||
_In_ PVOID Hash,
|
||||
_In_reads_bytes_(HashSize) PVOID Hash,
|
||||
_In_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_ PVOID SecurityDirectory,
|
||||
_In_reads_bytes_(SizeOfSecurityDirectory) PVOID SecurityDirectory,
|
||||
_In_ UINT32 SizeOfSecurityDirectory,
|
||||
_Out_ MINCRYPT_POLICY_INFO* PolicyInfo,
|
||||
_Out_ LARGE_INTEGER* SigningTime,
|
||||
_Out_ MINCRYPT_POLICY_INFO* TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
// Existing Domito compatibility filter, not a universal trusted-root policy.
|
||||
// Includes test/DMD categories and a diagnostic bit; select policy deliberately.
|
||||
#define DOMITO_CI_LEGACY_CATALOG_POLICY_BITS 0x0002007FUL
|
||||
|
||||
/**
|
||||
* Win7SP1-Win8.1 only (KB3033929 installed). Use CiValidateFileObject on Win10!
|
||||
* Digest-based workflow, also implemented on observed Windows 11 build 26100.
|
||||
* Prefer the file-object workflow when its policy fits. Older-build ABI and
|
||||
* KB3033929 requirements need a separately verified compatibility matrix.
|
||||
*
|
||||
* Checks if the SHA-1 message digest is contained within a verified system catalog
|
||||
* Finds a caller-computed file digest in an accepted installed catalog.
|
||||
* Observed 26100 x64 file-hash lookup supports SHA-1 and SHA-256.
|
||||
*
|
||||
* @note must be attached to the PsInitialSystemProcess first!
|
||||
*
|
||||
* @param Hash - buffer containing the digest
|
||||
* @param Hash[in] - caller-computed file digest; CI does not recompute the file bytes
|
||||
*
|
||||
* @param HashSize - size of the digest, e.g. 0x14(160bit) for SHA1, 0x20(256bit) for SHA256
|
||||
*
|
||||
* @param HashAlgId - digest algorithm identifier, e.g. CALG_SHA1(0x8004), CALG_SHA_256(0x800C)
|
||||
*
|
||||
* @param IsReloadCatalogs - is reload catalogs cache.
|
||||
* @param ReloadCatalogs - request catalog-store reload; process/silo and cache state matter.
|
||||
*
|
||||
* @param Always0 - this is for IsReloadCatalogs, Always0 != 0 ? 16 : 24;
|
||||
* @param RequireTrustedCatalog - nonzero requires the observed catalog TRUSTED
|
||||
* extension marker. This does not itself establish Microsoft signer trust.
|
||||
*
|
||||
* @param Always2007F - unknown, always 0x2007F, maybe a mask.
|
||||
* @param AcceptedCatalogPolicyBits - candidate catalog policy-bit filter.
|
||||
* Observed 26100 x64 requires ANY overlap with catalog PolicyBits, not
|
||||
* every requested bit. This is not a flags-to-enable input.
|
||||
*
|
||||
* @param PolicyInfo[out] - PolicyInfo containing information about the signer certificate chain.
|
||||
*
|
||||
* @param CatalogName[out option] - catalog file name.
|
||||
* @param CatalogName[out option] - initialize empty; observed 26100 x64 returns
|
||||
* a separate duplicated string allocation. Release a returned Buffer
|
||||
* with RtlFreeUnicodeString, separately from both policy outputs.
|
||||
*
|
||||
* @param SigningTime[out] - when the file was signed (FILETIME format)
|
||||
*
|
||||
* @param TimeStampPolicyInfo[out] - PolicyInfo containing information about the timestamping authority (TSA) certificate chain.
|
||||
*
|
||||
* @return STATUS_SUCCESS if the file digest in the signature matches the given digest and the signer cetificate is verified.
|
||||
* @return Overall verification NTSTATUS. A successful policy VerificationStatus
|
||||
* does not replace this return value. Catalog selection and the
|
||||
* selected policy still apply. Outputs may carry diagnostics on failure.
|
||||
* See docs/mincrypt-contracts.md for ownership and policy limits.
|
||||
* Various error values otherwise, for example:
|
||||
* STATUS_INVALID_IMAGE_HASH - the digest does not match the digest in the signature
|
||||
* STATUS_INVALID_IMAGE_HASH - digest/signature or selected CI policy rejected
|
||||
* STATUS_IMAGE_CERT_REVOKED - the certificate used for signing the file is revoked
|
||||
* STATUS_IMAGE_CERT_EXPIRED - the certificate used for signing the file has expired
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
MINCRYPTAPI
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
NTSTATUS NTAPI
|
||||
CiVerifyHashInCatalog(
|
||||
_In_ PVOID Hash,
|
||||
_In_reads_bytes_(HashSize) PVOID Hash,
|
||||
_In_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_ BOOLEAN IsReloadCatalogs,
|
||||
_In_ UINT32 Always0,
|
||||
_In_ UINT32 Always2007F,
|
||||
_In_ BOOLEAN ReloadCatalogs,
|
||||
_In_ UINT32 RequireTrustedCatalog,
|
||||
_In_ UINT32 AcceptedCatalogPolicyBits,
|
||||
_Out_ MINCRYPT_POLICY_INFO* PolicyInfo,
|
||||
_Out_opt_ UNICODE_STRING* CatalogName,
|
||||
_Out_ LARGE_INTEGER* SigningTime,
|
||||
_Out_ MINCRYPT_POLICY_INFO* TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
|
||||
typedef
|
||||
_IRQL_requires_same_
|
||||
_Function_class_(MINCRYPT_ALLOCATE_ROUTINE)
|
||||
__drv_allocatesMem(Mem)
|
||||
typedef _IRQL_requires_same_ _Function_class_(MINCRYPT_ALLOCATE_ROUTINE)
|
||||
__drv_allocatesMem(
|
||||
Mem
|
||||
)
|
||||
PVOID
|
||||
NTAPI
|
||||
MINCRYPT_ALLOCATE_ROUTINE(
|
||||
_In_ SIZE_T ByteSize
|
||||
);
|
||||
MINCRYPT_ALLOCATE_ROUTINE(_In_ SIZE_T ByteSize);
|
||||
typedef MINCRYPT_ALLOCATE_ROUTINE* PMINCRYPT_ALLOCATE_ROUTINE;
|
||||
|
||||
/**
|
||||
* Parse the publisher name from the certificate
|
||||
* Parse the publisher name from the certificate.
|
||||
* Export absent from sampled Windows 10 x64 builds 10240–17134; observed present
|
||||
* on 19041.1865, 22000.318 and 26100 x64; absent on sampled 19041.1 x64.
|
||||
* The NTDDI guard does not establish runtime availability. See NOTES.md.
|
||||
*
|
||||
* @param Certificate - &PolicyInfo.ChainInfo->ChainElements[x].Certificate
|
||||
*
|
||||
@@ -491,7 +519,10 @@ typedef MINCRYPT_ALLOCATE_ROUTINE* PMINCRYPT_ALLOCATE_ROUTINE;
|
||||
*
|
||||
* @param PublisherName[out] - publisher name.
|
||||
*
|
||||
* @return buffer length.
|
||||
* @return NTSTATUS. The string byte count is returned in PublisherName->Length.
|
||||
* Free PublisherName->Buffer with the allocator paired with AllocateRoutine,
|
||||
* including on failure if a buffer was allocated. This routine extracts a name;
|
||||
* it does not verify the certificate or its signer.
|
||||
*/
|
||||
MINCRYPTAPI
|
||||
NTSTATUS
|
||||
@@ -502,23 +533,40 @@ CiGetCertPublisherName(
|
||||
_Out_ PUNICODE_STRING PublisherName
|
||||
);
|
||||
|
||||
|
||||
// Export observed already in 15063.0 x64 (1703); absent from sampled
|
||||
// 10240/10586/14393 files. This writes CI-global state, not per-file policy.
|
||||
MINCRYPTAPI
|
||||
VOID
|
||||
NTAPI
|
||||
VOID NTAPI
|
||||
CiSetTrustedOriginClaimId(
|
||||
_In_ UINT32 ClaimId
|
||||
);
|
||||
|
||||
// Domito descriptive name, not an official Microsoft flag name.
|
||||
// Observed on CI 10.0.26100.8328 x64: broadens the accepted-root mask in
|
||||
// CipGetAcceptedRootKeys. Sampled 1709/1803 instead select 0x224FF; earlier helper
|
||||
// generations differ. This is not a WinVerifyTrust-equivalent trust policy.
|
||||
// See docs/ci-validate-file-object.md for evidence and version limitations.
|
||||
#define DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE 0x00000020UL
|
||||
|
||||
// Descriptive experimental bit, not an official or isolated selector flag.
|
||||
// Participates in first-success guards in examined 22621/26100 x64 CI bodies;
|
||||
// also changes default-path policy and fallback. One 22631 TESTSIGNING-enabled
|
||||
// BthPS3 run confirms 0x20|0x80 returns its primary company signer. It is not an
|
||||
// arbitrary signer index, guaranteed primary selection, or a production default.
|
||||
#define DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL 0x00000080UL
|
||||
|
||||
/**
|
||||
* Given a file object, verify the signature and provide information regarding
|
||||
* the certificates that was used for signing (the entire certificate chain)
|
||||
*
|
||||
* @param FileObject - FileObject of the PE in question
|
||||
*
|
||||
* @param Unkonwn1 - unknown, 0 is a valid value. (Unkonwn1 and Unkonwn2 together calculate the minimum support algorithm)
|
||||
* @param ValidationFlags - validation policy flags; 0 is a valid value.
|
||||
* See DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE and the accompanying documentation.
|
||||
*
|
||||
* @param Unkonwn2 - unknown, 0 is a valid value. (^ the words above refer to 'CipGetHashAlgorithmForLegacyScenario')
|
||||
* @param RequestedSigningLevel - requested signing level; 0 is used by the probe.
|
||||
* CI 26100 x64 consumes the low byte. UINT32 is retained for ABI compatibility
|
||||
* until other supported architectures/builds are verified; use values in 0..255.
|
||||
*
|
||||
* @param PolicyInfo[out] - PolicyInfo containing information about the signer certificate chain.
|
||||
*
|
||||
@@ -526,30 +574,35 @@ CiSetTrustedOriginClaimId(
|
||||
*
|
||||
* @param SigningTime[out] - when the file was signed (FILETIME format)
|
||||
*
|
||||
* @param Hash - buffer containing the digest
|
||||
* @param Hash[out] - file digest, not a certificate thumbprint. Supply at least
|
||||
* MINCRYPT_MAX_HASH_LENGTH bytes (required by the observed 26100 x64 helper).
|
||||
*
|
||||
* @param HashSize - size of the digest, e.g. 0x14(160bit) for SHA1, 0x20(256bit) for SHA256
|
||||
* @param HashSize[in,out] - buffer capacity on entry; actual digest size on success.
|
||||
* Initialize to sizeof(Hash) for every call, including retries.
|
||||
*
|
||||
* @param HashAlgId - digest algorithm identifier, e.g. CALG_SHA1(0x8004), CALG_SHA_256(0x800C)
|
||||
*
|
||||
* @return STATUS_SUCCESS if the file digest in the signature matches the given digest and the signer cetificate is verified.
|
||||
* Initialize both policy outputs to empty objects and free both with CiFreePolicyInfo
|
||||
* before reuse. Do not interpret failed-call outputs as a verification result.
|
||||
*
|
||||
* @return STATUS_SUCCESS when verification succeeds under the selected CI policy.
|
||||
* This does not by itself establish the application's signer trust policy.
|
||||
* Various error values otherwise, for example:
|
||||
* STATUS_INVALID_IMAGE_HASH - the digest does not match the digest in the signature
|
||||
* STATUS_INVALID_IMAGE_HASH - digest/signature or selected CI policy rejected
|
||||
* STATUS_IMAGE_CERT_REVOKED - the certificate used for signing the file is revoked
|
||||
* STATUS_IMAGE_CERT_EXPIRED - the certificate used for signing the file has expired
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
MINCRYPTAPI
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
NTSTATUS NTAPI
|
||||
CiValidateFileObject(
|
||||
_In_ FILE_OBJECT* FileObject,
|
||||
_In_opt_ UINT32 Unkonwn1,
|
||||
_In_opt_ UINT32 Unkonwn2,
|
||||
_In_ UINT32 ValidationFlags,
|
||||
_In_ UINT32 RequestedSigningLevel,
|
||||
_Out_ MINCRYPT_POLICY_INFO* PolicyInfo,
|
||||
_Out_ MINCRYPT_POLICY_INFO* TimeStampPolicyInfo,
|
||||
_Out_ LARGE_INTEGER* SigningTime,
|
||||
_Out_ UINT8* Hash,
|
||||
_Out_writes_bytes_(*HashSize) UINT8* Hash,
|
||||
_Inout_ UINT32* HashSize,
|
||||
_Out_ ALG_ID* HashAlgId
|
||||
);
|
||||
|
||||
+214
-36
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <Domito.MinCrypt.h>
|
||||
|
||||
|
||||
/* ___
|
||||
* / __|___ _ __ _ __ ___ _ _
|
||||
* | (__/ _ \ ' \| ' \/ _ \ ' \
|
||||
@@ -25,7 +24,6 @@ EXTERN_C
|
||||
void
|
||||
DomitoShutdown();
|
||||
|
||||
|
||||
/* __ __ __ __ _
|
||||
* | \/ |___ _ __ ___ _ _ _ _ | \/ |__ _ _ _ __ _ __ _ ___ _ __ ___ _ _| |_
|
||||
* | |\/| / -_) ' \/ _ \ '_| || | | |\/| / _` | ' \/ _` / _` / -_) ' \/ -_) ' \ _|
|
||||
@@ -36,28 +34,20 @@ DomitoShutdown();
|
||||
//
|
||||
// Allocator function the library uses.
|
||||
//
|
||||
typedef
|
||||
_IRQL_requires_same_
|
||||
_Function_class_(EVT_DOMITO_ALLOCATE_ROUTINE)
|
||||
__drv_allocatesMem(mem)
|
||||
typedef _IRQL_requires_same_ _Function_class_(EVT_DOMITO_ALLOCATE_ROUTINE)
|
||||
__drv_allocatesMem(
|
||||
mem
|
||||
)
|
||||
PVOID
|
||||
NTAPI
|
||||
EVT_DOMITO_ALLOCATE_ROUTINE(
|
||||
_In_ SIZE_T ByteSize
|
||||
);
|
||||
EVT_DOMITO_ALLOCATE_ROUTINE(_In_ SIZE_T ByteSize);
|
||||
typedef EVT_DOMITO_ALLOCATE_ROUTINE* PFN_DOMITO_ALLOCATE_ROUTINE;
|
||||
|
||||
//
|
||||
// Freeing function the library uses.
|
||||
//
|
||||
typedef
|
||||
_IRQL_requires_same_
|
||||
_Function_class_(EVT_DOMITO_FREE_ROUTINE)
|
||||
void
|
||||
NTAPI
|
||||
EVT_DOMITO_FREE_ROUTINE(
|
||||
_In_ __drv_freesMem(mem) PVOID Memory
|
||||
);
|
||||
typedef _IRQL_requires_same_ _Function_class_(EVT_DOMITO_FREE_ROUTINE) void NTAPI
|
||||
EVT_DOMITO_FREE_ROUTINE(_In_ __drv_freesMem(mem) PVOID Memory);
|
||||
typedef EVT_DOMITO_FREE_ROUTINE* PFN_DOMITO_FREE_ROUTINE;
|
||||
|
||||
//
|
||||
@@ -81,7 +71,9 @@ DomitoGetMemoryFunctions(
|
||||
);
|
||||
|
||||
//
|
||||
// Replace Domito's memory allocation functions with a custom set
|
||||
// Replace Domito's memory allocation functions with a compatible pair during
|
||||
// quiescent initialization. No in-flight calls or outstanding callback-backed
|
||||
// allocations may span a pair change. Replacement is not synchronized.
|
||||
EXTERN_C
|
||||
void
|
||||
DomitoSetMemoryFunctions(
|
||||
@@ -89,7 +81,6 @@ DomitoSetMemoryFunctions(
|
||||
_In_opt_ PFN_DOMITO_FREE_ROUTINE Free
|
||||
);
|
||||
|
||||
|
||||
/* ___ _ _
|
||||
* / __|_ _ _ _ _ __| |_ ___ __ _ _ _ __ _ _ __| |_ _ _
|
||||
* | (__| '_| || | '_ \ _/ _ \/ _` | '_/ _` | '_ \ ' \ || |
|
||||
@@ -141,6 +132,7 @@ typedef struct _WIN_CERTIFICATE
|
||||
// Converts a WinCrypt CALG_ID to a BCRYPT_ALGORITHM identifier.
|
||||
//
|
||||
PCWSTR
|
||||
|
||||
FORCEINLINE
|
||||
DOMITO_CALG_TO_BCRYPT_ALGORITHM(
|
||||
_In_ UINT32 Calg
|
||||
@@ -161,7 +153,6 @@ DOMITO_CALG_TO_BCRYPT_ALGORITHM(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ___ _ ___ _ _ _
|
||||
* / __|___ __| |___ |_ _|_ _| |_ ___ __ _ _ _(_) |_ _ _
|
||||
* | (__/ _ \/ _` / -_) | || ' \ _/ -_) _` | '_| | _| || |
|
||||
@@ -170,26 +161,74 @@ DOMITO_CALG_TO_BCRYPT_ALGORITHM(
|
||||
*/
|
||||
|
||||
//
|
||||
// Extracts the CALG_ID from a signed PE that was used to
|
||||
// calculate the message digest when it was signed
|
||||
// Heuristically detects the CALG_ID in the first WIN_CERTIFICATE using byte patterns.
|
||||
// Returns CALG_SHA1 when absent, malformed, or unrecognized; this is not ASN.1
|
||||
// validation or signature verification. The directory array must contain the
|
||||
// IMAGE_DIRECTORY_ENTRY_SECURITY entry.
|
||||
//
|
||||
_IRQL_requires_max_(DISPATCH_LEVEL)
|
||||
EXTERN_C
|
||||
UINT32
|
||||
__declspec(deprecated(
|
||||
"Use DomitoGetPortableExecutableDigestKindEx; heuristic detection silently falls back to SHA-1. Check the returned NTSTATUS."
|
||||
)) UINT32
|
||||
DomitoGetPortableExecutableDigestKind(
|
||||
_In_ PUCHAR pPeBytes,
|
||||
_In_ ULONG PeSize,
|
||||
_In_ PIMAGE_DATA_DIRECTORY pImgDataDirectory
|
||||
);
|
||||
|
||||
//
|
||||
// Extracts Authenticode signing information and calculates the file digest of a PE file.
|
||||
//
|
||||
// Reads the first PKCS#7 entry's SpcIndirectDataContent DigestInfo algorithm
|
||||
// using bounded DER navigation. Does not validate the signature or select among
|
||||
// multiple/nested signatures. Absent table => STATUS_NOT_FOUND; unsupported OID
|
||||
// or certificate type => STATUS_NOT_SUPPORTED; malformed DER => format error.
|
||||
// Unlike the compatibility helper above, it never silently falls back to SHA-1.
|
||||
_IRQL_requires_max_(DISPATCH_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoGetPortableExecutableDigestKindEx(
|
||||
_In_reads_bytes_(PeSize) const UCHAR* PeBytes,
|
||||
_In_ ULONG PeSize,
|
||||
_In_reads_(IMAGE_DIRECTORY_ENTRY_SECURITY + 1) const IMAGE_DATA_DIRECTORY* Directories,
|
||||
_Out_ PUINT32 DigestCalg
|
||||
);
|
||||
|
||||
// Algorithm-explicit Authenticode digest for the supported layout subset:
|
||||
// bounded PE32/PE32+ headers, contiguous nonoverlapping raw sections in file
|
||||
// offset order, optional overlay before a complete EOF certificate table.
|
||||
// SHA-1/256/384/512 supported. Gaps/overlaps/nonterminal certificates are rejected.
|
||||
// No signature trust decision is made. Outputs are cleared on failure; the
|
||||
// certificate borrows stable input memory, and only the digest must be freed.
|
||||
// See docs/authenticode-digests.md for policy and Windows SIP comparisons.
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoCalculatePortableExecutableDigestEx(
|
||||
_In_reads_bytes_(PeSize) const UCHAR* PeBytes,
|
||||
_In_ ULONG PeSize,
|
||||
_In_ UINT32 HashAlgId,
|
||||
_Out_ PULONG DigestSize,
|
||||
_Outptr_result_bytebuffer_(*DigestSize) PVOID* Digest,
|
||||
_Outptr_result_maybenull_ LPWIN_CERTIFICATE* Certificate,
|
||||
_Out_ PULONG SecuritySize
|
||||
);
|
||||
|
||||
//
|
||||
// Calculates a PE digest using the existing linear hashing convention.
|
||||
// Validates accessed header/certificate ranges and clears outputs on failure
|
||||
// when all output pointers are valid. The input buffer must remain stable.
|
||||
// The returned certificate pointer borrows the input buffer; free only the digest
|
||||
// with DomitoFreePortableExecutableDigest. Algorithm detection is heuristic;
|
||||
// general section-ordered Authenticode hashing is not implemented here.
|
||||
//
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
__declspec(deprecated(
|
||||
"Use DomitoCalculatePortableExecutableDigestEx with an explicit algorithm; review its supported PE layout and failure statuses."
|
||||
)) NTSTATUS
|
||||
DomitoCalculatePortableExecutableDigest(
|
||||
_In_ PUCHAR pPeBytes,
|
||||
_In_ ULONG PeSize,
|
||||
@@ -210,14 +249,121 @@ DomitoFreePortableExecutableDigest(
|
||||
_In_ PVOID pDigestOut
|
||||
);
|
||||
|
||||
//
|
||||
// Verifies if the Authenticode signature of a give PE file matches the provided (e.g. SHA1) file digest.
|
||||
//
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
// Native forwards caller flags unchanged. The experimental x64 mode additionally
|
||||
// sets 0x80; it stops after the first accepted candidate in examined CI builds,
|
||||
// and also changes other policy/fallback paths. Neither mode selects a publisher
|
||||
// or enumerates signatures. No root-acceptance flag is added automatically.
|
||||
typedef enum _DOMITO_CI_SIGNATURE_SELECTION
|
||||
{
|
||||
DomitoCiSignatureSelectionNative = 0,
|
||||
DomitoCiSignatureSelectionFirstSuccessfulExperimental = 1
|
||||
} DOMITO_CI_SIGNATURE_SELECTION;
|
||||
|
||||
// Zero-initialize before first use. Both policies own their CI chain allocations,
|
||||
// including possible diagnostic allocations on failure. Do not copy as an owner.
|
||||
// Hash is a file digest, not a certificate thumbprint. Hash/algorithm/signing time
|
||||
// are cleared on failure; policies may retain diagnostics. Check return NTSTATUS.
|
||||
typedef struct _DOMITO_CI_VALIDATION_RESULT
|
||||
{
|
||||
MINCRYPT_POLICY_INFO PolicyInfo;
|
||||
MINCRYPT_POLICY_INFO TimeStampPolicyInfo;
|
||||
LARGE_INTEGER SigningTime;
|
||||
UINT8 Hash[MINCRYPT_MAX_HASH_LENGTH];
|
||||
UINT32 HashSize;
|
||||
ALG_ID HashAlgId;
|
||||
UINT32 FileObjectValidationFlags; // Flags passed to CI export; zero for embedded helper.
|
||||
UINT8 RequestedSigningLevel; // File-object helper only.
|
||||
} DOMITO_CI_VALIDATION_RESULT, *PDOMITO_CI_VALIDATION_RESULT;
|
||||
|
||||
// Safe for NULL or an empty/initialized result; resets all fields after cleanup.
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
void
|
||||
DomitoFreeCiValidationResult(
|
||||
_Inout_opt_ DOMITO_CI_VALIDATION_RESULT* Result
|
||||
);
|
||||
|
||||
// Referenced FILE_OBJECT at PASSIVE_LEVEL; keep file bytes stable. Previous result
|
||||
// policies are released before reuse, including invalid-argument failures.
|
||||
// Supplies a fresh 64-byte hash buffer on every call. FirstSuccessfulExperimental
|
||||
// is opt-in and x64-only; other architectures return STATUS_NOT_SUPPORTED for it.
|
||||
// Only one selected signer plus its timestamp policy is returned. The caller
|
||||
// must enforce its expected certificate and intended trust policy separately.
|
||||
_Success_(return >= 0)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoValidateFileObjectEx(
|
||||
_In_ PFILE_OBJECT FileObject,
|
||||
_In_ UINT32 ValidationFlags,
|
||||
_In_ UINT8 RequestedSigningLevel,
|
||||
_In_ DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
_Inout_ DOMITO_CI_VALIDATION_RESULT* Result
|
||||
);
|
||||
|
||||
// Hash and verify the first WIN_CERTIFICATE in a stable PE buffer. Uses the
|
||||
// explicit digest helper's supported contiguous-section/EOF-certificate subset.
|
||||
// SHA-1/256/384/512 are explicit; no algorithm retry or catalog fallback occurs.
|
||||
// CI may select a nested signature under its algorithm rules: this is not a
|
||||
// universal force-primary API. Matching SHA-256 selects the company primary in
|
||||
// the tested dual-signed BthPS3 fixture. Caller still checks the returned signer.
|
||||
_Success_(return >= 0)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(
|
||||
_In_reads_bytes_(PeSize) const UCHAR* PeBytes,
|
||||
_In_ ULONG PeSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_Inout_ DOMITO_CI_VALIDATION_RESULT* Result
|
||||
);
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
|
||||
#define DOMITO_CI_SOURCE_EMBEDDED 1UL
|
||||
#define DOMITO_CI_SOURCE_CATALOG 2UL
|
||||
|
||||
// Policy-explicit legacy helper. SHA-1/256 only; hash size must match algorithm.
|
||||
// Policy objects must be zero-initialized or own valid CI results; both are freed
|
||||
// before each attempt. Caller owns both final policy outputs, including failure.
|
||||
// Embedded validation is tried first; only INVALID_IMAGE_HASH permits catalog
|
||||
// fallback/reload. AcceptedCatalogPolicyBits uses ANY-overlap catalog filtering;
|
||||
// RequireTrustedCatalog gates the observed marker, not publisher identity.
|
||||
// Source is 0 on failure, EMBEDDED/CATALOG on success. Stable file bytes and a
|
||||
// handle valid in the caller's current process context are required during I/O.
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoValidateFileLegacyModeEx(
|
||||
_In_ HANDLE FileHandle,
|
||||
_In_reads_bytes_(HashSize) PVOID Hash,
|
||||
_In_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_ const IMAGE_DATA_DIRECTORY* SecurityDirectory,
|
||||
_Inout_ MINCRYPT_POLICY_INFO* PolicyInfo,
|
||||
_Out_ LARGE_INTEGER* SigningTime,
|
||||
_Inout_ MINCRYPT_POLICY_INFO* TimeStampPolicyInfo,
|
||||
_In_ UINT32 AcceptedCatalogPolicyBits,
|
||||
_In_ BOOLEAN RequireTrustedCatalog,
|
||||
_Out_ PULONG ValidationSource
|
||||
);
|
||||
|
||||
//
|
||||
// Compatibility wrapper around DomitoValidateFileLegacyModeEx, with catalog
|
||||
// mask DOMITO_CI_LEGACY_CATALOG_POLICY_BITS and RequireTrustedCatalog = FALSE.
|
||||
// The same initialization, ownership, hash, handle, and stable-file rules apply.
|
||||
//
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
__declspec(deprecated(
|
||||
"Use DomitoValidateFileLegacyModeEx with explicit catalog policy and validation-source output; do not change trust policy implicitly."
|
||||
)) NTSTATUS
|
||||
DomitoValidateFileLegacyMode(
|
||||
_In_ HANDLE FileHandle,
|
||||
_In_ PVOID Hash,
|
||||
@@ -229,7 +375,6 @@ DomitoValidateFileLegacyMode(
|
||||
_Inout_ MINCRYPT_POLICY_INFO* TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
|
||||
/* __ __ _
|
||||
* | \/ (_)___ __
|
||||
* | |\/| | (_-</ _|_
|
||||
@@ -238,7 +383,7 @@ DomitoValidateFileLegacyMode(
|
||||
*/
|
||||
|
||||
//
|
||||
// Finds the base address of a driver module.
|
||||
// Finds the base address of a driver module. Clears the optional output first.
|
||||
//
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
@@ -251,7 +396,10 @@ DomitoFindModuleBaseAddress(
|
||||
);
|
||||
|
||||
//
|
||||
// Finds the address of an exported function by name.
|
||||
// Finds an export by name (case-insensitive) in a valid loader-mapped module.
|
||||
// ModuleBase must remain mapped; arbitrary file buffers are not supported.
|
||||
// Export table spans are checked against SizeOfImage. Forwarded exports return
|
||||
// STATUS_NOT_SUPPORTED; the optional output is cleared before lookup.
|
||||
//
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
@@ -282,7 +430,28 @@ DomitoMemorySearchPattern(
|
||||
);
|
||||
|
||||
//
|
||||
// Reads from the beginning of a file until the end or the buffer size is reached.
|
||||
// Reads exactly BufferSize bytes at a nonnegative absolute file offset.
|
||||
// Same completion, buffer, handle, APC, and short-read contract as DomitoReadFile.
|
||||
//
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
DomitoReadFileAt(
|
||||
_In_ HANDLE FileHandle,
|
||||
_Out_writes_bytes_(BufferSize) PVOID Buffer,
|
||||
_In_ ULONG BufferSize,
|
||||
_In_ const LARGE_INTEGER* ByteOffset
|
||||
);
|
||||
|
||||
//
|
||||
// Reads exactly BufferSize bytes from file offset zero. Short reads return
|
||||
// STATUS_END_OF_FILE; Buffer may then contain a partial read. Zero length succeeds.
|
||||
// Waits for pending I/O on a private event before returning. Requires PASSIVE_LEVEL
|
||||
// with special kernel APCs enabled, a readable handle valid in the current process
|
||||
// (or a kernel handle), and a buffer that remains valid for the entire call.
|
||||
// For synchronous handles, the explicit read also updates the file position.
|
||||
//
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
@@ -296,14 +465,23 @@ DomitoReadFile(
|
||||
);
|
||||
|
||||
//
|
||||
// Gets the name of the main image of the process identified by PID.
|
||||
// Gets the main image name after resolving the ULONG PID once to a process handle.
|
||||
// Requires DomitoInit. Output is NULL on failure; successful result is one Domito
|
||||
// allocation containing UNICODE_STRING and its buffer. Use DomitoFreeProcessImageName.
|
||||
// Do not change memory callbacks with outstanding allocations. PID reuse can race
|
||||
// the initial open; a later path reopen is not proof of original image identity.
|
||||
//
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
__declspec(deprecated(
|
||||
"Use DomitoGetProcessImageNameEx with a caller-referenced PEPROCESS to avoid a fresh PID lookup; free the result with DomitoFreeProcessImageName."
|
||||
)) NTSTATUS
|
||||
DomitoGetProcessImageName(
|
||||
_In_ ULONG ProcessId,
|
||||
_Inout_ PUNICODE_STRING* ProcessImageName
|
||||
_Outptr_result_maybenull_ PUNICODE_STRING* ProcessImageName
|
||||
);
|
||||
|
||||
// Additive consumer helpers, included here for existing Domito.h clients.
|
||||
#include "Domito.Consumer.h"
|
||||
@@ -0,0 +1,396 @@
|
||||
#include "Domito.Internal.h"
|
||||
|
||||
typedef struct _DOMITO_CERTIFICATE_DER_VIEW
|
||||
{
|
||||
const UCHAR* Data;
|
||||
ULONG Size;
|
||||
} DOMITO_CERTIFICATE_DER_VIEW;
|
||||
|
||||
static BOOLEAN
|
||||
DomitoCertificateDerTake(
|
||||
DOMITO_CERTIFICATE_DER_VIEW* Input,
|
||||
UCHAR Tag,
|
||||
DOMITO_CERTIFICATE_DER_VIEW* Content,
|
||||
DOMITO_CERTIFICATE_DER_VIEW* Encoded = nullptr
|
||||
)
|
||||
{
|
||||
if (Input->Size < 2 || Input->Data[0] != Tag)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ULONG length = Input->Data[1];
|
||||
ULONG header = 2;
|
||||
|
||||
if (length & 0x80)
|
||||
{
|
||||
const ULONG count = length & 0x7f;
|
||||
|
||||
if (!count || count > 4 || count > Input->Size - 2 || !Input->Data[2])
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
length = 0;
|
||||
|
||||
for (ULONG i = 0; i < count; ++i)
|
||||
{
|
||||
length = (length << 8) | Input->Data[header++];
|
||||
}
|
||||
|
||||
if (length < 128)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (length > Input->Size - header)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*Content = {Input->Data + header, length};
|
||||
|
||||
if (Encoded)
|
||||
{
|
||||
*Encoded = {Input->Data, header + length};
|
||||
}
|
||||
|
||||
Input->Data += header + length;
|
||||
Input->Size -= header + length;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoGetCertificateSpki(
|
||||
const UCHAR* CertData,
|
||||
ULONG CertSize,
|
||||
DOMITO_CERTIFICATE_SPKI* Spki
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Spki)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*Spki = {};
|
||||
|
||||
if (!CertData || !CertSize)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DOMITO_CERTIFICATE_DER_VIEW certificateInput = {CertData, CertSize};
|
||||
DOMITO_CERTIFICATE_DER_VIEW certificate;
|
||||
DOMITO_CERTIFICATE_DER_VIEW tbsCertificate;
|
||||
DOMITO_CERTIFICATE_DER_VIEW fieldContent;
|
||||
DOMITO_CERTIFICATE_DER_VIEW encodedSpki;
|
||||
DOMITO_CERTIFICATE_DER_VIEW spkiContent;
|
||||
// Validate complete certificate framing, without interpreting trust/algorithms.
|
||||
if (!DomitoCertificateDerTake(&certificateInput, 0x30, &certificate) || certificateInput.Size ||
|
||||
!DomitoCertificateDerTake(&certificate, 0x30, &tbsCertificate) ||
|
||||
!DomitoCertificateDerTake(&certificate, 0x30, &fieldContent) ||
|
||||
!DomitoCertificateDerTake(&certificate, 0x03, &fieldContent) || !fieldContent.Size ||
|
||||
fieldContent.Data[0] > 7 || certificate.Size)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
if (tbsCertificate.Size && tbsCertificate.Data[0] == 0xa0)
|
||||
{
|
||||
DOMITO_CERTIFICATE_DER_VIEW certificateVersion;
|
||||
|
||||
if (!DomitoCertificateDerTake(&tbsCertificate, 0xa0, &fieldContent) ||
|
||||
!DomitoCertificateDerTake(&fieldContent, 0x02, &certificateVersion) || fieldContent.Size ||
|
||||
certificateVersion.Size != 1 || certificateVersion.Data[0] > 2)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
const UCHAR tbsFieldTags[] = {0x02, 0x30, 0x30, 0x30, 0x30};
|
||||
|
||||
for (ULONG i = 0; i < RTL_NUMBER_OF(tbsFieldTags); ++i)
|
||||
{
|
||||
if (!DomitoCertificateDerTake(&tbsCertificate, tbsFieldTags[i], &fieldContent) ||
|
||||
(i == 0 && !fieldContent.Size))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!DomitoCertificateDerTake(&tbsCertificate, 0x30, &spkiContent, &encodedSpki))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
DOMITO_CERTIFICATE_DER_VIEW spkiFields = spkiContent;
|
||||
DOMITO_CERTIFICATE_DER_VIEW keyAlgorithm;
|
||||
DOMITO_CERTIFICATE_DER_VIEW algorithmOid;
|
||||
DOMITO_CERTIFICATE_DER_VIEW publicKeyBits;
|
||||
|
||||
if (!DomitoCertificateDerTake(&spkiFields, 0x30, &keyAlgorithm) ||
|
||||
!DomitoCertificateDerTake(&keyAlgorithm, 0x06, &algorithmOid) || !algorithmOid.Size ||
|
||||
!DomitoCertificateDerTake(&spkiFields, 0x03, &publicKeyBits) || publicKeyBits.Size < 2 ||
|
||||
publicKeyBits.Data[0] > 7 || spkiFields.Size)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
Spki->Encoded = {encodedSpki.Data, encodedSpki.Size};
|
||||
Spki->Content = {spkiContent.Data, spkiContent.Size};
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoHashBuffer(
|
||||
const UCHAR* Bytes,
|
||||
ULONG Size,
|
||||
ALG_ID Algorithm,
|
||||
UCHAR* Hash,
|
||||
ULONG* HashSize
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!HashSize)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const ULONG capacity = *HashSize;
|
||||
ULONG required;
|
||||
|
||||
switch (Algorithm)
|
||||
{
|
||||
case CALG_SHA1:
|
||||
required = 20;
|
||||
break;
|
||||
case CALG_SHA_256:
|
||||
required = 32;
|
||||
break;
|
||||
case CALG_SHA_384:
|
||||
required = 48;
|
||||
break;
|
||||
case CALG_SHA_512:
|
||||
required = 64;
|
||||
break;
|
||||
default:
|
||||
*HashSize = 0;
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (!Bytes && Size)
|
||||
{
|
||||
*HashSize = 0;
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!Hash || capacity < required)
|
||||
{
|
||||
*HashSize = required;
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
RtlZeroMemory(Hash, required);
|
||||
BCRYPT_ALG_HANDLE algorithm = nullptr;
|
||||
BCRYPT_HASH_HANDLE hash = nullptr;
|
||||
NTSTATUS status =
|
||||
BCryptOpenAlgorithmProvider(&algorithm, DOMITO_CALG_TO_BCRYPT_ALGORITHM(Algorithm), MS_PRIMITIVE_PROVIDER, 0);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
status = BCryptCreateHash(algorithm, &hash, nullptr, 0, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status) && Size)
|
||||
{
|
||||
status = BCryptHashData(hash, const_cast<PUCHAR>(Bytes), Size, 0);
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
status = BCryptFinishHash(hash, Hash, required, 0);
|
||||
}
|
||||
|
||||
if (hash)
|
||||
{
|
||||
BCryptDestroyHash(hash);
|
||||
}
|
||||
|
||||
if (algorithm)
|
||||
{
|
||||
BCryptCloseAlgorithmProvider(algorithm, 0);
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
*HashSize = required;
|
||||
}
|
||||
else
|
||||
{
|
||||
RtlZeroMemory(Hash, required);
|
||||
*HashSize = 0;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoHashCertificate(
|
||||
const UCHAR* CertData,
|
||||
ULONG CertSize,
|
||||
DOMITO_CERTIFICATE_HASH_KIND Kind,
|
||||
ALG_ID Algorithm,
|
||||
UCHAR* Hash,
|
||||
ULONG* HashSize
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!HashSize)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!CertData || !CertSize)
|
||||
{
|
||||
*HashSize = 0;
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DOMITO_CERTIFICATE_SPKI spki = {};
|
||||
|
||||
if (Kind == DomitoCertificateHashSpkiDer || Kind == DomitoCertificateHashSpkiContentLegacy)
|
||||
{
|
||||
NTSTATUS status = DomitoGetCertificateSpki(CertData, CertSize, &spki);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
*HashSize = 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
const DOMITO_BYTE_VIEW selected = Kind == DomitoCertificateHashSpkiDer ? spki.Encoded : spki.Content;
|
||||
return DomitoHashBuffer(selected.Data, selected.Size, Algorithm, Hash, HashSize);
|
||||
}
|
||||
|
||||
if (Kind != DomitoCertificateHashDer)
|
||||
{
|
||||
*HashSize = 0;
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// Whole-certificate fingerprint: hashes exact input bytes, no trust claim.
|
||||
return DomitoHashBuffer(CertData, CertSize, Algorithm, Hash, HashSize);
|
||||
}
|
||||
|
||||
static int
|
||||
DomitoHexDigit(
|
||||
WCHAR Value
|
||||
)
|
||||
{
|
||||
if (Value >= L'0' && Value <= L'9')
|
||||
{
|
||||
return Value - L'0';
|
||||
}
|
||||
|
||||
if (Value >= L'A' && Value <= L'F')
|
||||
{
|
||||
return Value - L'A' + 10;
|
||||
}
|
||||
|
||||
if (Value >= L'a' && Value <= L'f')
|
||||
{
|
||||
return Value - L'a' + 10;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoDecodeHex(
|
||||
const UNICODE_STRING* Hex,
|
||||
UCHAR* Bytes,
|
||||
ULONG ByteCount
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Bytes && ByteCount)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Bytes && ByteCount)
|
||||
{
|
||||
RtlZeroMemory(Bytes, ByteCount);
|
||||
}
|
||||
|
||||
if (!Hex || Hex->Length > Hex->MaximumLength || (Hex->Length && !Hex->Buffer) ||
|
||||
ByteCount > MAXUSHORT / (2 * sizeof(WCHAR)) || Hex->Length != ByteCount * 2 * sizeof(WCHAR))
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < ByteCount * 2; ++i)
|
||||
{
|
||||
if (DomitoHexDigit(Hex->Buffer[i]) < 0)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < ByteCount; ++i)
|
||||
{
|
||||
Bytes[i] =
|
||||
static_cast<UCHAR>((DomitoHexDigit(Hex->Buffer[2 * i]) << 4) | DomitoHexDigit(Hex->Buffer[2 * i + 1]));
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoEncodeHex(
|
||||
const UCHAR* Bytes,
|
||||
ULONG ByteCount,
|
||||
WCHAR* Hex,
|
||||
ULONG HexChars
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (Hex && HexChars)
|
||||
{
|
||||
Hex[0] = 0;
|
||||
}
|
||||
|
||||
if ((!Bytes && ByteCount) || !Hex)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!HexChars || ByteCount > (HexChars - 1) / 2)
|
||||
{
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
static const WCHAR digits[] = L"0123456789ABCDEF";
|
||||
WCHAR* destination = Hex;
|
||||
|
||||
for (ULONG i = 0; i < ByteCount; ++i)
|
||||
{
|
||||
*destination++ = digits[Bytes[i] >> 4];
|
||||
*destination++ = digits[Bytes[i] & 15];
|
||||
}
|
||||
|
||||
*destination = 0;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
#include "Domito.Internal.h"
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
|
||||
static UINT32
|
||||
DomitoCiDigestSize(
|
||||
ALG_ID Algorithm
|
||||
)
|
||||
{
|
||||
switch (Algorithm)
|
||||
{
|
||||
case CALG_SHA1:
|
||||
return MINCRYPT_SHA1_LENGTH;
|
||||
case CALG_SHA_256:
|
||||
return MINCRYPT_SHA256_LENGTH;
|
||||
case CALG_SHA_384:
|
||||
return MINCRYPT_SHA384_LENGTH;
|
||||
case CALG_SHA_512:
|
||||
return MINCRYPT_SHA512_LENGTH;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
DomitoClearCiDigest(
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Result->Hash, sizeof(Result->Hash));
|
||||
Result->HashSize = 0;
|
||||
Result->HashAlgId = 0;
|
||||
Result->SigningTime.QuadPart = 0;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
void
|
||||
DomitoFreeCiValidationResult(
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CiFreePolicyInfo(&Result->PolicyInfo);
|
||||
CiFreePolicyInfo(&Result->TimeStampPolicyInfo);
|
||||
RtlZeroMemory(Result, sizeof(*Result));
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoValidateFileObjectEx(
|
||||
PFILE_OBJECT FileObject,
|
||||
UINT32 ValidationFlags,
|
||||
UINT8 RequestedSigningLevel,
|
||||
DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
|
||||
if (!FileObject)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Selection != DomitoCiSignatureSelectionNative &&
|
||||
Selection != DomitoCiSignatureSelectionFirstSuccessfulExperimental)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Selection == DomitoCiSignatureSelectionFirstSuccessfulExperimental)
|
||||
{
|
||||
#if defined(_M_AMD64) || defined(_AMD64_)
|
||||
ValidationFlags |= DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL;
|
||||
#else
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
Result->FileObjectValidationFlags = ValidationFlags;
|
||||
Result->RequestedSigningLevel = RequestedSigningLevel;
|
||||
Result->HashSize = sizeof(Result->Hash);
|
||||
NTSTATUS status = CiValidateFileObject(
|
||||
FileObject,
|
||||
ValidationFlags,
|
||||
RequestedSigningLevel,
|
||||
&Result->PolicyInfo,
|
||||
&Result->TimeStampPolicyInfo,
|
||||
&Result->SigningTime,
|
||||
Result->Hash,
|
||||
&Result->HashSize,
|
||||
&Result->HashAlgId
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
const UINT32 expected = DomitoCiDigestSize(Result->HashAlgId);
|
||||
|
||||
if (!expected)
|
||||
{
|
||||
status = STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
else if (Result->HashSize != expected || Result->HashSize > sizeof(Result->Hash))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DomitoClearCiDigest(Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
RtlZeroMemory(Result->Hash + Result->HashSize, sizeof(Result->Hash) - Result->HashSize);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(
|
||||
const UCHAR* PeBytes,
|
||||
ULONG PeSize,
|
||||
ALG_ID HashAlgId,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
|
||||
if (!PeBytes)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const UINT32 expected = DomitoCiDigestSize(HashAlgId);
|
||||
|
||||
if (!expected)
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
PVOID digest = nullptr;
|
||||
ULONG digestSize = 0;
|
||||
ULONG securitySize = 0;
|
||||
LPWIN_CERTIFICATE certificate = nullptr;
|
||||
NTSTATUS status = DomitoCalculatePortableExecutableDigestEx(
|
||||
PeBytes, PeSize, HashAlgId, &digestSize, &digest, &certificate, &securitySize
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (!certificate || !securitySize)
|
||||
{
|
||||
status = STATUS_INVALID_IMAGE_HASH;
|
||||
}
|
||||
else if (!digest || digestSize != expected || digestSize > sizeof(Result->Hash))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = CiCheckSignedFile(
|
||||
digest,
|
||||
digestSize,
|
||||
HashAlgId,
|
||||
certificate,
|
||||
securitySize,
|
||||
&Result->PolicyInfo,
|
||||
&Result->SigningTime,
|
||||
&Result->TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
RtlCopyMemory(Result->Hash, digest, digestSize);
|
||||
Result->HashSize = digestSize;
|
||||
Result->HashAlgId = HashAlgId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (digest)
|
||||
{
|
||||
DomitoFreePortableExecutableDigest(digest);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DomitoClearCiDigest(Result);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
+806
-256
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,395 @@
|
||||
#include "Domito.Internal.h"
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoFindModuleExportedFunctionAddress(
|
||||
PANSI_STRING ModuleName,
|
||||
PANSI_STRING FunctionName,
|
||||
PVOID* FunctionAddress
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!FunctionAddress)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*FunctionAddress = nullptr;
|
||||
|
||||
if (!ModuleName || !ModuleName->Buffer || !ModuleName->Length || ModuleName->Length > ModuleName->MaximumLength ||
|
||||
!FunctionName || !FunctionName->Buffer || !FunctionName->Length ||
|
||||
FunctionName->Length > FunctionName->MaximumLength)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
PVOID moduleBase = nullptr;
|
||||
NTSTATUS status = DomitoFindModuleBaseAddress(ModuleName, &moduleBase);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
return DomitoFindExportedFunctionAddress(moduleBase, FunctionName, FunctionAddress);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
void
|
||||
DomitoFreeProcessImageName(
|
||||
PUNICODE_STRING ImageName
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (ImageName)
|
||||
{
|
||||
G_Memory.Free(ImageName);
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoGetProcessImageNameEx(
|
||||
PEPROCESS Process,
|
||||
PUNICODE_STRING* ImageName
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!ImageName)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*ImageName = nullptr;
|
||||
|
||||
if (!Process)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
PUNICODE_STRING processImageName = nullptr;
|
||||
NTSTATUS status = SeLocateProcessImageName(Process, &processImageName);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (!processImageName || !processImageName->Buffer || !processImageName->Length ||
|
||||
(processImageName->Length & 1) || processImageName->Length > processImageName->MaximumLength ||
|
||||
processImageName->Length > MAXUSHORT - sizeof(WCHAR))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
const SIZE_T size = sizeof(UNICODE_STRING) + processImageName->Length + sizeof(WCHAR);
|
||||
PUNICODE_STRING imageName = static_cast<PUNICODE_STRING>(G_Memory.Allocate(size));
|
||||
|
||||
if (!imageName)
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
else
|
||||
{
|
||||
imageName->Buffer = reinterpret_cast<PWCH>(imageName + 1);
|
||||
imageName->Length = processImageName->Length;
|
||||
imageName->MaximumLength = processImageName->Length + sizeof(WCHAR);
|
||||
RtlCopyMemory(imageName->Buffer, processImageName->Buffer, processImageName->Length);
|
||||
imageName->Buffer[imageName->Length / sizeof(WCHAR)] = 0;
|
||||
*ImageName = imageName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (processImageName)
|
||||
{
|
||||
ExFreePool(processImageName);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoValidateFileHandleEx(
|
||||
HANDLE FileHandle,
|
||||
UINT32 ValidationFlags,
|
||||
UINT8 RequestedSigningLevel,
|
||||
DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
|
||||
if (!FileHandle)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
PFILE_OBJECT fileObject = nullptr;
|
||||
NTSTATUS status = ObReferenceObjectByHandle(
|
||||
FileHandle, FILE_READ_DATA, *IoFileObjectType, KernelMode, reinterpret_cast<PVOID*>(&fileObject), nullptr
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = DomitoValidateFileObjectEx(fileObject, ValidationFlags, RequestedSigningLevel, Selection, Result);
|
||||
ObDereferenceObject(fileObject);
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoValidateFilePathEx(
|
||||
const UNICODE_STRING* Path,
|
||||
UINT32 ValidationFlags,
|
||||
UINT8 RequestedSigningLevel,
|
||||
DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
|
||||
if (!Path || !Path->Buffer || Path->Length < sizeof(WCHAR) || (Path->Length & 1) ||
|
||||
Path->Length > Path->MaximumLength || Path->Buffer[0] != L'\\')
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
for (USHORT i = 0; i < Path->Length / sizeof(WCHAR); ++i)
|
||||
{
|
||||
if (!Path->Buffer[i])
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
OBJECT_ATTRIBUTES attributes;
|
||||
InitializeObjectAttributes(
|
||||
&attributes, const_cast<PUNICODE_STRING>(Path), OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr
|
||||
);
|
||||
HANDLE fileHandle = nullptr;
|
||||
IO_STATUS_BLOCK iosb = {};
|
||||
NTSTATUS status = ZwOpenFile(
|
||||
&fileHandle,
|
||||
FILE_READ_DATA | SYNCHRONIZE,
|
||||
&attributes,
|
||||
&iosb,
|
||||
FILE_SHARE_READ,
|
||||
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = DomitoValidateFileHandleEx(fileHandle, ValidationFlags, RequestedSigningLevel, Selection, Result);
|
||||
ZwClose(fileHandle);
|
||||
return status;
|
||||
}
|
||||
|
||||
#if defined(_M_AMD64) || defined(_AMD64_)
|
||||
static BOOLEAN
|
||||
DomitoCiOwnedSpan(
|
||||
const MINCRYPT_CHAIN_INFO* Chain,
|
||||
const void* Data,
|
||||
SIZE_T Size
|
||||
)
|
||||
{
|
||||
const ULONG_PTR chainBaseAddress = reinterpret_cast<ULONG_PTR>(Chain);
|
||||
const ULONG_PTR blobAddress = reinterpret_cast<ULONG_PTR>(Data);
|
||||
// Empty blobs may be NULL; they are never dereferenced by this accessor.
|
||||
if (!Size)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return blobAddress >= chainBaseAddress && blobAddress - chainBaseAddress >= 56 &&
|
||||
blobAddress - chainBaseAddress <= Chain->Size && Size <= Chain->Size - (blobAddress - chainBaseAddress);
|
||||
}
|
||||
#endif
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoGetCiChainView(
|
||||
const MINCRYPT_POLICY_INFO* Policy,
|
||||
DOMITO_CI_CHAIN_LAYOUT Layout,
|
||||
DOMITO_CI_CHAIN_VIEW* View
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!View)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*View = {};
|
||||
|
||||
if (!Policy || Layout != DomitoCiChainLayoutObservedX64)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
#if defined(_M_AMD64) || defined(_AMD64_)
|
||||
C_ASSERT(sizeof(MINCRYPT_CHAIN_ELEMENT) == 120);
|
||||
C_ASSERT(FIELD_OFFSET(MINCRYPT_CHAIN_INFO, ChainElements) == 40);
|
||||
C_ASSERT(FIELD_OFFSET(MINCRYPT_CHAIN_INFO, NumberOfChainElement) == 48);
|
||||
|
||||
if (Policy->Size != sizeof(*Policy))
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
const MINCRYPT_CHAIN_INFO* chain = Policy->ChainInfo;
|
||||
|
||||
if (!chain)
|
||||
{
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (chain->Size < 56)
|
||||
{
|
||||
return STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
const ULONG count = chain->NumberOfChainElement;
|
||||
|
||||
if (!count)
|
||||
{
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (count > (chain->Size - 56) / sizeof(MINCRYPT_CHAIN_ELEMENT) ||
|
||||
!DomitoCiOwnedSpan(chain, chain->ChainElements, static_cast<SIZE_T>(count) * sizeof(MINCRYPT_CHAIN_ELEMENT)))
|
||||
{
|
||||
return STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < count; ++i)
|
||||
{
|
||||
const MINCRYPT_CHAIN_ELEMENT& element = chain->ChainElements[i];
|
||||
|
||||
if (element.HashSize > sizeof(element.Hash) || !element.Certificate.Size ||
|
||||
!DomitoCiOwnedSpan(chain, element.Certificate.Data, element.Certificate.Size) ||
|
||||
!DomitoCiOwnedSpan(chain, element.Subject.Data, element.Subject.Size) ||
|
||||
!DomitoCiOwnedSpan(chain, element.Issuer.Data, element.Issuer.Size))
|
||||
{
|
||||
return STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG rootId;
|
||||
RtlCopyMemory(&rootId, reinterpret_cast<const UCHAR*>(chain) + 52, sizeof(rootId));
|
||||
*View = {chain->ChainElements, count, rootId};
|
||||
return STATUS_SUCCESS;
|
||||
#else
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
static MINCRYPT_ALLOCATE_ROUTINE DomitoAllocatePublisher;
|
||||
|
||||
static PVOID NTAPI
|
||||
DomitoAllocatePublisher(
|
||||
SIZE_T Size
|
||||
)
|
||||
{
|
||||
#pragma warning(suppress : 4996)
|
||||
return ExAllocatePoolWithTag(PagedPool, Size, DOMITO_POOL_TAG);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
void
|
||||
DomitoFreeCertificatePublisherName(
|
||||
PUNICODE_STRING PublisherName
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!PublisherName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (PublisherName->Buffer)
|
||||
{
|
||||
ExFreePoolWithTag(PublisherName->Buffer, DOMITO_POOL_TAG);
|
||||
}
|
||||
|
||||
*PublisherName = {};
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoGetCertificatePublisherName(
|
||||
const MINCERT_BLOB* Certificate,
|
||||
PUNICODE_STRING PublisherName
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!PublisherName)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*PublisherName = {};
|
||||
|
||||
if (!Certificate || !Certificate->Data || !Certificate->Size)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
ANSI_STRING moduleName = RTL_CONSTANT_STRING("\\SystemRoot\\system32\\CI.dll");
|
||||
ANSI_STRING exportName = RTL_CONSTANT_STRING("CiGetCertPublisherName");
|
||||
PVOID publisherRoutine = nullptr;
|
||||
NTSTATUS status = DomitoFindModuleExportedFunctionAddress(&moduleName, &exportName, &publisherRoutine);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!publisherRoutine)
|
||||
{
|
||||
return STATUS_PROCEDURE_NOT_FOUND;
|
||||
}
|
||||
|
||||
typedef NTSTATUS(NTAPI * GET_PUBLISHER)(MINCERT_BLOB*, PMINCRYPT_ALLOCATE_ROUTINE, PUNICODE_STRING);
|
||||
const GET_PUBLISHER getPublisher = reinterpret_cast<GET_PUBLISHER>(publisherRoutine);
|
||||
status = getPublisher(const_cast<MINCERT_BLOB*>(Certificate), DomitoAllocatePublisher, PublisherName);
|
||||
|
||||
if (NT_SUCCESS(status) && (PublisherName->Length > PublisherName->MaximumLength || (PublisherName->Length & 1) ||
|
||||
!PublisherName->Length || !PublisherName->Buffer))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DomitoFreeCertificatePublisherName(PublisherName);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
+293
-145
@@ -7,18 +7,26 @@
|
||||
|
||||
#include "Domito.Internal.h"
|
||||
|
||||
|
||||
DOMITO_COMMON G_Common = {};
|
||||
|
||||
DECLARE_GLOBAL_CONST_UNICODE_STRING(G_QipRoutineName, L"ZwQueryInformationProcess");
|
||||
DECLARE_GLOBAL_CONST_UNICODE_STRING(G_IdetdRoutineName, L"RtlImageDirectoryEntryToData");
|
||||
DECLARE_GLOBAL_CONST_UNICODE_STRING(G_QsiRoutineName, L"ZwQuerySystemInformation");
|
||||
DECLARE_GLOBAL_CONST_UNICODE_STRING(
|
||||
G_QipRoutineName,
|
||||
L"ZwQueryInformationProcess"
|
||||
);
|
||||
DECLARE_GLOBAL_CONST_UNICODE_STRING(
|
||||
G_IdetdRoutineName,
|
||||
L"RtlImageDirectoryEntryToData"
|
||||
);
|
||||
DECLARE_GLOBAL_CONST_UNICODE_STRING(
|
||||
G_QsiRoutineName,
|
||||
L"ZwQuerySystemInformation"
|
||||
);
|
||||
|
||||
#ifndef LOG
|
||||
#define LOG(Format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Domito][" __FUNCTION__ "] " Format " \n", __VA_ARGS__)
|
||||
#define LOG(Format, ...) \
|
||||
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Domito][" __FUNCTION__ "] " Format " \n", __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
@@ -43,10 +51,8 @@ _IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
void
|
||||
DomitoShutdown()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
@@ -56,6 +62,16 @@ DomitoFindModuleBaseAddress(
|
||||
_Inout_opt_ PVOID* ModuleBase
|
||||
)
|
||||
{
|
||||
if (ModuleBase)
|
||||
{
|
||||
*ModuleBase = nullptr;
|
||||
}
|
||||
|
||||
if (!ModuleName || !ModuleName->Buffer || !ModuleName->Length || ModuleName->Length > ModuleName->MaximumLength)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
ULONG bufferSize = 0;
|
||||
PSYSTEM_MODULE_INFORMATION moduleInfo = NULL;
|
||||
|
||||
@@ -67,12 +83,7 @@ DomitoFindModuleBaseAddress(
|
||||
const ULONG SystemModuleInformation = 11;
|
||||
|
||||
// Query the required buffer size for module information
|
||||
NTSTATUS status = G_Common.ZwQuerySystemInformation(
|
||||
SystemModuleInformation,
|
||||
&bufferSize,
|
||||
0,
|
||||
&bufferSize
|
||||
);
|
||||
NTSTATUS status = G_Common.ZwQuerySystemInformation(SystemModuleInformation, &bufferSize, 0, &bufferSize);
|
||||
|
||||
if (status != STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
@@ -80,9 +91,7 @@ DomitoFindModuleBaseAddress(
|
||||
}
|
||||
|
||||
// Allocate memory for the module information
|
||||
moduleInfo = (PSYSTEM_MODULE_INFORMATION)G_Memory.Allocate(
|
||||
bufferSize
|
||||
);
|
||||
moduleInfo = (PSYSTEM_MODULE_INFORMATION)G_Memory.Allocate(bufferSize);
|
||||
|
||||
if (moduleInfo == NULL)
|
||||
{
|
||||
@@ -90,12 +99,7 @@ DomitoFindModuleBaseAddress(
|
||||
}
|
||||
|
||||
// Retrieve the module information
|
||||
status = G_Common.ZwQuerySystemInformation(
|
||||
SystemModuleInformation,
|
||||
moduleInfo,
|
||||
bufferSize,
|
||||
NULL
|
||||
);
|
||||
status = G_Common.ZwQuerySystemInformation(SystemModuleInformation, moduleInfo, bufferSize, NULL);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
@@ -120,6 +124,7 @@ DomitoFindModuleBaseAddress(
|
||||
{
|
||||
*ModuleBase = moduleInfo->Module[i].Base;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -129,6 +134,19 @@ DomitoFindModuleBaseAddress(
|
||||
return status;
|
||||
}
|
||||
|
||||
// This helper handles directory RVAs after the loader-mapped header establishes
|
||||
// SizeOfImage. ModuleBase is not an arbitrary file buffer (there is no size input).
|
||||
static BOOLEAN
|
||||
DomitoImageRvaSpan(
|
||||
ULONG ImageSize,
|
||||
ULONG Rva,
|
||||
ULONG Count,
|
||||
ULONG ElementSize
|
||||
)
|
||||
{
|
||||
return Rva <= ImageSize && ElementSize != 0 && Count <= (ImageSize - Rva) / ElementSize;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
@@ -139,56 +157,143 @@ DomitoFindExportedFunctionAddress(
|
||||
_Inout_opt_ PVOID* FunctionAddress
|
||||
)
|
||||
{
|
||||
NTSTATUS status = STATUS_NOT_FOUND;
|
||||
ULONG exportSize;
|
||||
if (FunctionAddress)
|
||||
{
|
||||
*FunctionAddress = nullptr;
|
||||
}
|
||||
|
||||
if (G_Common.RtlImageDirectoryEntryToData == NULL)
|
||||
if (!ModuleBase || !FunctionName || !FunctionName->Buffer || !FunctionName->Length)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!G_Common.RtlImageDirectoryEntryToData)
|
||||
{
|
||||
return STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// Retrieve the export directory information
|
||||
const PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)G_Common.RtlImageDirectoryEntryToData(
|
||||
ModuleBase,
|
||||
TRUE,
|
||||
IMAGE_DIRECTORY_ENTRY_EXPORT,
|
||||
&exportSize
|
||||
);
|
||||
// Initial headers belong to a valid loaded module, as required by this API.
|
||||
const PUCHAR base = (PUCHAR)ModuleBase;
|
||||
IMAGE_DOS_HEADER dos;
|
||||
memcpy(&dos, base, sizeof(dos));
|
||||
|
||||
if (exportDirectory == NULL)
|
||||
if (dos.e_magic != IMAGE_DOS_SIGNATURE || dos.e_lfanew < (LONG)sizeof(dos))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
STRING currentFunctionName;
|
||||
const PUCHAR nt = base + dos.e_lfanew;
|
||||
ULONG signature;
|
||||
memcpy(&signature, nt, sizeof(signature));
|
||||
USHORT magic;
|
||||
memcpy(&magic, nt + sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER), sizeof(magic));
|
||||
|
||||
const PULONG functionAddresses = (PULONG)((ULONG_PTR)ModuleBase + exportDirectory->AddressOfFunctions);
|
||||
const PULONG functionNames = (PULONG)((ULONG_PTR)ModuleBase + exportDirectory->AddressOfNames);
|
||||
const PUSHORT functionOrdinals = (PUSHORT)((ULONG_PTR)ModuleBase + exportDirectory->AddressOfNameOrdinals);
|
||||
|
||||
for (ULONG i = 0; i < exportDirectory->NumberOfNames; i++)
|
||||
if (signature != IMAGE_NT_SIGNATURE ||
|
||||
(magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC && magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC))
|
||||
{
|
||||
const char* functionName = (const char*)((ULONG_PTR)ModuleBase + functionNames[i]);
|
||||
const USHORT functionOrdinal = functionOrdinals[i];
|
||||
UNREFERENCED_PARAMETER(functionOrdinal);
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
const ULONG functionRva = functionAddresses[i];
|
||||
const PVOID functionAddress = (PVOID)((ULONG_PTR)ModuleBase + functionRva);
|
||||
// SizeOfImage is at the same offset in PE32 and PE32+ optional headers.
|
||||
ULONG imageSize;
|
||||
memcpy(
|
||||
&imageSize,
|
||||
nt + sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + FIELD_OFFSET(IMAGE_OPTIONAL_HEADER32, SizeOfImage),
|
||||
sizeof(imageSize)
|
||||
);
|
||||
|
||||
RtlInitAnsiString(¤tFunctionName, functionName);
|
||||
|
||||
if (0 == RtlCompareString(FunctionName, ¤tFunctionName, TRUE))
|
||||
if (!DomitoImageRvaSpan(
|
||||
imageSize,
|
||||
(ULONG)dos.e_lfanew,
|
||||
sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + FIELD_OFFSET(IMAGE_OPTIONAL_HEADER32, SizeOfImage) +
|
||||
sizeof(imageSize),
|
||||
1
|
||||
))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
ULONG exportSize = 0;
|
||||
const PVOID directory =
|
||||
G_Common.RtlImageDirectoryEntryToData(ModuleBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &exportSize);
|
||||
const ULONG_PTR directoryOffset = (ULONG_PTR)directory - (ULONG_PTR)ModuleBase;
|
||||
|
||||
if (!directory || directoryOffset > imageSize || exportSize < sizeof(IMAGE_EXPORT_DIRECTORY) ||
|
||||
exportSize > imageSize - directoryOffset)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
IMAGE_EXPORT_DIRECTORY exports;
|
||||
memcpy(&exports, directory, sizeof(exports));
|
||||
|
||||
if (!DomitoImageRvaSpan(imageSize, exports.AddressOfFunctions, exports.NumberOfFunctions, sizeof(ULONG)) ||
|
||||
!DomitoImageRvaSpan(imageSize, exports.AddressOfNames, exports.NumberOfNames, sizeof(ULONG)) ||
|
||||
!DomitoImageRvaSpan(imageSize, exports.AddressOfNameOrdinals, exports.NumberOfNames, sizeof(USHORT)))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < exports.NumberOfNames; ++i)
|
||||
{
|
||||
ULONG nameRva;
|
||||
USHORT ordinal;
|
||||
memcpy(&nameRva, base + exports.AddressOfNames + i * sizeof(ULONG), sizeof(nameRva));
|
||||
memcpy(&ordinal, base + exports.AddressOfNameOrdinals + i * sizeof(USHORT), sizeof(ordinal));
|
||||
|
||||
if (nameRva >= imageSize || ordinal >= exports.NumberOfFunctions)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
ULONG length = 0;
|
||||
while (length < imageSize - nameRva && base[nameRva + length] != 0)
|
||||
{
|
||||
++length;
|
||||
}
|
||||
|
||||
if (length == imageSize - nameRva || length > MAXUSHORT)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
ANSI_STRING current;
|
||||
current.Buffer = (PCHAR)(base + nameRva);
|
||||
current.Length = (USHORT)length;
|
||||
current.MaximumLength = (USHORT)length;
|
||||
|
||||
if (RtlCompareString(FunctionName, ¤t, TRUE) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ULONG functionRva;
|
||||
memcpy(&functionRva, base + exports.AddressOfFunctions + ordinal * sizeof(ULONG), sizeof(functionRva));
|
||||
|
||||
if (!functionRva)
|
||||
{
|
||||
return STATUS_NOT_FOUND; // Empty EAT slot.
|
||||
}
|
||||
|
||||
if (functionRva >= imageSize)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
if (functionRva >= directoryOffset && functionRva - directoryOffset < exportSize)
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED; // Forwarder string, not a callable address.
|
||||
}
|
||||
|
||||
if (FunctionAddress)
|
||||
{
|
||||
status = STATUS_SUCCESS;
|
||||
*FunctionAddress = functionAddress;
|
||||
}
|
||||
break;
|
||||
}
|
||||
*FunctionAddress = base + functionRva;
|
||||
}
|
||||
|
||||
return status;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
@@ -221,6 +326,7 @@ DomitoMemorySearchPattern(
|
||||
for (SIZE_T i = 0; i + puLen <= puSize; i++)
|
||||
{
|
||||
BOOLEAN found = TRUE;
|
||||
|
||||
for (SIZE_T j = 0; j < puLen; j++)
|
||||
{
|
||||
if (pcPattern[j] != uWildcard && pcPattern[j] != ((PCUCHAR)pcBase)[i + j])
|
||||
@@ -240,6 +346,74 @@ DomitoMemorySearchPattern(
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
DomitoReadFileAt(
|
||||
_In_ HANDLE FileHandle,
|
||||
_Out_writes_bytes_(BufferSize) PVOID Buffer,
|
||||
_In_ ULONG BufferSize,
|
||||
_In_ const LARGE_INTEGER* ByteOffset
|
||||
)
|
||||
{
|
||||
if (!FileHandle || !ByteOffset || ByteOffset->QuadPart < 0 || (!Buffer && BufferSize))
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!BufferSize)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
HANDLE eventHandle = nullptr;
|
||||
OBJECT_ATTRIBUTES attributes;
|
||||
InitializeObjectAttributes(&attributes, nullptr, OBJ_KERNEL_HANDLE, nullptr, nullptr);
|
||||
NTSTATUS status = ZwCreateEvent(&eventHandle, EVENT_ALL_ACCESS, &attributes, NotificationEvent, FALSE);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
PVOID eventObject = nullptr;
|
||||
status = ObReferenceObjectByHandle(eventHandle, SYNCHRONIZE, *ExEventObjectType, KernelMode, &eventObject, nullptr);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
ZwClose(eventHandle);
|
||||
return status;
|
||||
}
|
||||
|
||||
IO_STATUS_BLOCK ioStatusBlock = {};
|
||||
LARGE_INTEGER offset = *ByteOffset;
|
||||
status =
|
||||
ZwReadFile(FileHandle, eventHandle, nullptr, nullptr, &ioStatusBlock, Buffer, BufferSize, &offset, nullptr);
|
||||
|
||||
if (status == STATUS_PENDING)
|
||||
{
|
||||
// A private event identifies this request even if the file has other I/O.
|
||||
// KernelMode + nonalertable + no timeout cannot return before completion.
|
||||
KeWaitForSingleObject(eventObject, Executive, KernelMode, FALSE, nullptr);
|
||||
status = ioStatusBlock.Status;
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
status = ioStatusBlock.Status;
|
||||
|
||||
if (NT_SUCCESS(status) && ioStatusBlock.Information != BufferSize)
|
||||
{
|
||||
status = STATUS_END_OF_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
ObDereferenceObject(eventObject);
|
||||
ZwClose(eventHandle);
|
||||
return status;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
@@ -250,33 +424,8 @@ DomitoReadFile(
|
||||
_In_ ULONG BufferSize
|
||||
)
|
||||
{
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
IO_STATUS_BLOCK ioStatusBlock;
|
||||
|
||||
// Read the file into memory using ZwReadFile
|
||||
if (!NT_SUCCESS(status = ZwReadFile(
|
||||
FileHandle,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&ioStatusBlock,
|
||||
Buffer,
|
||||
BufferSize,
|
||||
NULL,
|
||||
NULL
|
||||
)))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
// Check if the file was read successfully
|
||||
if (!NT_SUCCESS(ioStatusBlock.Status))
|
||||
{
|
||||
return ioStatusBlock.Status;
|
||||
}
|
||||
|
||||
// File read successfully
|
||||
return status;
|
||||
const LARGE_INTEGER offset = {};
|
||||
return DomitoReadFileAt(FileHandle, Buffer, BufferSize, &offset);
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
@@ -286,95 +435,94 @@ _IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
DomitoGetProcessImageName(
|
||||
_In_ ULONG ProcessId,
|
||||
_Inout_ PUNICODE_STRING * ProcessImageName
|
||||
_Outptr_result_maybenull_ PUNICODE_STRING* ProcessImageName
|
||||
)
|
||||
{
|
||||
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
||||
ULONG returnedLength;
|
||||
HANDLE hProcess = NULL;
|
||||
PAGED_CODE();
|
||||
|
||||
PAGED_CODE(); // this eliminates the possibility of the IDLE Thread/Process
|
||||
|
||||
if (ProcessId == 0 || ProcessId == 4)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER_1;
|
||||
}
|
||||
|
||||
if (ProcessImageName == NULL)
|
||||
if (!ProcessImageName)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER_2;
|
||||
}
|
||||
|
||||
CLIENT_ID cid;
|
||||
cid.UniqueProcess = (HANDLE)ProcessId;
|
||||
cid.UniqueThread = NULL;
|
||||
*ProcessImageName = nullptr;
|
||||
|
||||
OBJECT_ATTRIBUTES objAttr;
|
||||
InitializeObjectAttributes(
|
||||
&objAttr,
|
||||
NULL,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
if (!ProcessId || ProcessId == 4)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER_1;
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status = ZwOpenProcess(
|
||||
&hProcess,
|
||||
PROCESS_ALL_ACCESS,
|
||||
&objAttr,
|
||||
&cid
|
||||
)))
|
||||
if (!G_Common.ZwQueryInformationProcess)
|
||||
{
|
||||
return STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
CLIENT_ID cid = {};
|
||||
cid.UniqueProcess = ULongToHandle(ProcessId);
|
||||
OBJECT_ATTRIBUTES attributes;
|
||||
InitializeObjectAttributes(&attributes, nullptr, OBJ_KERNEL_HANDLE, nullptr, nullptr);
|
||||
HANDLE process = nullptr;
|
||||
// PROCESS_QUERY_LIMITED_INFORMATION (WinNT.h); not named by the WDK headers.
|
||||
const ACCESS_MASK queryLimitedInformation = 0x1000;
|
||||
NTSTATUS status = ZwOpenProcess(&process, queryLimitedInformation, &attributes, &cid);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (G_Common.ZwQueryInformationProcess == NULL)
|
||||
ULONG size = 0;
|
||||
status = G_Common.ZwQueryInformationProcess(process, ProcessImageFileName, nullptr, 0, &size);
|
||||
|
||||
if (status == STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
status = STATUS_NOT_IMPLEMENTED;
|
||||
goto cleanUp;
|
||||
}
|
||||
|
||||
/* Query the actual size of the process path */
|
||||
status = G_Common.ZwQueryInformationProcess(
|
||||
hProcess,
|
||||
ProcessImageFileName,
|
||||
NULL, // buffer
|
||||
0, // buffer size
|
||||
&returnedLength
|
||||
);
|
||||
|
||||
if (STATUS_INFO_LENGTH_MISMATCH != status)
|
||||
if (size < sizeof(UNICODE_STRING))
|
||||
{
|
||||
goto cleanUp;
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto image = static_cast<PUNICODE_STRING>(G_Memory.Allocate(size));
|
||||
|
||||
*ProcessImageName = (PUNICODE_STRING)G_Memory.Allocate(returnedLength);
|
||||
|
||||
if (*ProcessImageName == NULL)
|
||||
if (!image)
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto cleanUp;
|
||||
}
|
||||
|
||||
/* Retrieve the process path from the handle to the process */
|
||||
if (!NT_SUCCESS(status = G_Common.ZwQueryInformationProcess(
|
||||
hProcess,
|
||||
ProcessImageFileName,
|
||||
*ProcessImageName,
|
||||
returnedLength,
|
||||
&returnedLength
|
||||
)))
|
||||
else
|
||||
{
|
||||
G_Memory.Free(*ProcessImageName);
|
||||
}
|
||||
ULONG returned = 0;
|
||||
status = G_Common.ZwQueryInformationProcess(process, ProcessImageFileName, image, size, &returned);
|
||||
|
||||
cleanUp:
|
||||
|
||||
if (hProcess)
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
ZwClose(hProcess);
|
||||
const ULONG_PTR base = reinterpret_cast<ULONG_PTR>(image);
|
||||
const ULONG_PTR buffer = reinterpret_cast<ULONG_PTR>(image->Buffer);
|
||||
|
||||
if (!image->Length || (image->Length & 1) || image->Length > image->MaximumLength ||
|
||||
buffer < base || buffer - base < sizeof(UNICODE_STRING) || buffer - base > size ||
|
||||
image->MaximumLength > size - (buffer - base))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
*ProcessImageName = image;
|
||||
}
|
||||
else
|
||||
{
|
||||
G_Memory.Free(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (NT_SUCCESS(status))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
ZwClose(process);
|
||||
return status;
|
||||
}
|
||||
#pragma code_seg()
|
||||
@@ -121,12 +121,18 @@
|
||||
<FilesToPackage Include="$(TargetPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Domito.Certificate.cpp" />
|
||||
<ClCompile Include="Domito.Consumer.cpp" />
|
||||
<ClCompile Include="Domito.CodeIntegrity.cpp" />
|
||||
<ClCompile Include="Domito.CodeIntegrity.Validation.cpp" />
|
||||
<ClCompile Include="Domito.cpp" />
|
||||
<ClCompile Include="Domito.Memory.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.h" />
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.26100.h" />
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.Historical.h" />
|
||||
<ClInclude Include="..\include\Domito.Consumer.h" />
|
||||
<ClInclude Include="..\include\Domito.h" />
|
||||
<ClInclude Include="Domito.Internal.h" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -28,6 +28,15 @@
|
||||
<ClCompile Include="Domito.Memory.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Domito.CodeIntegrity.Validation.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Domito.Certificate.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Domito.Consumer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Domito.CodeIntegrity.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -36,9 +45,18 @@
|
||||
<ClInclude Include="Domito.Internal.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.Consumer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.h">
|
||||
<Filter>Header Files\Public</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.26100.h">
|
||||
<Filter>Header Files\Public</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.Historical.h">
|
||||
<Filter>Header Files\Public</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.h">
|
||||
<Filter>Header Files\Public</Filter>
|
||||
</ClInclude>
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Regression tests
|
||||
|
||||
From an **x64 Visual Studio developer shell**, with the Windows SDK and Python 3:
|
||||
|
||||
```powershell
|
||||
python tests\run-digest-tests.py
|
||||
python tests\run-common-tests.py
|
||||
python tests\run-ci-probe-tests.py
|
||||
python tests\run-ci-validation-tests.py
|
||||
```
|
||||
|
||||
Build outputs are temporary. No driver is installed or loaded. Test runners
|
||||
extract the actual production functions or include the probe body unchanged;
|
||||
kernel calls, CI calls, and pool allocation are replaced with test doubles.
|
||||
The digest harness uses real BCrypt, Crypt32 and the Windows PE SIP provider.
|
||||
|
||||
## Digest and legacy verification tests
|
||||
|
||||
Coverage includes PE32/PE32+ files, all four supported hash algorithms, checksum
|
||||
and certificate exclusions, overlays, reverse section-header order, multiple
|
||||
certificate entries, certificate padding, unsupported section layouts, bounded
|
||||
DER algorithm discovery, SHA-1 pattern decoys, truncated ASN.1, explicit catalog
|
||||
policy forwarding and cleanup between failed/successful CI attempts.
|
||||
|
||||
The test compares digests against `CryptSIPCreateIndirectData` and reads the
|
||||
installed system `ci.dll`, `ntdll.dll`, `kernel32.dll`, and `notepad.exe` as a
|
||||
real-file corpus. For embedded signatures, Crypt32 independently decodes the SPC
|
||||
algorithm and expected file digest. These checks do not verify signature trust.
|
||||
|
||||
Guard pages cover truncated headers and invalid ranges in both compatibility
|
||||
and strict paths. Fault injection covers allocation/hash failures, deterministic
|
||||
outputs, short certificate reads and digest ownership. A DOS stub larger than
|
||||
4 MiB exercises the removed scratch-buffer limit. Older entry points retain
|
||||
legacy selection/layout behavior; strict Ex APIs enforce their documented subset.
|
||||
|
||||
## File and export helper tests
|
||||
|
||||
The common harness checks explicit and zero offsets, exact/short/failed reads,
|
||||
pending completion, event/object cleanup, and error propagation. Export fixtures
|
||||
cover nonidentity name-ordinal mappings, forwarders, empty slots, invalid spans,
|
||||
unbounded strings, and deterministic failure outputs. The export API requires a
|
||||
valid loader-mapped module; it is not an arbitrary file parser.
|
||||
|
||||
## CI validation result and selection helpers
|
||||
|
||||
`run-ci-validation-tests.py` compiles the production wrapper source unchanged
|
||||
(apart from replacing its private include with test dependencies) and extracts
|
||||
the public declarations/constants. It checks native flag/level forwarding,
|
||||
64-byte capacity on every call, the opt-in x64 experimental bit, non-x64 mode
|
||||
rejection, invalid inputs, returned hash invariants, deterministic failure
|
||||
scalars, preserved diagnostic policies, and result cleanup/reuse. The embedded
|
||||
path covers all four explicit algorithms, unsupported/unsigned/invalid layouts,
|
||||
partial digest allocation cleanup, CI failure propagation and no catalog retry.
|
||||
CI/hash-layout behavior is injected; real digest behavior is checked by the
|
||||
existing Windows SIP/Crypt32 differential harness. A second host build exercises
|
||||
the non-x64 conditional branch; real ARM64 compilation is a separate build check.
|
||||
|
||||
The first kernel trace used direct CI calls. Probe v2 in CodeIntegrity-RE now
|
||||
consumes these library helpers; a returned v2 trace is needed to claim runtime
|
||||
verification of the wrappers themselves. The earlier native result is not
|
||||
silently relabelled as an API-wrapper test.
|
||||
|
||||
## File-object validation flag probe
|
||||
|
||||
See [the flag documentation and VM procedure](../docs/ci-validate-file-object.md)
|
||||
for `CiValidateFileObject` flag `0x20`. The host runner tests probe plumbing with
|
||||
mock CI. The kernel probe must still run in a suitable VM to verify actual
|
||||
policy behavior.
|
||||
|
||||
## Kernel compile checks and remaining validation
|
||||
|
||||
`mincrypt-layout-tests.cpp` asserts default modern x64/ARM64 and pre-Windows-10
|
||||
compile layouts. On x64 it also includes the opt-in observed 26100 header's 14
|
||||
layout assertions. Compile it with the same WDK/kernel architecture flags and
|
||||
include paths as the library. This checks structure definitions, not runtime
|
||||
compatibility on those systems. `ci-file-object-probe.cpp` is separately compiled
|
||||
as kernel code for x64/ARM64; its real CI behavior is still untested.
|
||||
|
||||
The host tests do not establish CI trust policy, real asynchronous kernel I/O,
|
||||
real policy ownership under Driver Verifier, revocation/timestamp behavior, or
|
||||
all Windows-version/layout combinations. See the
|
||||
[implementation ledger](../docs/implementation-status.md) for the remaining work.
|
||||
|
||||
## Consumer helper regression tests
|
||||
|
||||
Run `python tests/run-consumer-helper-tests.py` in an x64 Visual Studio developer
|
||||
shell. This fifth host runner compiles the actual certificate/common-consumer
|
||||
implementations and the existing PID name function, with kernel operations
|
||||
replaced by deterministic doubles. Hashing uses real Windows CNG; independent
|
||||
Crypt32 decoding/re-encoding/hash calls verify SPKI representation and pins.
|
||||
The synthetic v1/v3 certificates are framing fixtures, not trust fixtures.
|
||||
|
||||
Coverage includes truncated certificate prefixes at inaccessible guard pages,
|
||||
malformed DER lengths and optional read-only comparisons with real system-root
|
||||
certificate SPKIs,
|
||||
four hash algorithms, short capacities and four injected CNG failures, strict
|
||||
hex conversion across all byte values, path/handle ownership, failed references,
|
||||
partial publisher allocation, process-name query failures, stale-output cleanup,
|
||||
and malformed/overflowing x64 chain spans. A second host build exercises the
|
||||
non-x64 rejection branch without claiming ARM64 ABI or runtime emulation.
|
||||
No service is started, certificate trusted, or real CI policy evaluated.
|
||||
|
||||
## Deprecation compiler checks
|
||||
|
||||
Run `python tests/run-deprecation-tests.py --arch x64` or `--arch ARM64` from a
|
||||
Visual Studio developer shell with the WDK installed. This compile-only runner
|
||||
uses the actual public headers: modern API references must compile under /WX,
|
||||
while each of four deprecated references must fail specifically with C4996 and
|
||||
its migration message. Both C and C++ are checked. It does not link or load code.
|
||||
@@ -0,0 +1,175 @@
|
||||
// Host tests validate the probe plumbing, NOT CI trust behavior.
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
#include <ntstatus.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include "ci-probe-constants.inc"
|
||||
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
||||
#define DOMITO_CI_PROBE_HOST_TEST
|
||||
typedef void* PFILE_OBJECT;
|
||||
|
||||
struct MINCRYPT_POLICY_INFO
|
||||
{
|
||||
ULONG Size;
|
||||
ULONG VerificationStatus;
|
||||
ULONG PolicyBits;
|
||||
void* ChainInfo;
|
||||
};
|
||||
|
||||
static unsigned calls;
|
||||
static unsigned frees;
|
||||
static unsigned outstanding;
|
||||
static unsigned checks;
|
||||
static bool failureMode;
|
||||
static int fileToken;
|
||||
static int chainToken;
|
||||
|
||||
static void
|
||||
Check(
|
||||
bool value,
|
||||
const char* label
|
||||
)
|
||||
{
|
||||
++checks;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
std::fprintf(stderr, "FAIL: %s\n", label);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
CiValidateFileObject(
|
||||
PFILE_OBJECT file,
|
||||
ULONG flags,
|
||||
ULONG level,
|
||||
MINCRYPT_POLICY_INFO* policy,
|
||||
MINCRYPT_POLICY_INFO* timestamp,
|
||||
LARGE_INTEGER* time,
|
||||
UCHAR* hash,
|
||||
UINT32* size,
|
||||
ALG_ID* algorithm
|
||||
)
|
||||
{
|
||||
const ULONG expectedFlags[] = {0, 0x20, 0x20, 0, 0, 0x20};
|
||||
Check(calls < 6 && flags == expectedFlags[calls], "paired flags and order");
|
||||
Check(file == &fileToken && level == 0, "same file and level zero");
|
||||
Check(outstanding == 0, "previous outputs freed before next call");
|
||||
Check(policy->Size == sizeof(*policy) && timestamp->Size == sizeof(*timestamp), "policy sizes");
|
||||
Check(
|
||||
!policy->ChainInfo && !timestamp->ChainInfo && !policy->PolicyBits && !timestamp->PolicyBits,
|
||||
"fresh policy objects"
|
||||
);
|
||||
Check(*size == (calls < 4 ? 64u : 63u), "capacity reset for every call");
|
||||
Check(*algorithm == 0 && time->QuadPart == 0, "fresh scalar outputs");
|
||||
|
||||
for (unsigned i = 0; i < 64; ++i)
|
||||
{
|
||||
Check(hash[i] == 0, "fresh hash buffer");
|
||||
}
|
||||
|
||||
++calls;
|
||||
|
||||
if (*size < 64)
|
||||
{
|
||||
*size = 64;
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
policy->ChainInfo = timestamp->ChainInfo = &chainToken;
|
||||
outstanding = 2;
|
||||
policy->PolicyBits = 0xA5;
|
||||
timestamp->PolicyBits = 0x5A;
|
||||
policy->VerificationStatus = failureMode ? STATUS_INVALID_IMAGE_HASH : STATUS_SUCCESS;
|
||||
timestamp->VerificationStatus = STATUS_SUCCESS;
|
||||
// Also allocate on simulated failure to exercise unconditional cleanup.
|
||||
if (failureMode)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_HASH;
|
||||
}
|
||||
|
||||
std::memset(hash, 0xCC, 32);
|
||||
*size = 32;
|
||||
*algorithm = 0x800C;
|
||||
time->QuadPart = 123;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
CiFreePolicyInfo(
|
||||
MINCRYPT_POLICY_INFO* policy
|
||||
)
|
||||
{
|
||||
++frees;
|
||||
|
||||
if (policy->ChainInfo)
|
||||
{
|
||||
Check(outstanding > 0, "owned chain");
|
||||
--outstanding;
|
||||
}
|
||||
|
||||
*policy = {};
|
||||
}
|
||||
|
||||
#include "ci-file-object-probe.cpp"
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
for (unsigned pass = 0; pass < 2; ++pass)
|
||||
{
|
||||
failureMode = pass != 0;
|
||||
calls = frees = outstanding = 0;
|
||||
DOMITO_CI_PROBE_RESULT results[DOMITO_CI_PROBE_RESULT_COUNT];
|
||||
std::memset(results, 0xFF, sizeof(results));
|
||||
DomitoProbeCiValidateFileObject(&fileToken, results);
|
||||
Check(calls == 6 && frees == 12 && outstanding == 0, "all calls and frees");
|
||||
|
||||
for (unsigned i = 0; i < 6; ++i)
|
||||
{
|
||||
const auto& r = results[i];
|
||||
Check(r.HashGuardIntact, "buffer guard");
|
||||
const NTSTATUS expected = i >= 4 ? STATUS_BUFFER_TOO_SMALL
|
||||
: failureMode ? STATUS_INVALID_IMAGE_HASH
|
||||
: STATUS_SUCCESS;
|
||||
Check(r.Status == expected, "status preserved");
|
||||
|
||||
if (i < 4)
|
||||
{
|
||||
Check(r.PolicyBits == 0xA5 && r.TimeStampPolicyBits == 0x5A, "policy capture");
|
||||
Check(r.HasChain && r.HasTimeStampChain, "chain presence captured before free");
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(r.Status))
|
||||
{
|
||||
Check(
|
||||
r.HashSize == 32 && r.HashAlgId == 0x800C && r.SigningTime.QuadPart == 123,
|
||||
"digest metadata and signing time"
|
||||
);
|
||||
|
||||
for (unsigned j = 0; j < 64; ++j)
|
||||
{
|
||||
Check(r.Hash[j] == (j < 32 ? 0xCC : 0), "digest copied and tail cleared");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto byte : r.Hash)
|
||||
{
|
||||
Check(byte == 0, "no digest from failed call");
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= 4)
|
||||
{
|
||||
Check(r.HashSize == 64, "required capacity captured");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::printf("PASS: %u probe checks (mock CI; no runtime trust validation)\n", checks);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Compile into an existing signed test driver; this file has no DriverEntry.
|
||||
#ifndef DOMITO_CI_PROBE_HOST_TEST
|
||||
#include <ntifs.h>
|
||||
#include "Domito.MinCrypt.h"
|
||||
#endif
|
||||
#include "ci-file-object-probe.h"
|
||||
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
void
|
||||
DomitoProbeCiValidateFileObject(
|
||||
PFILE_OBJECT FileObject,
|
||||
DOMITO_CI_PROBE_RESULT* Results
|
||||
)
|
||||
{
|
||||
const ULONG flags[DOMITO_CI_PROBE_RESULT_COUNT] = {
|
||||
0,
|
||||
DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE,
|
||||
DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE,
|
||||
0,
|
||||
0,
|
||||
DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE
|
||||
};
|
||||
|
||||
for (ULONG i = 0; i < DOMITO_CI_PROBE_RESULT_COUNT; ++i)
|
||||
{
|
||||
MINCRYPT_POLICY_INFO policy = {};
|
||||
MINCRYPT_POLICY_INFO timestamp = {};
|
||||
policy.Size = sizeof(policy);
|
||||
timestamp.Size = sizeof(timestamp);
|
||||
|
||||
struct
|
||||
{
|
||||
UCHAR Bytes[MINCRYPT_MAX_HASH_LENGTH];
|
||||
ULONG Guard;
|
||||
} hash = {};
|
||||
|
||||
hash.Guard = 0xD01170AB;
|
||||
DOMITO_CI_PROBE_RESULT& result = Results[i];
|
||||
result = {};
|
||||
result.Flags = flags[i];
|
||||
result.InputCapacity = i < 4 ? sizeof(hash.Bytes) : sizeof(hash.Bytes) - 1;
|
||||
result.HashSize = result.InputCapacity;
|
||||
result.Status = CiValidateFileObject(
|
||||
FileObject,
|
||||
result.Flags,
|
||||
0,
|
||||
&policy,
|
||||
×tamp,
|
||||
&result.SigningTime,
|
||||
hash.Bytes,
|
||||
&result.HashSize,
|
||||
&result.HashAlgId
|
||||
);
|
||||
result.HashGuardIntact = hash.Guard == 0xD01170AB;
|
||||
// Keep failure outputs as raw observations; only successful calls have
|
||||
// a usable digest. Do not dereference the incompletely mapped chains.
|
||||
result.PolicyBits = policy.PolicyBits;
|
||||
result.TimeStampPolicyBits = timestamp.PolicyBits;
|
||||
result.VerificationStatus = (NTSTATUS)policy.VerificationStatus;
|
||||
result.TimeStampVerificationStatus = (NTSTATUS)timestamp.VerificationStatus;
|
||||
result.HasChain = policy.ChainInfo != nullptr;
|
||||
result.HasTimeStampChain = timestamp.ChainInfo != nullptr;
|
||||
|
||||
if (NT_SUCCESS(result.Status) && result.HashSize <= sizeof(result.Hash))
|
||||
{
|
||||
RtlCopyMemory(result.Hash, hash.Bytes, result.HashSize);
|
||||
}
|
||||
|
||||
CiFreePolicyInfo(&policy);
|
||||
CiFreePolicyInfo(×tamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
// Include ntifs.h and Domito.MinCrypt.h first. Test support, not a library API.
|
||||
typedef struct _DOMITO_CI_PROBE_RESULT
|
||||
{
|
||||
ULONG Flags;
|
||||
ULONG InputCapacity;
|
||||
NTSTATUS Status;
|
||||
UINT32 HashSize;
|
||||
ALG_ID HashAlgId;
|
||||
UCHAR Hash[MINCRYPT_MAX_HASH_LENGTH];
|
||||
LARGE_INTEGER SigningTime;
|
||||
ULONG PolicyBits;
|
||||
ULONG TimeStampPolicyBits;
|
||||
NTSTATUS VerificationStatus;
|
||||
NTSTATUS TimeStampVerificationStatus;
|
||||
BOOLEAN HasChain;
|
||||
BOOLEAN HasTimeStampChain;
|
||||
BOOLEAN HashGuardIntact;
|
||||
} DOMITO_CI_PROBE_RESULT;
|
||||
|
||||
#define DOMITO_CI_PROBE_RESULT_COUNT 6
|
||||
|
||||
// PASSIVE_LEVEL; referenced file object; file bytes must remain stable.
|
||||
// Writes observations, not an overall trust verdict. Frees both policies per call.
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
void
|
||||
DomitoProbeCiValidateFileObject(
|
||||
_In_ PFILE_OBJECT FileObject,
|
||||
_Out_writes_(DOMITO_CI_PROBE_RESULT_COUNT) DOMITO_CI_PROBE_RESULT* Results
|
||||
);
|
||||
@@ -0,0 +1,519 @@
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
#include <ntstatus.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
|
||||
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
||||
#ifndef _IRQL_requires_max_
|
||||
#define _IRQL_requires_max_(x)
|
||||
#endif
|
||||
#define PASSIVE_LEVEL 0
|
||||
#define PAGED_CODE() ((void)0)
|
||||
typedef void* PFILE_OBJECT;
|
||||
typedef void* LPWIN_CERTIFICATE;
|
||||
|
||||
struct MINCRYPT_POLICY_INFO
|
||||
{
|
||||
ULONG Size;
|
||||
NTSTATUS VerificationStatus;
|
||||
ULONG PolicyBits;
|
||||
void* ChainInfo;
|
||||
};
|
||||
|
||||
#include "ci-validation-api.inc"
|
||||
|
||||
static unsigned checks;
|
||||
static unsigned live;
|
||||
static unsigned validationCalls;
|
||||
static unsigned embeddedCalls;
|
||||
static unsigned digestCalls;
|
||||
static unsigned digestFrees;
|
||||
static NTSTATUS nativeStatus = STATUS_SUCCESS;
|
||||
static NTSTATUS embeddedStatus = STATUS_SUCCESS;
|
||||
static NTSTATUS digestStatus = STATUS_SUCCESS;
|
||||
static UINT32 nativeAlgorithm = CALG_SHA_256;
|
||||
static UINT32 nativeSize = 32;
|
||||
static ULONG fakeDigestSize = 32;
|
||||
static bool omitCertificate;
|
||||
static bool omitDigest;
|
||||
static bool partialDigestOnFailure;
|
||||
static const UCHAR image[] = {1, 2, 3};
|
||||
static ULONG observedFlags;
|
||||
static ULONG observedLevel;
|
||||
static ULONG observedCapacity;
|
||||
static PFILE_OBJECT observedObject;
|
||||
static ALG_ID observedEmbeddedAlgorithm;
|
||||
static ULONG observedSecuritySize;
|
||||
static void* observedCertificate;
|
||||
|
||||
static void
|
||||
Check(
|
||||
bool value,
|
||||
const char* message
|
||||
)
|
||||
{
|
||||
++checks;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
std::fprintf(stderr, "FAIL: %s\n", message);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void*
|
||||
Allocate(
|
||||
SIZE_T size
|
||||
)
|
||||
{
|
||||
void* value = std::malloc(size);
|
||||
Check(value != nullptr, "host allocation");
|
||||
++live;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void
|
||||
Release(
|
||||
void* value
|
||||
)
|
||||
{
|
||||
Check(value && live, "single allocation owner");
|
||||
--live;
|
||||
std::free(value);
|
||||
}
|
||||
|
||||
static void
|
||||
CiFreePolicyInfo(
|
||||
MINCRYPT_POLICY_INFO* policy
|
||||
)
|
||||
{
|
||||
if (policy->ChainInfo)
|
||||
{
|
||||
Release(policy->ChainInfo);
|
||||
}
|
||||
|
||||
*policy = {};
|
||||
}
|
||||
|
||||
static void
|
||||
Populate(
|
||||
MINCRYPT_POLICY_INFO* policy,
|
||||
MINCRYPT_POLICY_INFO* timestamp
|
||||
)
|
||||
{
|
||||
Check(!policy->ChainInfo && !timestamp->ChainInfo, "old result freed before reuse");
|
||||
policy->Size = timestamp->Size = sizeof(*policy);
|
||||
policy->ChainInfo = Allocate(1);
|
||||
timestamp->ChainInfo = Allocate(1);
|
||||
policy->PolicyBits = 0x123;
|
||||
timestamp->PolicyBits = 0x456;
|
||||
policy->VerificationStatus = STATUS_INVALID_IMAGE_HASH;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
CiValidateFileObject(
|
||||
PFILE_OBJECT object,
|
||||
UINT32 flags,
|
||||
UINT32 level,
|
||||
MINCRYPT_POLICY_INFO* policy,
|
||||
MINCRYPT_POLICY_INFO* timestamp,
|
||||
LARGE_INTEGER* time,
|
||||
UINT8* hash,
|
||||
UINT32* size,
|
||||
ALG_ID* algorithm
|
||||
)
|
||||
{
|
||||
++validationCalls;
|
||||
observedObject = object;
|
||||
observedFlags = flags;
|
||||
observedLevel = level;
|
||||
observedCapacity = *size;
|
||||
Check(*size == 64, "fresh native hash capacity");
|
||||
Check(!time->QuadPart && !*algorithm, "native scalar outputs initialized");
|
||||
|
||||
for (unsigned i = 0; i < 64; ++i)
|
||||
{
|
||||
Check(!hash[i], "native digest starts empty");
|
||||
}
|
||||
|
||||
Populate(policy, timestamp);
|
||||
time->QuadPart = 99;
|
||||
std::memset(hash, 0x5a, 64);
|
||||
*size = nativeSize;
|
||||
*algorithm = nativeAlgorithm;
|
||||
return nativeStatus;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
DomitoCalculatePortableExecutableDigestEx(
|
||||
const UCHAR* bytes,
|
||||
ULONG size,
|
||||
UINT32 algorithm,
|
||||
PULONG digestSize,
|
||||
PVOID* digest,
|
||||
LPWIN_CERTIFICATE* certificate,
|
||||
PULONG securitySize
|
||||
)
|
||||
{
|
||||
++digestCalls;
|
||||
Check(bytes == image && size == sizeof(image), "same PE snapshot forwarded");
|
||||
Check(
|
||||
algorithm == CALG_SHA1 || algorithm == CALG_SHA_256 || algorithm == CALG_SHA_384 || algorithm == CALG_SHA_512,
|
||||
"supported digest choice forwarded"
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(digestStatus) || partialDigestOnFailure)
|
||||
{
|
||||
*digest = omitDigest ? nullptr : Allocate(64);
|
||||
|
||||
if (*digest)
|
||||
{
|
||||
std::memset(*digest, 0xa5, 64);
|
||||
}
|
||||
|
||||
*digestSize = fakeDigestSize;
|
||||
*certificate = omitCertificate ? nullptr : const_cast<UCHAR*>(image);
|
||||
*securitySize = omitCertificate ? 0 : 123;
|
||||
}
|
||||
|
||||
return digestStatus;
|
||||
}
|
||||
|
||||
static void
|
||||
DomitoFreePortableExecutableDigest(
|
||||
PVOID digest
|
||||
)
|
||||
{
|
||||
++digestFrees;
|
||||
Release(digest);
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
CiCheckSignedFile(
|
||||
PVOID hash,
|
||||
UINT32 size,
|
||||
ALG_ID algorithm,
|
||||
LPWIN_CERTIFICATE certificate,
|
||||
ULONG securitySize,
|
||||
MINCRYPT_POLICY_INFO* policy,
|
||||
LARGE_INTEGER* time,
|
||||
MINCRYPT_POLICY_INFO* timestamp
|
||||
)
|
||||
{
|
||||
++embeddedCalls;
|
||||
observedEmbeddedAlgorithm = algorithm;
|
||||
observedSecuritySize = securitySize;
|
||||
observedCertificate = certificate;
|
||||
|
||||
for (UINT32 i = 0; i < size; ++i)
|
||||
{
|
||||
Check(static_cast<UCHAR*>(hash)[i] == 0xa5, "computed digest forwarded unchanged");
|
||||
}
|
||||
|
||||
Populate(policy, timestamp);
|
||||
time->QuadPart = 123;
|
||||
return embeddedStatus;
|
||||
}
|
||||
|
||||
#ifdef DOMITO_TEST_OTHER_ARCH
|
||||
#undef _M_AMD64
|
||||
#undef _AMD64_
|
||||
#endif
|
||||
#include "ci-validation-under-test.inc"
|
||||
|
||||
static void
|
||||
CheckEmptyDigest(
|
||||
const DOMITO_CI_VALIDATION_RESULT& result
|
||||
)
|
||||
{
|
||||
Check(!result.HashSize && !result.HashAlgId && !result.SigningTime.QuadPart, "failure digest/time cleared");
|
||||
|
||||
for (auto value : result.Hash)
|
||||
{
|
||||
Check(!value, "failure hash bytes cleared");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
Clear(
|
||||
DOMITO_CI_VALIDATION_RESULT* result
|
||||
)
|
||||
{
|
||||
DomitoFreeCiValidationResult(result);
|
||||
DOMITO_CI_VALIDATION_RESULT empty = {};
|
||||
Check(std::memcmp(result, &empty, sizeof(empty)) == 0, "full result cleared by destructor");
|
||||
Check(live == 0, "no leaked policy/digest allocation");
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
DOMITO_CI_VALIDATION_RESULT result = {};
|
||||
PFILE_OBJECT object = reinterpret_cast<PFILE_OBJECT>(static_cast<ULONG_PTR>(0x1234));
|
||||
DomitoFreeCiValidationResult(nullptr);
|
||||
Clear(&result);
|
||||
Clear(&result);
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, nullptr) == STATUS_INVALID_PARAMETER,
|
||||
"null native result"
|
||||
);
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, nullptr) ==
|
||||
STATUS_INVALID_PARAMETER,
|
||||
"null embedded result"
|
||||
);
|
||||
Check(!validationCalls && !embeddedCalls && !digestCalls, "null result made no validation calls");
|
||||
|
||||
for (UINT32 flags : {0UL, 0x20UL, 0xa0UL, 0x80000024UL})
|
||||
{
|
||||
for (UINT8 level : {UINT8(0), UINT8(7), UINT8(255)})
|
||||
{
|
||||
result.HashSize = 1;
|
||||
result.HashAlgId = 0xdead;
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, flags, level, DomitoCiSignatureSelectionNative, &result) ==
|
||||
STATUS_SUCCESS,
|
||||
"native status"
|
||||
);
|
||||
Check(
|
||||
observedObject == object && observedFlags == flags && observedLevel == level && observedCapacity == 64,
|
||||
"native inputs unchanged"
|
||||
);
|
||||
Check(
|
||||
result.FileObjectValidationFlags == flags && result.RequestedSigningLevel == level,
|
||||
"export input controls recorded"
|
||||
);
|
||||
Check(
|
||||
result.HashSize == 32 && result.HashAlgId == CALG_SHA_256 && result.Hash[0] == 0x5a &&
|
||||
result.SigningTime.QuadPart == 99,
|
||||
"native result"
|
||||
);
|
||||
// API return success does not silently reinterpret a policy status.
|
||||
Check(result.PolicyInfo.VerificationStatus == STATUS_INVALID_IMAGE_HASH, "policy observation retained");
|
||||
Clear(&result);
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned beforeExperimental = validationCalls;
|
||||
NTSTATUS experimental =
|
||||
DomitoValidateFileObjectEx(object, 0x20, 9, DomitoCiSignatureSelectionFirstSuccessfulExperimental, &result);
|
||||
#ifdef DOMITO_TEST_OTHER_ARCH
|
||||
Check(
|
||||
experimental == STATUS_NOT_SUPPORTED && validationCalls == beforeExperimental,
|
||||
"experimental mode excluded on non-x64"
|
||||
);
|
||||
CheckEmptyDigest(result);
|
||||
#else
|
||||
Check(experimental == STATUS_SUCCESS && validationCalls == beforeExperimental + 1, "experimental native call");
|
||||
Check(observedFlags == 0xa0 && observedLevel == 9, "only experimental bit added");
|
||||
#endif
|
||||
Clear(&result);
|
||||
const unsigned beforeInvalid = validationCalls;
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(nullptr, 0x20, 0, DomitoCiSignatureSelectionNative, &result) ==
|
||||
STATUS_INVALID_PARAMETER,
|
||||
"null file object"
|
||||
);
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0x20, 0, static_cast<DOMITO_CI_SIGNATURE_SELECTION>(-1), &result) ==
|
||||
STATUS_INVALID_PARAMETER,
|
||||
"invalid selection"
|
||||
);
|
||||
Check(validationCalls == beforeInvalid, "invalid requests never call validation");
|
||||
Clear(&result);
|
||||
|
||||
for (NTSTATUS failure : {STATUS_INVALID_IMAGE_HASH, STATUS_BUFFER_TOO_SMALL, STATUS_INSUFFICIENT_RESOURCES})
|
||||
{
|
||||
nativeStatus = failure;
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0x20, 0, DomitoCiSignatureSelectionNative, &result) == failure,
|
||||
"native failure preserved"
|
||||
);
|
||||
CheckEmptyDigest(result);
|
||||
Check(live == 2 && result.PolicyInfo.PolicyBits == 0x123, "failure policy diagnostics remain owned");
|
||||
Clear(&result);
|
||||
}
|
||||
|
||||
nativeStatus = STATUS_SUCCESS;
|
||||
nativeSize = 65;
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) ==
|
||||
STATUS_INVALID_BUFFER_SIZE,
|
||||
"oversized returned hash rejected"
|
||||
);
|
||||
CheckEmptyDigest(result);
|
||||
Clear(&result);
|
||||
nativeSize = 31;
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) ==
|
||||
STATUS_INVALID_BUFFER_SIZE,
|
||||
"algorithm length mismatch rejected"
|
||||
);
|
||||
Clear(&result);
|
||||
nativeSize = 32;
|
||||
nativeAlgorithm = 0xdead;
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) == STATUS_NOT_SUPPORTED,
|
||||
"unknown returned algorithm rejected"
|
||||
);
|
||||
Clear(&result);
|
||||
|
||||
for (ALG_ID algorithm : {CALG_SHA1, CALG_SHA_256, CALG_SHA_384, CALG_SHA_512})
|
||||
{
|
||||
nativeAlgorithm = algorithm;
|
||||
nativeSize = DomitoCiDigestSize(algorithm);
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) == STATUS_SUCCESS,
|
||||
"known native digest sizes"
|
||||
);
|
||||
|
||||
for (UINT32 i = result.HashSize; i < sizeof(result.Hash); ++i)
|
||||
{
|
||||
Check(!result.Hash[i], "unused native digest bytes cleared");
|
||||
}
|
||||
|
||||
Clear(&result);
|
||||
fakeDigestSize = DomitoCiDigestSize(algorithm);
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), algorithm, &result) ==
|
||||
STATUS_SUCCESS,
|
||||
"embedded success"
|
||||
);
|
||||
Check(
|
||||
observedEmbeddedAlgorithm == algorithm && observedSecuritySize == 123 && observedCertificate == image,
|
||||
"embedded certificate/algorithm forwarded"
|
||||
);
|
||||
Check(
|
||||
result.HashSize == fakeDigestSize && result.HashAlgId == algorithm && result.Hash[0] == 0xa5 &&
|
||||
result.SigningTime.QuadPart == 123,
|
||||
"embedded result owns copied digest"
|
||||
);
|
||||
Check(
|
||||
!result.FileObjectValidationFlags && !result.RequestedSigningLevel && live == 2,
|
||||
"embedded metadata and digest cleanup"
|
||||
);
|
||||
Clear(&result);
|
||||
}
|
||||
|
||||
nativeAlgorithm = CALG_SHA_256;
|
||||
nativeSize = 32;
|
||||
fakeDigestSize = 32;
|
||||
nativeStatus = STATUS_MORE_ENTRIES;
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) == STATUS_MORE_ENTRIES,
|
||||
"nonnegative native status preserved"
|
||||
);
|
||||
Clear(&result);
|
||||
nativeStatus = STATUS_SUCCESS;
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(nullptr, 0, CALG_SHA_256, &result) ==
|
||||
STATUS_INVALID_PARAMETER,
|
||||
"null PE"
|
||||
);
|
||||
unsigned beforeDigest = digestCalls;
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), 0xdead, &result) ==
|
||||
STATUS_NOT_SUPPORTED,
|
||||
"unsupported algorithm"
|
||||
);
|
||||
Check(digestCalls == beforeDigest, "unsupported algorithm does not hash");
|
||||
|
||||
for (NTSTATUS failure : {STATUS_NOT_SUPPORTED, STATUS_INVALID_IMAGE_FORMAT, STATUS_INSUFFICIENT_RESOURCES})
|
||||
{
|
||||
digestStatus = failure;
|
||||
unsigned beforeEmbedded = embeddedCalls;
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) == failure,
|
||||
"digest failure propagation"
|
||||
);
|
||||
Check(embeddedCalls == beforeEmbedded, "digest failure never calls CI");
|
||||
CheckEmptyDigest(result);
|
||||
Clear(&result);
|
||||
}
|
||||
|
||||
partialDigestOnFailure = true;
|
||||
unsigned beforeFree = digestFrees;
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
|
||||
STATUS_INSUFFICIENT_RESOURCES,
|
||||
"partial digest failure"
|
||||
);
|
||||
Check(digestFrees == beforeFree + 1, "partial digest allocation freed");
|
||||
Clear(&result);
|
||||
partialDigestOnFailure = false;
|
||||
digestStatus = STATUS_SUCCESS;
|
||||
omitCertificate = true;
|
||||
unsigned beforeEmbedded = embeddedCalls;
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
|
||||
STATUS_INVALID_IMAGE_HASH,
|
||||
"unsigned PE rejected"
|
||||
);
|
||||
Check(embeddedCalls == beforeEmbedded, "unsigned PE does not call CI");
|
||||
Clear(&result);
|
||||
omitCertificate = false;
|
||||
omitDigest = true;
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
|
||||
STATUS_INVALID_BUFFER_SIZE,
|
||||
"missing digest rejected"
|
||||
);
|
||||
Clear(&result);
|
||||
omitDigest = false;
|
||||
fakeDigestSize = 31;
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
|
||||
STATUS_INVALID_BUFFER_SIZE,
|
||||
"digest helper length mismatch"
|
||||
);
|
||||
Clear(&result);
|
||||
fakeDigestSize = 32;
|
||||
embeddedStatus = STATUS_INVALID_IMAGE_HASH;
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
|
||||
STATUS_INVALID_IMAGE_HASH,
|
||||
"CI embedded failure propagated without fallback"
|
||||
);
|
||||
CheckEmptyDigest(result);
|
||||
Check(live == 2 && result.TimeStampPolicyInfo.PolicyBits == 0x456, "embedded failure diagnostics owned");
|
||||
Clear(&result);
|
||||
embeddedStatus = STATUS_SUCCESS;
|
||||
|
||||
for (unsigned i = 0; i < 16; ++i)
|
||||
{
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(object, 0x20, 0, DomitoCiSignatureSelectionNative, &result) == STATUS_SUCCESS,
|
||||
"repeated native"
|
||||
);
|
||||
Check(
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
|
||||
STATUS_SUCCESS,
|
||||
"reuse native result as embedded result"
|
||||
);
|
||||
Check(
|
||||
!result.FileObjectValidationFlags && !result.RequestedSigningLevel && live == 2,
|
||||
"reuse resets metadata without leaking"
|
||||
);
|
||||
}
|
||||
|
||||
// Invalid calls must release a previously valid result, too.
|
||||
Check(
|
||||
DomitoValidateFileObjectEx(nullptr, 0, 0, DomitoCiSignatureSelectionNative, &result) ==
|
||||
STATUS_INVALID_PARAMETER,
|
||||
"invalid reuse cleans old result"
|
||||
);
|
||||
Clear(&result);
|
||||
std::printf(
|
||||
"PASS: %u CI validation checks (%s architecture branch), no live allocations\n",
|
||||
checks,
|
||||
#ifdef DOMITO_TEST_OTHER_ARCH
|
||||
"non-x64"
|
||||
#else
|
||||
"x64"
|
||||
#endif
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
// Tests extract the unchanged production bodies. Kernel I/O is simulated.
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
#include <ntstatus.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <cctype>
|
||||
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
||||
#define MAXUSHORT 0xffff
|
||||
#define Executive 0
|
||||
#define KernelMode 0
|
||||
#define NotificationEvent 0
|
||||
#define OBJ_KERNEL_HANDLE 0x200
|
||||
#define InitializeObjectAttributes(a, n, f, r, s) ((a)->Attributes = (f))
|
||||
|
||||
struct OBJECT_ATTRIBUTES
|
||||
{
|
||||
ULONG Attributes;
|
||||
};
|
||||
|
||||
struct ANSI_STRING
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PCHAR Buffer;
|
||||
};
|
||||
|
||||
typedef ANSI_STRING* PANSI_STRING;
|
||||
|
||||
struct IO_STATUS_BLOCK
|
||||
{
|
||||
NTSTATUS Status;
|
||||
ULONG_PTR Information;
|
||||
};
|
||||
|
||||
static int objectType;
|
||||
static void* objectTypeValue = &objectType;
|
||||
static void** ExEventObjectType = &objectTypeValue;
|
||||
static unsigned checks;
|
||||
|
||||
static void
|
||||
Check(
|
||||
bool value,
|
||||
const char* label
|
||||
)
|
||||
{
|
||||
++checks;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
std::fprintf(stderr, "FAIL: %s\n", label);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static LONG
|
||||
RtlCompareString(
|
||||
PANSI_STRING a,
|
||||
PANSI_STRING b,
|
||||
BOOLEAN insensitive
|
||||
)
|
||||
{
|
||||
if (a->Length != b->Length)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (USHORT i = 0; i < a->Length; ++i)
|
||||
{
|
||||
unsigned char x = (unsigned char)a->Buffer[i], y = (unsigned char)b->Buffer[i];
|
||||
|
||||
if (insensitive)
|
||||
{
|
||||
x = (unsigned char)std::tolower(x);
|
||||
y = (unsigned char)std::tolower(y);
|
||||
}
|
||||
|
||||
if (x != y)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void* exportPointer;
|
||||
static ULONG exportBytes;
|
||||
|
||||
static PVOID
|
||||
Directory(
|
||||
PVOID,
|
||||
BOOLEAN mapped,
|
||||
USHORT index,
|
||||
PULONG bytes
|
||||
)
|
||||
{
|
||||
Check(mapped && index == IMAGE_DIRECTORY_ENTRY_EXPORT, "mapped export lookup");
|
||||
*bytes = exportBytes;
|
||||
return exportPointer;
|
||||
}
|
||||
|
||||
struct
|
||||
{
|
||||
PVOID (*RtlImageDirectoryEntryToData)(
|
||||
PVOID,
|
||||
BOOLEAN,
|
||||
USHORT,
|
||||
PULONG
|
||||
);
|
||||
} G_Common = {Directory};
|
||||
|
||||
static int eventToken;
|
||||
static int fileToken;
|
||||
static unsigned created;
|
||||
static unsigned referenced;
|
||||
static unsigned closed;
|
||||
static unsigned dereferenced;
|
||||
static unsigned reads;
|
||||
static unsigned waits;
|
||||
static int ioMode;
|
||||
static bool outstanding;
|
||||
static IO_STATUS_BLOCK* pendingIosb;
|
||||
static ULONG requested;
|
||||
static LONGLONG expectedOffset;
|
||||
|
||||
static NTSTATUS
|
||||
ZwCreateEvent(
|
||||
HANDLE* event,
|
||||
ACCESS_MASK access,
|
||||
OBJECT_ATTRIBUTES* attributes,
|
||||
int type,
|
||||
BOOLEAN signaled
|
||||
)
|
||||
{
|
||||
Check(
|
||||
access == EVENT_ALL_ACCESS && attributes->Attributes == OBJ_KERNEL_HANDLE && type == NotificationEvent &&
|
||||
!signaled,
|
||||
"private kernel event"
|
||||
);
|
||||
|
||||
if (ioMode == 4)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
++created;
|
||||
*event = &eventToken;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
ObReferenceObjectByHandle(
|
||||
HANDLE event,
|
||||
ACCESS_MASK access,
|
||||
void* type,
|
||||
int mode,
|
||||
void** object,
|
||||
void*
|
||||
)
|
||||
{
|
||||
Check(
|
||||
event == &eventToken && access == SYNCHRONIZE && type == *ExEventObjectType && mode == KernelMode,
|
||||
"reference own event"
|
||||
);
|
||||
|
||||
if (ioMode == 5)
|
||||
{
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
++referenced;
|
||||
*object = &eventToken;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
ZwClose(
|
||||
HANDLE event
|
||||
)
|
||||
{
|
||||
Check(event == &eventToken && !outstanding, "close only after completion");
|
||||
++closed;
|
||||
}
|
||||
|
||||
static void
|
||||
ObDereferenceObject(
|
||||
void* object
|
||||
)
|
||||
{
|
||||
Check(object == &eventToken && !outstanding, "dereference only after completion");
|
||||
++dereferenced;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
ZwReadFile(
|
||||
HANDLE file,
|
||||
HANDLE event,
|
||||
void*,
|
||||
void*,
|
||||
IO_STATUS_BLOCK* iosb,
|
||||
void*,
|
||||
ULONG size,
|
||||
LARGE_INTEGER* offset,
|
||||
void*
|
||||
)
|
||||
{
|
||||
++reads;
|
||||
Check(
|
||||
file == &fileToken && event == &eventToken && offset && offset->QuadPart == expectedOffset,
|
||||
"file and explicit offset"
|
||||
);
|
||||
Check(iosb->Status == 0 && iosb->Information == 0, "initialized IOSB");
|
||||
requested = size;
|
||||
|
||||
if (ioMode == 6)
|
||||
{
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
if (ioMode == 1 || ioMode == 2)
|
||||
{
|
||||
outstanding = true;
|
||||
pendingIosb = iosb;
|
||||
return STATUS_PENDING;
|
||||
}
|
||||
|
||||
iosb->Status = STATUS_SUCCESS;
|
||||
iosb->Information = ioMode == 3 ? size - 1 : size;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
KeWaitForSingleObject(
|
||||
void* object,
|
||||
int reason,
|
||||
int mode,
|
||||
BOOLEAN alertable,
|
||||
LARGE_INTEGER* timeout
|
||||
)
|
||||
{
|
||||
++waits;
|
||||
Check(
|
||||
object == &eventToken && reason == Executive && mode == KernelMode && !alertable && !timeout && outstanding,
|
||||
"noninterruptible per-request wait"
|
||||
);
|
||||
outstanding = false;
|
||||
pendingIosb->Status = ioMode == 2 ? STATUS_END_OF_FILE : STATUS_SUCCESS;
|
||||
pendingIosb->Information = ioMode == 2 ? 0 : requested;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#include "common-under-test.inc"
|
||||
|
||||
static std::vector<UCHAR>
|
||||
Image()
|
||||
{
|
||||
std::vector<UCHAR> image(4096);
|
||||
auto dos = (IMAGE_DOS_HEADER*)image.data();
|
||||
dos->e_magic = IMAGE_DOS_SIGNATURE;
|
||||
dos->e_lfanew = 128;
|
||||
auto nt = (IMAGE_NT_HEADERS64*)(image.data() + 128);
|
||||
nt->Signature = IMAGE_NT_SIGNATURE;
|
||||
nt->OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC;
|
||||
nt->OptionalHeader.SizeOfImage = (ULONG)image.size();
|
||||
auto exports = (IMAGE_EXPORT_DIRECTORY*)(image.data() + 512);
|
||||
exports->NumberOfFunctions = 3;
|
||||
exports->NumberOfNames = 2;
|
||||
exports->AddressOfFunctions = 801;
|
||||
exports->AddressOfNames = 901;
|
||||
exports->AddressOfNameOrdinals = 1001;
|
||||
const ULONG functions[] = {2000, 2100, 2200};
|
||||
const ULONG names[] = {1100, 1120};
|
||||
const USHORT ordinals[] = {2, 0};
|
||||
std::memcpy(image.data() + 801, functions, sizeof(functions));
|
||||
std::memcpy(image.data() + 901, names, sizeof(names));
|
||||
std::memcpy(image.data() + 1001, ordinals, sizeof(ordinals));
|
||||
std::memcpy(image.data() + 1100, "Beta", 5);
|
||||
std::memcpy(image.data() + 1120, "Alpha", 6);
|
||||
return image;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
Lookup(
|
||||
std::vector<UCHAR>& image,
|
||||
const char* name,
|
||||
PVOID* output
|
||||
)
|
||||
{
|
||||
ANSI_STRING string = {(USHORT)std::strlen(name), (USHORT)std::strlen(name), (PCHAR)name};
|
||||
exportPointer = image.data() + 512;
|
||||
exportBytes = 256;
|
||||
return DomitoFindExportedFunctionAddress(image.data(), &string, output);
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
auto image = Image();
|
||||
PVOID output = nullptr;
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_SUCCESS && output == image.data() + 2200, "ordinal-indexed EAT");
|
||||
Check(Lookup(image, "aLpHa", &output) == STATUS_SUCCESS && output == image.data() + 2000, "case-insensitive name");
|
||||
Check(Lookup(image, "Alpha", nullptr) == STATUS_SUCCESS, "optional output discovery");
|
||||
Check(Lookup(image, "missing", &output) == STATUS_NOT_FOUND && !output, "notfound clears output");
|
||||
auto exports = (IMAGE_EXPORT_DIRECTORY*)(image.data() + 512);
|
||||
const USHORT invalidOrdinal = 3;
|
||||
std::memcpy(image.data() + 1001, &invalidOrdinal, 2);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT && !output, "ordinal bounded");
|
||||
image = Image();
|
||||
const ULONG forwarder = 600;
|
||||
std::memcpy(image.data() + 801 + 8, &forwarder, 4);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_NOT_SUPPORTED && !output, "forwarder rejected");
|
||||
image = Image();
|
||||
const ULONG zero = 0;
|
||||
std::memcpy(image.data() + 801 + 8, &zero, 4);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_NOT_FOUND, "empty EAT slot");
|
||||
image = Image();
|
||||
const ULONG outside = 4096;
|
||||
std::memcpy(image.data() + 801 + 8, &outside, 4);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "function RVA bounded");
|
||||
image = Image();
|
||||
exports = (IMAGE_EXPORT_DIRECTORY*)(image.data() + 512);
|
||||
exports->NumberOfNames = 0xffffffff;
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "table multiplication bounded");
|
||||
image = Image();
|
||||
exports = (IMAGE_EXPORT_DIRECTORY*)(image.data() + 512);
|
||||
exports->AddressOfFunctions = 4090;
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "EAT span bounded");
|
||||
image = Image();
|
||||
const ULONG lateName = 4094;
|
||||
std::memcpy(image.data() + 901, &lateName, 4);
|
||||
image[4094] = 'x';
|
||||
image[4095] = 'x';
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "unterminated name bounded");
|
||||
image = Image();
|
||||
std::memcpy(image.data() + 901, &outside, 4);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "name RVA bounded");
|
||||
image = Image();
|
||||
ANSI_STRING name = {4, 4, (PCHAR) "Beta"};
|
||||
exportPointer = image.data() + 512;
|
||||
exportBytes = 4096;
|
||||
Check(
|
||||
DomitoFindExportedFunctionAddress(image.data(), &name, &output) == STATUS_INVALID_IMAGE_FORMAT,
|
||||
"export directory span bounded"
|
||||
);
|
||||
output = (void*)1;
|
||||
Check(
|
||||
DomitoFindExportedFunctionAddress(nullptr, &name, &output) == STATUS_INVALID_PARAMETER && !output,
|
||||
"invalid arguments clear output"
|
||||
);
|
||||
char buffer[32];
|
||||
|
||||
for (ioMode = 0; ioMode <= 6; ++ioMode)
|
||||
{
|
||||
created = referenced = closed = dereferenced = reads = waits = 0;
|
||||
outstanding = false;
|
||||
expectedOffset = 123;
|
||||
LARGE_INTEGER offset;
|
||||
offset.QuadPart = expectedOffset;
|
||||
NTSTATUS status = DomitoReadFileAt(&fileToken, buffer, sizeof(buffer), &offset);
|
||||
const NTSTATUS expected[] = {
|
||||
STATUS_SUCCESS,
|
||||
STATUS_SUCCESS,
|
||||
STATUS_END_OF_FILE,
|
||||
STATUS_END_OF_FILE,
|
||||
STATUS_INSUFFICIENT_RESOURCES,
|
||||
STATUS_ACCESS_DENIED,
|
||||
STATUS_ACCESS_DENIED
|
||||
};
|
||||
Check(status == expected[ioMode], "read status and exact-byte contract");
|
||||
Check(created == closed && referenced == dereferenced && !outstanding, "event lifetime balanced");
|
||||
Check(waits == ((ioMode == 1 || ioMode == 2) ? 1u : 0u), "wait pending only");
|
||||
}
|
||||
|
||||
ioMode = 0;
|
||||
expectedOffset = 0;
|
||||
Check(DomitoReadFile(&fileToken, buffer, sizeof(buffer)) == STATUS_SUCCESS, "zero-offset wrapper");
|
||||
LARGE_INTEGER offset = {};
|
||||
offset.QuadPart = -1;
|
||||
Check(
|
||||
DomitoReadFileAt(&fileToken, buffer, sizeof(buffer), &offset) == STATUS_INVALID_PARAMETER,
|
||||
"negative offset rejected"
|
||||
);
|
||||
Check(
|
||||
DomitoReadFileAt(&fileToken, buffer, sizeof(buffer), nullptr) == STATUS_INVALID_PARAMETER,
|
||||
"null offset rejected"
|
||||
);
|
||||
offset.QuadPart = 0;
|
||||
Check(DomitoReadFileAt(&fileToken, nullptr, 0, &offset) == STATUS_SUCCESS, "zero byte read");
|
||||
Check(DomitoReadFileAt(&fileToken, nullptr, 1, &offset) == STATUS_INVALID_PARAMETER, "null buffer rejected");
|
||||
std::printf("PASS: %u common-helper checks (mock kernel I/O)\n", checks);
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
#include <ntstatus.h>
|
||||
#include <bcrypt.h>
|
||||
#include <wincrypt.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
||||
#define PASSIVE_LEVEL 0
|
||||
#define PAGED_CODE() ((void)0)
|
||||
#undef NTDDI_VERSION
|
||||
#define NTDDI_VERSION 0x0a000008
|
||||
#undef NTDDI_WIN10
|
||||
#define NTDDI_WIN10 0x0a000000
|
||||
#define DOMITO_POOL_TAG 0x446f6d69
|
||||
#define MAXUSHORT 0xffff
|
||||
#define PagedPool 1
|
||||
#define KernelMode 0
|
||||
#define ProcessImageFileName 27
|
||||
#define OBJ_KERNEL_HANDLE 0x200
|
||||
#define OBJ_CASE_INSENSITIVE 0x40
|
||||
#define FILE_NON_DIRECTORY_FILE 0x40
|
||||
#define FILE_SYNCHRONOUS_IO_NONALERT 0x20
|
||||
typedef void* PEPROCESS;
|
||||
typedef void* PFILE_OBJECT;
|
||||
typedef const UCHAR* PCUCHAR;
|
||||
|
||||
typedef struct _UNICODE_STRING
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWSTR Buffer;
|
||||
} UNICODE_STRING, *PUNICODE_STRING;
|
||||
|
||||
typedef struct _ANSI_STRING
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PCHAR Buffer;
|
||||
} ANSI_STRING, *PANSI_STRING;
|
||||
|
||||
struct OBJECT_ATTRIBUTES
|
||||
{
|
||||
ULONG Attributes;
|
||||
PUNICODE_STRING ObjectName;
|
||||
};
|
||||
|
||||
struct CLIENT_ID
|
||||
{
|
||||
HANDLE UniqueProcess;
|
||||
HANDLE UniqueThread;
|
||||
};
|
||||
|
||||
struct IO_STATUS_BLOCK
|
||||
{
|
||||
NTSTATUS Status;
|
||||
ULONG_PTR Information;
|
||||
};
|
||||
|
||||
#define InitializeObjectAttributes(a, n, f, r, s) ((a)->Attributes = (f), (a)->ObjectName = (n))
|
||||
#define RTL_CONSTANT_STRING(s) {sizeof(s) - sizeof((s)[0]), sizeof(s), const_cast<char*>(s)}
|
||||
typedef PVOID(NTAPI* PMINCRYPT_ALLOCATE_ROUTINE)(SIZE_T);
|
||||
typedef PVOID NTAPI MINCRYPT_ALLOCATE_ROUTINE(SIZE_T);
|
||||
#include "consumer-api.inc"
|
||||
|
||||
static unsigned checks;
|
||||
static unsigned live;
|
||||
static unsigned refs;
|
||||
static unsigned derefs;
|
||||
static unsigned closed;
|
||||
static unsigned validated;
|
||||
static unsigned opened;
|
||||
static unsigned queried;
|
||||
static int failure;
|
||||
static int cryptoFailure;
|
||||
static NTSTATUS moduleStatus = STATUS_SUCCESS;
|
||||
static NTSTATUS exportStatus = STATUS_SUCCESS;
|
||||
static NTSTATUS validateStatus = STATUS_SUCCESS;
|
||||
static int fileToken;
|
||||
static int processToken;
|
||||
static void* fileTypeValue = &fileToken;
|
||||
static void** IoFileObjectType = &fileTypeValue;
|
||||
|
||||
static void
|
||||
Check(
|
||||
bool value,
|
||||
const char* msg
|
||||
)
|
||||
{
|
||||
++checks;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
std::fprintf(stderr, "FAIL: %s\n", msg);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void*
|
||||
Allocate(
|
||||
SIZE_T size
|
||||
)
|
||||
{
|
||||
if (failure == 1)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* p = std::malloc(size);
|
||||
Check(p != nullptr, "allocation");
|
||||
++live;
|
||||
std::memset(p, 0xa5, size);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void
|
||||
Release(
|
||||
void* p
|
||||
)
|
||||
{
|
||||
Check(p && live, "single owner release");
|
||||
--live;
|
||||
std::free(p);
|
||||
}
|
||||
|
||||
static struct
|
||||
{
|
||||
PVOID (*Allocate)(SIZE_T);
|
||||
void (*Free)(PVOID);
|
||||
} G_Memory = {Allocate, Release};
|
||||
|
||||
static PVOID
|
||||
ExAllocatePoolWithTag(
|
||||
int,
|
||||
SIZE_T size,
|
||||
ULONG
|
||||
)
|
||||
{
|
||||
return Allocate(size);
|
||||
}
|
||||
|
||||
static void
|
||||
ExFreePoolWithTag(
|
||||
void* p,
|
||||
ULONG
|
||||
)
|
||||
{
|
||||
Release(p);
|
||||
}
|
||||
|
||||
static void
|
||||
ExFreePool(
|
||||
void* p
|
||||
)
|
||||
{
|
||||
Release(p);
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
FakePublisher(
|
||||
MINCERT_BLOB*,
|
||||
PMINCRYPT_ALLOCATE_ROUTINE allocator,
|
||||
PUNICODE_STRING name
|
||||
)
|
||||
{
|
||||
name->Buffer = static_cast<PWCHAR>(allocator(12));
|
||||
|
||||
if (!name->Buffer)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
std::memcpy(name->Buffer, L"hello", 12);
|
||||
name->Length = 10;
|
||||
name->MaximumLength = 12;
|
||||
|
||||
if (failure == 8)
|
||||
{
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
if (failure == 9)
|
||||
{
|
||||
name->Length = 13;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
DomitoFindModuleBaseAddress(
|
||||
PANSI_STRING name,
|
||||
PVOID* module
|
||||
)
|
||||
{
|
||||
Check(name && name->Length, "module name");
|
||||
|
||||
if (NT_SUCCESS(moduleStatus))
|
||||
{
|
||||
*module = &fileToken;
|
||||
}
|
||||
|
||||
return moduleStatus;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
DomitoFindExportedFunctionAddress(
|
||||
PVOID module,
|
||||
PANSI_STRING name,
|
||||
PVOID* routine
|
||||
)
|
||||
{
|
||||
Check(module == &fileToken && name->Length, "export resolution");
|
||||
|
||||
if (NT_SUCCESS(exportStatus))
|
||||
{
|
||||
*routine = reinterpret_cast<PVOID>(FakePublisher);
|
||||
}
|
||||
|
||||
return exportStatus;
|
||||
}
|
||||
|
||||
void
|
||||
DomitoFreeCiValidationResult(
|
||||
DOMITO_CI_VALIDATION_RESULT* result
|
||||
)
|
||||
{
|
||||
if (result->PolicyInfo.ChainInfo)
|
||||
{
|
||||
Release(result->PolicyInfo.ChainInfo);
|
||||
}
|
||||
|
||||
*result = {};
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
DomitoValidateFileObjectEx(
|
||||
PFILE_OBJECT object,
|
||||
UINT32 flags,
|
||||
UINT8 level,
|
||||
DOMITO_CI_SIGNATURE_SELECTION selection,
|
||||
DOMITO_CI_VALIDATION_RESULT* result
|
||||
)
|
||||
{
|
||||
++validated;
|
||||
Check(
|
||||
object == &fileToken && flags == 0x20 && level == 7 && selection == DomitoCiSignatureSelectionNative,
|
||||
"CI arguments unchanged"
|
||||
);
|
||||
Check(!result->PolicyInfo.ChainInfo, "result reset before native call");
|
||||
result->PolicyInfo.ChainInfo = static_cast<MINCRYPT_CHAIN_INFO*>(Allocate(1));
|
||||
return validateStatus;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
ObReferenceObjectByHandle(
|
||||
HANDLE handle,
|
||||
ACCESS_MASK access,
|
||||
void* type,
|
||||
int mode,
|
||||
void** object,
|
||||
void*
|
||||
)
|
||||
{
|
||||
Check(
|
||||
handle == &fileToken && access == FILE_READ_DATA && type == *IoFileObjectType && mode == KernelMode,
|
||||
"trusted handle reference"
|
||||
);
|
||||
|
||||
if (failure == 2)
|
||||
{
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
++refs;
|
||||
*object = &fileToken;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
ObDereferenceObject(
|
||||
void* object
|
||||
)
|
||||
{
|
||||
Check(object == &fileToken, "file dereference");
|
||||
++derefs;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
ZwOpenFile(
|
||||
HANDLE* handle,
|
||||
ACCESS_MASK access,
|
||||
OBJECT_ATTRIBUTES* a,
|
||||
IO_STATUS_BLOCK*,
|
||||
ULONG share,
|
||||
ULONG options
|
||||
)
|
||||
{
|
||||
++opened;
|
||||
Check(
|
||||
access == (FILE_READ_DATA | SYNCHRONIZE) && a->Attributes == (OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE) &&
|
||||
share == FILE_SHARE_READ && options == (FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT),
|
||||
"read-only stable path open"
|
||||
);
|
||||
|
||||
if (failure == 3)
|
||||
{
|
||||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
*handle = &fileToken;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
ZwClose(
|
||||
HANDLE handle
|
||||
)
|
||||
{
|
||||
Check(handle == &fileToken || handle == &processToken, "close owned handle");
|
||||
++closed;
|
||||
}
|
||||
|
||||
static void
|
||||
FillName(
|
||||
PUNICODE_STRING name
|
||||
)
|
||||
{
|
||||
name->Length = 8;
|
||||
name->MaximumLength = 10;
|
||||
name->Buffer = reinterpret_cast<PWCHAR>(name + 1);
|
||||
std::memcpy(name->Buffer, L"test", 10);
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
SeLocateProcessImageName(
|
||||
PEPROCESS process,
|
||||
PUNICODE_STRING* image
|
||||
)
|
||||
{
|
||||
Check(process == &processToken, "referenced process passed unchanged");
|
||||
|
||||
if (failure == 4)
|
||||
{
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
// OS allocations are independent of configurable Domito allocator failures.
|
||||
*image = static_cast<PUNICODE_STRING>(std::malloc(sizeof(UNICODE_STRING) + 10));
|
||||
++live;
|
||||
FillName(*image);
|
||||
|
||||
if (failure == 5)
|
||||
{
|
||||
(*image)->Length = 9;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
ZwOpenProcess(
|
||||
HANDLE* process,
|
||||
ACCESS_MASK access,
|
||||
OBJECT_ATTRIBUTES* a,
|
||||
CLIENT_ID* cid
|
||||
)
|
||||
{
|
||||
Check(
|
||||
access == 0x1000 && a->Attributes == OBJ_KERNEL_HANDLE && cid->UniqueProcess == ULongToHandle(123),
|
||||
"PID least privilege kernel open"
|
||||
);
|
||||
|
||||
if (failure == 6)
|
||||
{
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
*process = &processToken;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
QueryProcess(
|
||||
HANDLE process,
|
||||
int info,
|
||||
PVOID buffer,
|
||||
ULONG size,
|
||||
PULONG returned
|
||||
)
|
||||
{
|
||||
++queried;
|
||||
Check(process == &processToken && info == ProcessImageFileName, "PID query bound to one handle");
|
||||
*returned = sizeof(UNICODE_STRING) + 10;
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
return failure == 10 ? STATUS_SUCCESS : STATUS_INFO_LENGTH_MISMATCH;
|
||||
}
|
||||
|
||||
Check(size == *returned, "sized name allocation");
|
||||
FillName(static_cast<PUNICODE_STRING>(buffer));
|
||||
|
||||
if (failure == 7)
|
||||
{
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
if (failure == 11)
|
||||
{
|
||||
static_cast<PUNICODE_STRING>(buffer)->Buffer = reinterpret_cast<PWCHAR>(1);
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static struct
|
||||
{
|
||||
NTSTATUS (*ZwQueryInformationProcess)(
|
||||
HANDLE,
|
||||
int,
|
||||
PVOID,
|
||||
ULONG,
|
||||
PULONG
|
||||
);
|
||||
} G_Common = {QueryProcess};
|
||||
|
||||
static NTSTATUS
|
||||
OpenAlgorithm(
|
||||
BCRYPT_ALG_HANDLE* a,
|
||||
LPCWSTR n,
|
||||
LPCWSTR p,
|
||||
ULONG f
|
||||
)
|
||||
{
|
||||
return cryptoFailure == 1 ? STATUS_UNSUCCESSFUL : BCryptOpenAlgorithmProvider(a, n, p, f);
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
CreateHash(
|
||||
BCRYPT_ALG_HANDLE a,
|
||||
BCRYPT_HASH_HANDLE* h,
|
||||
PUCHAR o,
|
||||
ULONG os,
|
||||
PUCHAR s,
|
||||
ULONG ss,
|
||||
ULONG f
|
||||
)
|
||||
{
|
||||
return cryptoFailure == 2 ? STATUS_UNSUCCESSFUL : BCryptCreateHash(a, h, o, os, s, ss, f);
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
HashData(
|
||||
BCRYPT_HASH_HANDLE h,
|
||||
PUCHAR p,
|
||||
ULONG n,
|
||||
ULONG f
|
||||
)
|
||||
{
|
||||
return cryptoFailure == 3 ? STATUS_UNSUCCESSFUL : BCryptHashData(h, p, n, f);
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
FinishHash(
|
||||
BCRYPT_HASH_HANDLE h,
|
||||
PUCHAR p,
|
||||
ULONG n,
|
||||
ULONG f
|
||||
)
|
||||
{
|
||||
if (cryptoFailure == 4)
|
||||
{
|
||||
std::memset(p, 0xff, n);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
return BCryptFinishHash(h, p, n, f);
|
||||
}
|
||||
|
||||
#define BCryptOpenAlgorithmProvider OpenAlgorithm
|
||||
#define BCryptCreateHash CreateHash
|
||||
#define BCryptHashData HashData
|
||||
#define BCryptFinishHash FinishHash
|
||||
#ifdef DOMITO_TEST_OTHER_ARCH
|
||||
#undef _M_AMD64
|
||||
#undef _AMD64_
|
||||
#endif
|
||||
#include "consumer-under-test.inc"
|
||||
#undef BCryptOpenAlgorithmProvider
|
||||
#undef BCryptCreateHash
|
||||
#undef BCryptHashData
|
||||
#undef BCryptFinishHash
|
||||
|
||||
using Bytes = std::vector<UCHAR>;
|
||||
|
||||
static Bytes
|
||||
Der(
|
||||
UCHAR tag,
|
||||
const Bytes& contents
|
||||
)
|
||||
{
|
||||
Bytes b = {tag};
|
||||
|
||||
if (contents.size() < 128)
|
||||
{
|
||||
b.push_back(static_cast<UCHAR>(contents.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Check(contents.size() < 256, "fixture DER bound");
|
||||
b.push_back(0x81);
|
||||
b.push_back(static_cast<UCHAR>(contents.size()));
|
||||
}
|
||||
|
||||
b.insert(b.end(), contents.begin(), contents.end());
|
||||
return b;
|
||||
}
|
||||
|
||||
static Bytes
|
||||
Join(
|
||||
std::initializer_list<Bytes> values
|
||||
)
|
||||
{
|
||||
Bytes b;
|
||||
|
||||
for (const auto& v : values)
|
||||
{
|
||||
b.insert(b.end(), v.begin(), v.end());
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
static Bytes
|
||||
Fixture(
|
||||
bool version
|
||||
)
|
||||
{
|
||||
Bytes sig = Der(0x30, Join({Der(6, {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 11}), Der(5, {})}));
|
||||
Bytes rsa = Der(0x30, Join({Der(6, {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 1}), Der(5, {})}));
|
||||
Bytes name = Der(0x30, Der(0x31, Der(0x30, Join({Der(6, {0x55, 4, 3}), Der(0x0c, {'t', 'e', 's', 't'})}))));
|
||||
const std::string date = "260101000000Z";
|
||||
Bytes time = Der(0x17, Bytes(date.begin(), date.end()));
|
||||
Bytes key = Der(0x30, Join({Der(2, {0, 0xab, 0xcd}), Der(2, {1, 0, 1})}));
|
||||
key.insert(key.begin(), 0);
|
||||
Bytes spki = Der(0x30, Join({rsa, Der(3, key)}));
|
||||
Bytes tbs = Join(
|
||||
{version ? Der(0xa0, Der(2, {2})) : Bytes{}, Der(2, {1}), sig, name, Der(0x30, Join({time, time})), name, spki}
|
||||
);
|
||||
return Der(0x30, Join({Der(0x30, tbs), sig, Der(3, {0, 0xab, 0xcd})}));
|
||||
}
|
||||
|
||||
static void
|
||||
TestCertificates()
|
||||
{
|
||||
for (bool v : {false, true})
|
||||
{
|
||||
auto cert = Fixture(v);
|
||||
DOMITO_CERTIFICATE_SPKI spki = {};
|
||||
Check(
|
||||
NT_SUCCESS(DomitoGetCertificateSpki(cert.data(), static_cast<ULONG>(cert.size()), &spki)),
|
||||
"v1/v3 extraction"
|
||||
);
|
||||
Check(
|
||||
spki.Content.Data > spki.Encoded.Data && spki.Content.Size < spki.Encoded.Size,
|
||||
"content and complete TLV differ"
|
||||
);
|
||||
auto context = CertCreateCertificateContext(X509_ASN_ENCODING, cert.data(), static_cast<DWORD>(cert.size()));
|
||||
Check(context != nullptr, "Crypt32 independently decodes fixture certificate");
|
||||
BYTE* encoded = nullptr;
|
||||
DWORD size = 0;
|
||||
Check(
|
||||
CryptEncodeObjectEx(
|
||||
X509_ASN_ENCODING,
|
||||
X509_PUBLIC_KEY_INFO,
|
||||
&context->pCertInfo->SubjectPublicKeyInfo,
|
||||
CRYPT_ENCODE_ALLOC_FLAG,
|
||||
nullptr,
|
||||
&encoded,
|
||||
&size
|
||||
) != FALSE,
|
||||
"Crypt32 independent SPKI encoding"
|
||||
);
|
||||
Check(
|
||||
size == spki.Encoded.Size && !std::memcmp(encoded, spki.Encoded.Data, size),
|
||||
"extracted complete SPKI equals Crypt32"
|
||||
);
|
||||
LocalFree(encoded);
|
||||
CertFreeCertificateContext(context);
|
||||
UCHAR full[64];
|
||||
UCHAR legacy[64];
|
||||
UCHAR whole[64];
|
||||
UCHAR oracle[64];
|
||||
ULONG a = 64;
|
||||
ULONG b = 64;
|
||||
ULONG c = 64;
|
||||
DWORD d = 64;
|
||||
Check(
|
||||
NT_SUCCESS(DomitoHashCertificate(
|
||||
cert.data(), static_cast<ULONG>(cert.size()), DomitoCertificateHashSpkiDer, CALG_SHA_256, full, &a
|
||||
)),
|
||||
"full SPKI hash"
|
||||
);
|
||||
Check(
|
||||
NT_SUCCESS(DomitoHashCertificate(
|
||||
cert.data(),
|
||||
static_cast<ULONG>(cert.size()),
|
||||
DomitoCertificateHashSpkiContentLegacy,
|
||||
CALG_SHA_256,
|
||||
legacy,
|
||||
&b
|
||||
)),
|
||||
"legacy content SPKI hash"
|
||||
);
|
||||
Check(a == 32 && b == 32 && std::memcmp(full, legacy, 32) != 0, "explicit SPKI conventions differ");
|
||||
Check(
|
||||
CryptHashCertificate2(
|
||||
BCRYPT_SHA256_ALGORITHM, 0, nullptr, spki.Content.Data, spki.Content.Size, oracle, &d
|
||||
) != FALSE &&
|
||||
d == b && !std::memcmp(legacy, oracle, d),
|
||||
"legacy hash matches exact content-only oracle"
|
||||
);
|
||||
Check(
|
||||
NT_SUCCESS(DomitoHashCertificate(
|
||||
cert.data(), static_cast<ULONG>(cert.size()), DomitoCertificateHashDer, CALG_SHA_256, whole, &c
|
||||
)),
|
||||
"DER thumbprint"
|
||||
);
|
||||
d = 64;
|
||||
Check(
|
||||
CryptHashCertificate2(
|
||||
BCRYPT_SHA256_ALGORITHM, 0, nullptr, cert.data(), static_cast<DWORD>(cert.size()), oracle, &d
|
||||
) != FALSE &&
|
||||
d == c && !std::memcmp(whole, oracle, d),
|
||||
"thumbprint matches Crypt32"
|
||||
);
|
||||
|
||||
for (ULONG i = 0; i < cert.size(); ++i)
|
||||
{
|
||||
spki.Encoded = {cert.data(), 99};
|
||||
Check(!NT_SUCCESS(DomitoGetCertificateSpki(cert.data(), i, &spki)), "every truncated certificate rejected");
|
||||
Check(!spki.Encoded.Data && !spki.Content.Data, "failed extraction clears borrowed views");
|
||||
}
|
||||
|
||||
cert.push_back(0);
|
||||
Check(
|
||||
!NT_SUCCESS(DomitoGetCertificateSpki(cert.data(), static_cast<ULONG>(cert.size()), &spki)),
|
||||
"trailing byte rejected"
|
||||
);
|
||||
}
|
||||
|
||||
for (auto malformed :
|
||||
{Bytes{0x30, 0x80, 0, 0},
|
||||
Bytes{0x30, 0x81, 1, 0},
|
||||
Bytes{0x30, 0x85, 1, 2, 3, 4, 5},
|
||||
Bytes{0x30, 0x84, 0xff, 0xff, 0xff, 0xff}})
|
||||
{
|
||||
DOMITO_CERTIFICATE_SPKI spki = {};
|
||||
Check(
|
||||
!NT_SUCCESS(DomitoGetCertificateSpki(malformed.data(), static_cast<ULONG>(malformed.size()), &spki)),
|
||||
"bad DER lengths rejected"
|
||||
);
|
||||
}
|
||||
|
||||
const UCHAR abc[] = {'a', 'b', 'c'};
|
||||
UCHAR hash[64];
|
||||
|
||||
for (ALG_ID alg : {CALG_SHA1, CALG_SHA_256, CALG_SHA_384, CALG_SHA_512})
|
||||
{
|
||||
ULONG n = 0;
|
||||
Check(DomitoHashBuffer(abc, 3, alg, nullptr, &n) == STATUS_BUFFER_TOO_SMALL, "hash sizing");
|
||||
Check(n == 20 || n == 32 || n == 48 || n == 64, "known output size");
|
||||
ULONG cap = n - 1;
|
||||
Check(DomitoHashBuffer(abc, 3, alg, hash, &cap) == STATUS_BUFFER_TOO_SMALL && cap == n, "short hash capacity");
|
||||
cap = 64;
|
||||
Check(NT_SUCCESS(DomitoHashBuffer(nullptr, 0, alg, hash, &cap)), "empty hash");
|
||||
}
|
||||
|
||||
for (int fail = 1; fail <= 4; ++fail)
|
||||
{
|
||||
cryptoFailure = fail;
|
||||
ULONG n = 64;
|
||||
std::memset(hash, 0xff, 64);
|
||||
Check(!NT_SUCCESS(DomitoHashBuffer(abc, 3, CALG_SHA_256, hash, &n)) && !n, "CNG failure propagated");
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
Check(!hash[i], "CNG failure digest erased");
|
||||
}
|
||||
}
|
||||
|
||||
cryptoFailure = 0;
|
||||
|
||||
for (unsigned byte = 0; byte < 256; ++byte)
|
||||
{
|
||||
UCHAR in = static_cast<UCHAR>(byte);
|
||||
UCHAR out = 0;
|
||||
WCHAR hex[3];
|
||||
Check(NT_SUCCESS(DomitoEncodeHex(&in, 1, hex, 3)), "hex encoding");
|
||||
UNICODE_STRING str = {4, 6, hex};
|
||||
Check(NT_SUCCESS(DomitoDecodeHex(&str, &out, 1)) && out == in, "all byte values hex roundtrip");
|
||||
}
|
||||
|
||||
for (WCHAR ch : {L'g', L'/', L':', L' ', L'\0', L'\u1234'})
|
||||
{
|
||||
WCHAR text[] = {L'0', ch};
|
||||
UNICODE_STRING str = {4, 4, text};
|
||||
UCHAR out = 9;
|
||||
Check(
|
||||
DomitoDecodeHex(&str, &out, 1) == STATUS_INVALID_PARAMETER && !out,
|
||||
"invalid hex digit rejected and output zeroed"
|
||||
);
|
||||
}
|
||||
|
||||
WCHAR shortHex[2];
|
||||
Check(DomitoEncodeHex(abc, 3, shortHex, 2) == STATUS_BUFFER_TOO_SMALL && !shortHex[0], "short hex buffer cleared");
|
||||
|
||||
// Put every truncated prefix immediately before an inaccessible page.
|
||||
SYSTEM_INFO system = {};
|
||||
GetSystemInfo(&system);
|
||||
const SIZE_T page = system.dwPageSize;
|
||||
auto guarded = static_cast<PUCHAR>(VirtualAlloc(nullptr, 2 * page, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
||||
Check(guarded != nullptr, "guard-page allocation");
|
||||
DWORD old = 0;
|
||||
Check(VirtualProtect(guarded + page, page, PAGE_NOACCESS, &old) != FALSE, "guard page installed");
|
||||
const auto fixture = Fixture(true);
|
||||
|
||||
for (ULONG n = 0; n < fixture.size(); ++n)
|
||||
{
|
||||
UCHAR* input = guarded + page - n;
|
||||
std::memcpy(input, fixture.data(), n);
|
||||
DOMITO_CERTIFICATE_SPKI spki = {};
|
||||
Check(!NT_SUCCESS(DomitoGetCertificateSpki(input, n, &spki)), "truncated DER at guard page rejected");
|
||||
}
|
||||
|
||||
VirtualFree(guarded, 0, MEM_RELEASE);
|
||||
|
||||
// Read-only optional corpus: exercises real long-length RSA/ECC certificates.
|
||||
HCERTSTORE store = CertOpenSystemStoreW(0, L"ROOT");
|
||||
|
||||
if (store)
|
||||
{
|
||||
PCCERT_CONTEXT context = nullptr;
|
||||
unsigned count = 0;
|
||||
while (count < 16 && (context = CertEnumCertificatesInStore(store, context)) != nullptr)
|
||||
{
|
||||
++count;
|
||||
DOMITO_CERTIFICATE_SPKI spki = {};
|
||||
Check(
|
||||
NT_SUCCESS(DomitoGetCertificateSpki(context->pbCertEncoded, context->cbCertEncoded, &spki)),
|
||||
"real certificate SPKI extraction"
|
||||
);
|
||||
BYTE* encoded = nullptr;
|
||||
DWORD size = 0;
|
||||
Check(
|
||||
CryptEncodeObjectEx(
|
||||
X509_ASN_ENCODING,
|
||||
X509_PUBLIC_KEY_INFO,
|
||||
&context->pCertInfo->SubjectPublicKeyInfo,
|
||||
CRYPT_ENCODE_ALLOC_FLAG,
|
||||
nullptr,
|
||||
&encoded,
|
||||
&size
|
||||
) != FALSE,
|
||||
"real Crypt32 SPKI encoding"
|
||||
);
|
||||
Check(
|
||||
size == spki.Encoded.Size && !std::memcmp(encoded, spki.Encoded.Data, size),
|
||||
"real certificate SPKI equals Crypt32"
|
||||
);
|
||||
LocalFree(encoded);
|
||||
}
|
||||
|
||||
if (context)
|
||||
{
|
||||
CertFreeCertificateContext(context);
|
||||
}
|
||||
|
||||
CertCloseStore(store, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
TestOwnership()
|
||||
{
|
||||
DOMITO_CI_VALIDATION_RESULT result = {};
|
||||
UNICODE_STRING path = {8, 10, const_cast<PWCHAR>(L"\\foo")};
|
||||
|
||||
for (int fail : {0, 2, 3})
|
||||
{
|
||||
failure = fail;
|
||||
const auto r = refs, d = derefs, c = closed;
|
||||
result.PolicyInfo.ChainInfo = static_cast<MINCRYPT_CHAIN_INFO*>(Allocate(1));
|
||||
NTSTATUS s = DomitoValidateFilePathEx(&path, 0x20, 7, DomitoCiSignatureSelectionNative, &result);
|
||||
Check(NT_SUCCESS(s) == (fail == 0), "path/reference failure returned");
|
||||
Check(
|
||||
refs - r == derefs - d && closed - c == (fail == 3 ? 0u : 1u),
|
||||
"all references and owned path handles released"
|
||||
);
|
||||
DomitoFreeCiValidationResult(&result);
|
||||
Check(!live, "path no allocations leaked");
|
||||
}
|
||||
|
||||
failure = 0;
|
||||
validateStatus = STATUS_INVALID_IMAGE_HASH;
|
||||
const auto c = closed;
|
||||
Check(
|
||||
DomitoValidateFileHandleEx(&fileToken, 0x20, 7, DomitoCiSignatureSelectionNative, &result) == validateStatus,
|
||||
"CI failure retained"
|
||||
);
|
||||
Check(closed == c && result.PolicyInfo.ChainInfo, "borrowed handle open and diagnostics retained");
|
||||
DomitoFreeCiValidationResult(&result);
|
||||
validateStatus = STATUS_SUCCESS;
|
||||
|
||||
for (int fail : {0, 1, 4, 5})
|
||||
{
|
||||
failure = fail;
|
||||
PUNICODE_STRING name = reinterpret_cast<PUNICODE_STRING>(1);
|
||||
NTSTATUS s = DomitoGetProcessImageNameEx(&processToken, &name);
|
||||
Check(NT_SUCCESS(s) == (fail == 0), "PEPROCESS lookup failure");
|
||||
|
||||
if (NT_SUCCESS(s))
|
||||
{
|
||||
Check(
|
||||
name->Buffer == reinterpret_cast<PWCHAR>(name + 1) && name->Length == 8,
|
||||
"one contiguous process-name allocation"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Check(!name, "failed process name null");
|
||||
}
|
||||
|
||||
DomitoFreeProcessImageName(name);
|
||||
Check(!live, "process-name allocations released");
|
||||
}
|
||||
|
||||
for (int fail : {0, 1, 6, 7, 10, 11})
|
||||
{
|
||||
failure = fail;
|
||||
PUNICODE_STRING name = reinterpret_cast<PUNICODE_STRING>(1);
|
||||
NTSTATUS s = DomitoGetProcessImageName(123, &name);
|
||||
Check(NT_SUCCESS(s) == (fail == 0), "PID query failure");
|
||||
|
||||
if (!NT_SUCCESS(s))
|
||||
{
|
||||
Check(!name, "PID failure never leaves freed pointer");
|
||||
}
|
||||
|
||||
DomitoFreeProcessImageName(name);
|
||||
Check(!live, "PID query cleanup");
|
||||
}
|
||||
|
||||
failure = 0;
|
||||
MINCERT_BLOB blob = {1, reinterpret_cast<PUCHAR>(&fileToken)};
|
||||
|
||||
for (int fail : {0, 1, 8, 9})
|
||||
{
|
||||
failure = fail;
|
||||
UNICODE_STRING name = {};
|
||||
NTSTATUS s = DomitoGetCertificatePublisherName(&blob, &name);
|
||||
Check(NT_SUCCESS(s) == (fail == 0), "publisher error propagation");
|
||||
DomitoFreeCertificatePublisherName(&name);
|
||||
DomitoFreeCertificatePublisherName(&name);
|
||||
Check(!live && !name.Buffer, "publisher matching free including partial failure");
|
||||
}
|
||||
|
||||
failure = 0;
|
||||
ANSI_STRING module = RTL_CONSTANT_STRING("module");
|
||||
ANSI_STRING name = RTL_CONSTANT_STRING("export");
|
||||
PVOID address = reinterpret_cast<PVOID>(1);
|
||||
moduleStatus = STATUS_NOT_FOUND;
|
||||
Check(
|
||||
DomitoFindModuleExportedFunctionAddress(&module, &name, &address) == STATUS_NOT_FOUND && !address,
|
||||
"missing module nulls export"
|
||||
);
|
||||
moduleStatus = STATUS_SUCCESS;
|
||||
exportStatus = STATUS_NOT_SUPPORTED;
|
||||
address = reinterpret_cast<PVOID>(1);
|
||||
Check(
|
||||
DomitoFindModuleExportedFunctionAddress(&module, &name, &address) == STATUS_NOT_SUPPORTED && !address,
|
||||
"unsupported forwarder status preserved"
|
||||
);
|
||||
exportStatus = STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
TestChains()
|
||||
{
|
||||
alignas(8) UCHAR storage[512] = {};
|
||||
auto chain = reinterpret_cast<MINCRYPT_CHAIN_INFO*>(storage);
|
||||
chain->Size = sizeof(storage);
|
||||
chain->ChainElements = reinterpret_cast<MINCRYPT_CHAIN_ELEMENT*>(storage + 56);
|
||||
chain->NumberOfChainElement = 2;
|
||||
|
||||
for (ULONG i = 0; i < 2; ++i)
|
||||
{
|
||||
chain->ChainElements[i].Certificate = {1, storage + 400 + i};
|
||||
chain->ChainElements[i].HashSize = 32;
|
||||
}
|
||||
|
||||
MINCRYPT_POLICY_INFO policy = {};
|
||||
policy.Size = sizeof(policy);
|
||||
policy.ChainInfo = chain;
|
||||
DOMITO_CI_CHAIN_VIEW view = {};
|
||||
#ifdef DOMITO_TEST_OTHER_ARCH
|
||||
Check(
|
||||
DomitoGetCiChainView(&policy, DomitoCiChainLayoutObservedX64, &view) == STATUS_NOT_SUPPORTED && !view.Count,
|
||||
"non-x64 rejects unverified layout"
|
||||
);
|
||||
#else
|
||||
Check(
|
||||
NT_SUCCESS(DomitoGetCiChainView(&policy, DomitoCiChainLayoutObservedX64, &view)) && view.Count == 2,
|
||||
"minimal historical 56-byte chain prefix"
|
||||
);
|
||||
// The last certificate is an arbitrary byte, not a proven self-signed root.
|
||||
chain->ChainElements[1].Certificate.Data = storage + 512;
|
||||
Check(
|
||||
!NT_SUCCESS(DomitoGetCiChainView(&policy, DomitoCiChainLayoutObservedX64, &view)) && !view.Count,
|
||||
"child span past allocation rejected"
|
||||
);
|
||||
chain->ChainElements[1].Certificate.Data = storage + 401;
|
||||
chain->ChainElements[0].Subject = {reinterpret_cast<PCHAR>(storage + 500), 13};
|
||||
Check(
|
||||
!NT_SUCCESS(DomitoGetCiChainView(&policy, DomitoCiChainLayoutObservedX64, &view)),
|
||||
"malformed name span rejected"
|
||||
);
|
||||
chain->ChainElements[0].Subject = {};
|
||||
chain->NumberOfChainElement = 0xffffffff;
|
||||
Check(
|
||||
!NT_SUCCESS(DomitoGetCiChainView(&policy, DomitoCiChainLayoutObservedX64, &view)),
|
||||
"element multiplication overflow rejected"
|
||||
);
|
||||
chain->NumberOfChainElement = 2;
|
||||
chain->ChainElements[0].HashSize = 65;
|
||||
Check(
|
||||
!NT_SUCCESS(DomitoGetCiChainView(&policy, DomitoCiChainLayoutObservedX64, &view)),
|
||||
"oversize CI element hash rejected"
|
||||
);
|
||||
chain->ChainElements[0].HashSize = 32;
|
||||
|
||||
for (ULONG n = 0; n < 56; ++n)
|
||||
{
|
||||
chain->Size = n;
|
||||
Check(
|
||||
!NT_SUCCESS(DomitoGetCiChainView(&policy, DomitoCiChainLayoutObservedX64, &view)),
|
||||
"short historical header rejected"
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
TestCertificates();
|
||||
TestOwnership();
|
||||
TestChains();
|
||||
Check(!live, "all allocations released");
|
||||
std::printf("PASS: %u consumer-helper checks, no live allocations\n", checks);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,55 @@
|
||||
// Compile with the WDK for supported 64-bit targets. No driver is linked/loaded.
|
||||
#include <ntifs.h>
|
||||
#include "Domito.MinCrypt.h"
|
||||
|
||||
#if NTDDI_VERSION >= NTDDI_WIN10
|
||||
C_ASSERT(sizeof(MINCRYPT_POLICY_INFO) == 48);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
MINCRYPT_POLICY_INFO,
|
||||
VerificationStatus
|
||||
) == 4
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
MINCRYPT_POLICY_INFO,
|
||||
ChainInfo
|
||||
) == 16
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
MINCRYPT_POLICY_INFO,
|
||||
NotBefore
|
||||
) == 32
|
||||
);
|
||||
C_ASSERT(sizeof(MINCRYPT_CHAIN_INFO) == 72);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
MINCRYPT_CHAIN_INFO,
|
||||
ChainElements
|
||||
) == 40
|
||||
);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
MINCRYPT_CHAIN_INFO,
|
||||
AuthenticodeAttributes
|
||||
) == 56
|
||||
);
|
||||
#else
|
||||
C_ASSERT(sizeof(MINCRYPT_POLICY_INFO) == 32);
|
||||
C_ASSERT(sizeof(MINCRYPT_CHAIN_INFO) == 40);
|
||||
#endif
|
||||
C_ASSERT(sizeof(MINCRYPT_NAME_BLOB) == 16);
|
||||
C_ASSERT(sizeof(MINCRYPT_CHAIN_ELEMENT) == 120);
|
||||
C_ASSERT(
|
||||
FIELD_OFFSET(
|
||||
MINCRYPT_CHAIN_ELEMENT,
|
||||
Certificate
|
||||
) == 104
|
||||
);
|
||||
|
||||
#if defined(_M_AMD64)
|
||||
// These opt-in declarations establish observations only for the named x64 build.
|
||||
#include "Domito.MinCrypt.26100.h"
|
||||
#include "Domito.MinCrypt.Historical.h"
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Test the actual kernel probe body with mock CI; run in an x64 VS dev shell."""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
tests = Path(__file__).resolve().parent
|
||||
header = (tests.parent / "include/Domito.MinCrypt.h").read_text(encoding="utf-8-sig")
|
||||
names = ["MINCRYPT_MAX_HASH_LENGTH", "DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE"]
|
||||
constants = "\n".join(
|
||||
re.search(r"^#define\s+" + name + r"\s+[^\r\n]+", header, re.M)[0] for name in names
|
||||
)
|
||||
with tempfile.TemporaryDirectory(prefix="domito-ci-probe-") as temp:
|
||||
build = Path(temp)
|
||||
(build / "ci-probe-constants.inc").write_text(constants + "\n", encoding="utf-8")
|
||||
exe = build / "ci-probe-tests.exe"
|
||||
subprocess.run(
|
||||
[
|
||||
"cl",
|
||||
"/nologo",
|
||||
"/EHsc",
|
||||
"/W4",
|
||||
"/WX",
|
||||
"/std:c++17",
|
||||
"/I" + str(build),
|
||||
str(tests / "ci-file-object-probe-tests.cpp"),
|
||||
"/Fe" + str(exe),
|
||||
"/Fo" + str(build / "ci-probe-tests.obj"),
|
||||
],
|
||||
cwd=build,
|
||||
check=True,
|
||||
)
|
||||
subprocess.run([str(exe)], cwd=build, check=True)
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Compile the actual production CI wrapper body against deterministic CI doubles."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
header = (root / "include/Domito.h").read_text(encoding="utf-8-sig")
|
||||
start = header.index("#if (NTDDI_VERSION >= NTDDI_WIN10)\n// Native forwards")
|
||||
end = header.index("#define DOMITO_CI_SOURCE_EMBEDDED", start)
|
||||
constants_header = (root / "include/Domito.MinCrypt.h").read_text(encoding="utf-8-sig")
|
||||
constants = "\n".join(
|
||||
line
|
||||
for line in constants_header.splitlines()
|
||||
if line.startswith(
|
||||
(
|
||||
"#define DOMITO_CI_VALIDATE_FLAG_",
|
||||
"#define MINCRYPT_MAX_HASH_LENGTH",
|
||||
"#define MINCRYPT_SHA",
|
||||
)
|
||||
)
|
||||
)
|
||||
source = (root / "src/Domito.CodeIntegrity.Validation.cpp").read_text(encoding="utf-8-sig")
|
||||
source = source.replace('#include "Domito.Internal.h"', "")
|
||||
with tempfile.TemporaryDirectory(prefix="domito-ci-validation-") as temporary:
|
||||
directory = Path(temporary)
|
||||
(directory / "ci-validation-api.inc").write_text(
|
||||
constants + "\n" + header[start:end], encoding="utf-8"
|
||||
)
|
||||
(directory / "ci-validation-under-test.inc").write_text(source, encoding="utf-8")
|
||||
for branch in ["x64", "other"]:
|
||||
program = directory / (branch + ".exe")
|
||||
flags = ["/DDOMITO_TEST_OTHER_ARCH"] if branch == "other" else []
|
||||
subprocess.run(
|
||||
[
|
||||
"cl",
|
||||
"/nologo",
|
||||
"/EHsc",
|
||||
"/W4",
|
||||
"/WX",
|
||||
"/std:c++17",
|
||||
"/DNTDDI_VERSION=0x0A000008",
|
||||
"/DNTDDI_WIN10=0x0A000000",
|
||||
"/I" + str(directory),
|
||||
*flags,
|
||||
str(Path(__file__).with_name("ci-validation-tests.cpp")),
|
||||
"/Fe" + str(program),
|
||||
"/Fo" + str(directory / (branch + ".obj")),
|
||||
],
|
||||
cwd=directory,
|
||||
check=True,
|
||||
)
|
||||
subprocess.run([str(program)], check=True)
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Run production export and file-read helpers with deterministic user-mode doubles."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
source = (root / "src/Domito.cpp").read_text(encoding="utf-8-sig")
|
||||
export_start = source.index("// This helper handles directory RVAs")
|
||||
export_end = source.index("_Success_", source.index("DomitoFindExportedFunctionAddress(") + 1)
|
||||
read_start = source.rfind("_Success_", 0, source.index("DomitoReadFileAt("))
|
||||
read_end = source.index("_Success_", source.index("DomitoReadFile(") + 1)
|
||||
with tempfile.TemporaryDirectory(prefix="domito-common-tests-") as temp:
|
||||
build = Path(temp)
|
||||
(build / "common-under-test.inc").write_text(
|
||||
source[export_start:export_end] + "\n" + source[read_start:read_end], encoding="utf-8"
|
||||
)
|
||||
exe = build / "common-tests.exe"
|
||||
subprocess.run(
|
||||
[
|
||||
"cl",
|
||||
"/nologo",
|
||||
"/EHsc",
|
||||
"/W4",
|
||||
"/WX",
|
||||
"/std:c++17",
|
||||
"/I" + str(build),
|
||||
str(root / "tests/common-tests.cpp"),
|
||||
"/Fe" + str(exe),
|
||||
"/Fo" + str(build / "common-tests.obj"),
|
||||
],
|
||||
cwd=build,
|
||||
check=True,
|
||||
)
|
||||
subprocess.run([str(exe)], cwd=build, check=True)
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Production consumer helpers with real CNG/Crypt32 and deterministic kernel doubles."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
public = (root / "include/Domito.h").read_text(encoding="utf-8-sig")
|
||||
mincrypt = (root / "include/Domito.MinCrypt.h").read_text(encoding="utf-8-sig")
|
||||
structures = mincrypt[
|
||||
mincrypt.index("#include <pshpack8.h>") : mincrypt.index("#include <poppack.h>")
|
||||
+ len("#include <poppack.h>")
|
||||
]
|
||||
validation = public[
|
||||
public.index("#if (NTDDI_VERSION >= NTDDI_WIN10)\n// Native forwards") : public.index(
|
||||
"#define DOMITO_CI_SOURCE_EMBEDDED"
|
||||
)
|
||||
]
|
||||
helpers = (
|
||||
(root / "include/Domito.Consumer.h")
|
||||
.read_text(encoding="utf-8-sig")
|
||||
.replace('#include "Domito.h"', "")
|
||||
)
|
||||
algorithm = public[
|
||||
public.index("#if !defined(CALG_SHA1)") : public.index(
|
||||
"/*", public.index("DOMITO_CALG_TO_BCRYPT_ALGORITHM(")
|
||||
)
|
||||
]
|
||||
common = (root / "src/Domito.cpp").read_text(encoding="utf-8-sig")
|
||||
process_start = common.rfind("_Success_", 0, common.index("DomitoGetProcessImageName("))
|
||||
process = (
|
||||
common[process_start:]
|
||||
.replace('#pragma code_seg("PAGED")', "")
|
||||
.replace("#pragma code_seg()", "")
|
||||
)
|
||||
with tempfile.TemporaryDirectory(prefix="domito-consumer-tests-") as temp:
|
||||
build = Path(temp)
|
||||
(build / "consumer-api.inc").write_text(
|
||||
algorithm + "\n" + structures + "\n" + validation + "\n" + helpers
|
||||
)
|
||||
bodies = "\n".join(
|
||||
(root / ("src/" + name))
|
||||
.read_text(encoding="utf-8-sig")
|
||||
.replace('#include "Domito.Internal.h"', "")
|
||||
for name in ["Domito.Certificate.cpp", "Domito.Consumer.cpp"]
|
||||
)
|
||||
(build / "consumer-under-test.inc").write_text(bodies + "\n" + process)
|
||||
for branch in ["x64", "other"]:
|
||||
exe = build / (branch + ".exe")
|
||||
flags = ["/DDOMITO_TEST_OTHER_ARCH"] if branch == "other" else []
|
||||
subprocess.run(
|
||||
[
|
||||
"cl",
|
||||
"/nologo",
|
||||
"/EHsc",
|
||||
"/W4",
|
||||
"/WX",
|
||||
"/std:c++17",
|
||||
"/I" + str(build),
|
||||
*flags,
|
||||
str(root / "tests/consumer-helper-tests.cpp"),
|
||||
"/Fe" + str(exe),
|
||||
"/Fo" + str(build / (branch + ".obj")),
|
||||
"/link",
|
||||
"bcrypt.lib",
|
||||
"crypt32.lib",
|
||||
],
|
||||
cwd=build,
|
||||
check=True,
|
||||
)
|
||||
subprocess.run([str(exe)], check=True)
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Compile actual public headers and verify MSVC C4996 migration diagnostics."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--arch", choices=["x64", "ARM64"], default="x64")
|
||||
args = parser.parse_args()
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
vc = Path(os.environ["VCToolsInstallDir"])
|
||||
kit = Path(os.environ["WindowsSdkDir"]) / "Include" / os.environ["WindowsSDKVersion"].strip("\\/")
|
||||
compiler = vc / "bin/Hostx64" / args.arch.lower() / "cl.exe"
|
||||
architecture = ["/D_AMD64_", "/D_AMD64", "/Gz"] if args.arch == "x64" else ["/D_ARM64_", "/DARM64"]
|
||||
common = [
|
||||
"/nologo",
|
||||
"/c",
|
||||
"/kernel",
|
||||
"/W4",
|
||||
"/WX",
|
||||
"/we4996",
|
||||
"/D_WIN64",
|
||||
"/DWINNT=1",
|
||||
"/DNTDDI_VERSION=0x0A000008",
|
||||
"/D_WIN32_WINNT=0x0A00",
|
||||
*architecture,
|
||||
"/I" + str(vc / "include"),
|
||||
"/I" + str(kit / "km"),
|
||||
"/I" + str(kit / "shared"),
|
||||
"/I" + str(kit / "ucrt"),
|
||||
"/I" + str(root / "include"),
|
||||
]
|
||||
old = {
|
||||
"DomitoGetPortableExecutableDigestKind": "DomitoGetPortableExecutableDigestKindEx",
|
||||
"DomitoCalculatePortableExecutableDigest": "DomitoCalculatePortableExecutableDigestEx",
|
||||
"DomitoValidateFileLegacyMode": "DomitoValidateFileLegacyModeEx",
|
||||
"DomitoGetProcessImageName": "DomitoGetProcessImageNameEx",
|
||||
}
|
||||
modern = [
|
||||
*old.values(),
|
||||
"DomitoFreePortableExecutableDigest",
|
||||
"DomitoFreeProcessImageName",
|
||||
"DomitoValidateFileObjectEx",
|
||||
"DomitoValidateFileHandleEx",
|
||||
"DomitoValidateFilePathEx",
|
||||
"DomitoGetCiChainView",
|
||||
"DomitoGetCertificatePublisherName",
|
||||
"DomitoFindModuleBaseAddress",
|
||||
"DomitoFindExportedFunctionAddress",
|
||||
"DomitoReadFile",
|
||||
"DomitoReadFileAt",
|
||||
"CiValidateFileObject",
|
||||
]
|
||||
prefix = "#include <ntifs.h>\n#include <ntintsafe.h>\n#include <ntimage.h>\n#include <bcrypt.h>\n#include <Domito.h>\n"
|
||||
checks = 0
|
||||
with tempfile.TemporaryDirectory(prefix="domito-deprecation-") as temp:
|
||||
build = Path(temp)
|
||||
for language in ["c", "cpp"]:
|
||||
for index, (name, replacement) in enumerate([("modern", None), *old.items()]):
|
||||
source = build / (f"check-{index}." + language)
|
||||
refs = modern if replacement is None else [name]
|
||||
source.write_text(
|
||||
prefix
|
||||
+ "void Check(void) {\n"
|
||||
+ "".join(f" (void)&{ref};\n" for ref in refs)
|
||||
+ "}\n"
|
||||
)
|
||||
result = subprocess.run(
|
||||
[
|
||||
str(compiler),
|
||||
*common,
|
||||
"/TC" if language == "c" else "/TP",
|
||||
"/Fo" + str(build / "check.obj"),
|
||||
str(source),
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
output = result.stdout + result.stderr
|
||||
if replacement is None:
|
||||
assert result.returncode == 0, output
|
||||
else:
|
||||
assert (
|
||||
result.returncode != 0
|
||||
and "C4996" in output
|
||||
and name in output
|
||||
and replacement in output
|
||||
), output
|
||||
errors = [line for line in output.splitlines() if "error C" in line]
|
||||
assert errors and all("C4996" in line for line in errors), output
|
||||
checks += 1
|
||||
print(f"PASS: {checks} {args.arch} C/C++ public-header deprecation checks")
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Run in an x64 VS developer shell with Windows SDK and Python 3 installed.
|
||||
|
||||
Extracts the actual production functions without changing their bodies, replacing
|
||||
only kernel dependencies with the test harness. No driver is loaded. This tests
|
||||
parsing, hashing and cleanup, not real CI policy or kernel I/O synchronization.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--source-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--digest-source", type=Path)
|
||||
args = parser.parse_args()
|
||||
root = args.source_root
|
||||
digest = (args.digest_source or root / "src/Domito.CodeIntegrity.cpp").read_text(
|
||||
encoding="utf-8-sig"
|
||||
)
|
||||
digest = digest.replace('#include "Domito.Internal.h"', "").replace(
|
||||
'#include "Domito.MinCrypt.h"', ""
|
||||
)
|
||||
common = (root / "src/Domito.cpp").read_text(encoding="utf-8-sig")
|
||||
start = common.rfind("_Success_", 0, common.index("DomitoMemorySearchPattern("))
|
||||
end = common.index("_Success_", common.index("DomitoMemorySearchPattern("))
|
||||
search = common[start:end]
|
||||
public = (root / "include/Domito.h").read_text(encoding="utf-8-sig")
|
||||
start = public.index("#if !defined(CALG_SHA1)")
|
||||
end = public.index("/*", public.index("DOMITO_CALG_TO_BCRYPT_ALGORITHM("))
|
||||
algorithm = public[start:end]
|
||||
with tempfile.TemporaryDirectory(prefix="domito-digest-tests-") as temp:
|
||||
build = Path(temp)
|
||||
(build / "digest-under-test.inc").write_text(
|
||||
algorithm + "\n" + search + "\n" + digest, encoding="utf-8"
|
||||
)
|
||||
test_source = Path(__file__).with_name("digest-tests.cpp")
|
||||
subprocess.run(
|
||||
[
|
||||
"cl",
|
||||
"/nologo",
|
||||
"/EHsc",
|
||||
"/W4",
|
||||
"/WX",
|
||||
"/std:c++17",
|
||||
"/I" + str(build),
|
||||
str(test_source),
|
||||
"/Fe" + str(build / "digest-tests.exe"),
|
||||
"/Fo" + str(build / "digest-tests.obj"),
|
||||
"/link",
|
||||
"bcrypt.lib",
|
||||
"crypt32.lib",
|
||||
],
|
||||
cwd=build,
|
||||
check=True,
|
||||
)
|
||||
subprocess.run([str(build / "digest-tests.exe")], cwd=build, check=True)
|
||||
Reference in new issue
Block a user