Compare commits
9
Commits
3fbfe31d15
...
a491bbd3f8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a491bbd3f8 | ||
|
|
1f4d62ae95 | ||
|
|
28f284be8f | ||
|
|
40165ccbb2 | ||
|
|
66be109b8f | ||
|
|
cb23a31adb | ||
|
|
7aa23f50be | ||
|
|
1026c48324 | ||
|
|
195b9fccf3 |
No files matched your search
+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.
|
||||
@@ -55,7 +71,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 +91,14 @@ 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
|
||||
);
|
||||
+302
-249
@@ -22,16 +22,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// Warnings which disabled for compiling
|
||||
// Warnings which disabled for compiling
|
||||
#if _MSC_VER >= 1200
|
||||
#pragma warning(push)
|
||||
// nonstandard extension used : nameless struct/union
|
||||
#pragma warning(disable:4201)
|
||||
#pragma warning(disable : 4201)
|
||||
// 'struct_name' : structure was padded due to __declspec(align())
|
||||
#pragma warning(disable:4324)
|
||||
#pragma warning(disable : 4324)
|
||||
// 'enumeration': a forward declaration of an unscoped enumeration must have an
|
||||
// underlying type (int assumed)
|
||||
#pragma warning(disable:4471)
|
||||
#pragma warning(disable : 4471)
|
||||
#endif
|
||||
|
||||
EXTERN_C_START
|
||||
@@ -47,144 +47,144 @@ EXTERN_C_START
|
||||
//
|
||||
|
||||
// ALG_ID crackers
|
||||
#define GET_ALG_CLASS(x) (x & (7 << 13))
|
||||
#define GET_ALG_TYPE(x) (x & (15 << 9))
|
||||
#define GET_ALG_SID(x) (x & (511))
|
||||
#define GET_ALG_CLASS(x) (x & (7 << 13))
|
||||
#define GET_ALG_TYPE(x) (x & (15 << 9))
|
||||
#define GET_ALG_SID(x) (x & (511))
|
||||
|
||||
// Algorithm classes
|
||||
// certenrolld_begin -- ALG_CLASS_*
|
||||
#define ALG_CLASS_ANY (0)
|
||||
#define ALG_CLASS_SIGNATURE (1 << 13)
|
||||
#define ALG_CLASS_MSG_ENCRYPT (2 << 13)
|
||||
#define ALG_CLASS_DATA_ENCRYPT (3 << 13)
|
||||
#define ALG_CLASS_HASH (4 << 13)
|
||||
#define ALG_CLASS_KEY_EXCHANGE (5 << 13)
|
||||
#define ALG_CLASS_ALL (7 << 13)
|
||||
#define ALG_CLASS_ANY (0)
|
||||
#define ALG_CLASS_SIGNATURE (1 << 13)
|
||||
#define ALG_CLASS_MSG_ENCRYPT (2 << 13)
|
||||
#define ALG_CLASS_DATA_ENCRYPT (3 << 13)
|
||||
#define ALG_CLASS_HASH (4 << 13)
|
||||
#define ALG_CLASS_KEY_EXCHANGE (5 << 13)
|
||||
#define ALG_CLASS_ALL (7 << 13)
|
||||
// certenrolld_end
|
||||
|
||||
// Algorithm types
|
||||
#define ALG_TYPE_ANY (0)
|
||||
#define ALG_TYPE_DSS (1 << 9)
|
||||
#define ALG_TYPE_RSA (2 << 9)
|
||||
#define ALG_TYPE_BLOCK (3 << 9)
|
||||
#define ALG_TYPE_STREAM (4 << 9)
|
||||
#define ALG_TYPE_DH (5 << 9)
|
||||
#define ALG_TYPE_SECURECHANNEL (6 << 9)
|
||||
#define ALG_TYPE_ANY (0)
|
||||
#define ALG_TYPE_DSS (1 << 9)
|
||||
#define ALG_TYPE_RSA (2 << 9)
|
||||
#define ALG_TYPE_BLOCK (3 << 9)
|
||||
#define ALG_TYPE_STREAM (4 << 9)
|
||||
#define ALG_TYPE_DH (5 << 9)
|
||||
#define ALG_TYPE_SECURECHANNEL (6 << 9)
|
||||
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
||||
#define ALG_TYPE_ECDH (7 << 9)
|
||||
#define ALG_TYPE_ECDH (7 << 9)
|
||||
#endif //(NTDDI_VERSION >= NTDDI_VISTA)
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
#define ALG_TYPE_THIRDPARTY (8 << 9)
|
||||
#define ALG_TYPE_THIRDPARTY (8 << 9)
|
||||
#endif //(NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
|
||||
// Generic sub-ids
|
||||
#define ALG_SID_ANY (0)
|
||||
#define ALG_SID_ANY (0)
|
||||
|
||||
// Generic ThirdParty sub-ids
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
#define ALG_SID_THIRDPARTY_ANY (0)
|
||||
#define ALG_SID_THIRDPARTY_ANY (0)
|
||||
#endif //(NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
|
||||
// Some RSA sub-ids
|
||||
#define ALG_SID_RSA_ANY 0
|
||||
#define ALG_SID_RSA_PKCS 1
|
||||
#define ALG_SID_RSA_MSATWORK 2
|
||||
#define ALG_SID_RSA_ENTRUST 3
|
||||
#define ALG_SID_RSA_PGP 4
|
||||
#define ALG_SID_RSA_ANY 0
|
||||
#define ALG_SID_RSA_PKCS 1
|
||||
#define ALG_SID_RSA_MSATWORK 2
|
||||
#define ALG_SID_RSA_ENTRUST 3
|
||||
#define ALG_SID_RSA_PGP 4
|
||||
|
||||
// Some DSS sub-ids
|
||||
//
|
||||
#define ALG_SID_DSS_ANY 0
|
||||
#define ALG_SID_DSS_PKCS 1
|
||||
#define ALG_SID_DSS_DMS 2
|
||||
#define ALG_SID_DSS_ANY 0
|
||||
#define ALG_SID_DSS_PKCS 1
|
||||
#define ALG_SID_DSS_DMS 2
|
||||
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
||||
#define ALG_SID_ECDSA 3
|
||||
#define ALG_SID_ECDSA 3
|
||||
#endif //(NTDDI_VERSION >= NTDDI_VISTA)
|
||||
|
||||
// Block cipher sub ids
|
||||
// DES sub_ids
|
||||
#define ALG_SID_DES 1
|
||||
#define ALG_SID_3DES 3
|
||||
#define ALG_SID_DESX 4
|
||||
#define ALG_SID_IDEA 5
|
||||
#define ALG_SID_CAST 6
|
||||
#define ALG_SID_SAFERSK64 7
|
||||
#define ALG_SID_SAFERSK128 8
|
||||
#define ALG_SID_3DES_112 9
|
||||
#define ALG_SID_CYLINK_MEK 12
|
||||
#define ALG_SID_RC5 13
|
||||
#define ALG_SID_DES 1
|
||||
#define ALG_SID_3DES 3
|
||||
#define ALG_SID_DESX 4
|
||||
#define ALG_SID_IDEA 5
|
||||
#define ALG_SID_CAST 6
|
||||
#define ALG_SID_SAFERSK64 7
|
||||
#define ALG_SID_SAFERSK128 8
|
||||
#define ALG_SID_3DES_112 9
|
||||
#define ALG_SID_CYLINK_MEK 12
|
||||
#define ALG_SID_RC5 13
|
||||
#if (NTDDI_VERSION >= NTDDI_WINXP)
|
||||
#define ALG_SID_AES_128 14
|
||||
#define ALG_SID_AES_192 15
|
||||
#define ALG_SID_AES_256 16
|
||||
#define ALG_SID_AES 17
|
||||
#define ALG_SID_AES_128 14
|
||||
#define ALG_SID_AES_192 15
|
||||
#define ALG_SID_AES_256 16
|
||||
#define ALG_SID_AES 17
|
||||
#endif //(NTDDI_VERSION >= NTDDI_WINXP)
|
||||
|
||||
// Fortezza sub-ids
|
||||
#define ALG_SID_SKIPJACK 10
|
||||
#define ALG_SID_TEK 11
|
||||
#define ALG_SID_SKIPJACK 10
|
||||
#define ALG_SID_TEK 11
|
||||
|
||||
// KP_MODE
|
||||
#define CRYPT_MODE_CBCI 6 // ANSI CBC Interleaved
|
||||
#define CRYPT_MODE_CFBP 7 // ANSI CFB Pipelined
|
||||
#define CRYPT_MODE_OFBP 8 // ANSI OFB Pipelined
|
||||
#define CRYPT_MODE_CBCOFM 9 // ANSI CBC + OF Masking
|
||||
#define CRYPT_MODE_CBCOFMI 10 // ANSI CBC + OFM Interleaved
|
||||
#define CRYPT_MODE_CBCI 6 // ANSI CBC Interleaved
|
||||
#define CRYPT_MODE_CFBP 7 // ANSI CFB Pipelined
|
||||
#define CRYPT_MODE_OFBP 8 // ANSI OFB Pipelined
|
||||
#define CRYPT_MODE_CBCOFM 9 // ANSI CBC + OF Masking
|
||||
#define CRYPT_MODE_CBCOFMI 10 // ANSI CBC + OFM Interleaved
|
||||
|
||||
// RC2 sub-ids
|
||||
#define ALG_SID_RC2 2
|
||||
#define ALG_SID_RC2 2
|
||||
|
||||
// Stream cipher sub-ids
|
||||
#define ALG_SID_RC4 1
|
||||
#define ALG_SID_SEAL 2
|
||||
#define ALG_SID_RC4 1
|
||||
#define ALG_SID_SEAL 2
|
||||
|
||||
// Diffie-Hellman sub-ids
|
||||
#define ALG_SID_DH_SANDF 1
|
||||
#define ALG_SID_DH_EPHEM 2
|
||||
#define ALG_SID_AGREED_KEY_ANY 3
|
||||
#define ALG_SID_KEA 4
|
||||
#define ALG_SID_DH_SANDF 1
|
||||
#define ALG_SID_DH_EPHEM 2
|
||||
#define ALG_SID_AGREED_KEY_ANY 3
|
||||
#define ALG_SID_KEA 4
|
||||
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
||||
#define ALG_SID_ECDH 5
|
||||
#define ALG_SID_ECDH_EPHEM 6
|
||||
#define ALG_SID_ECDH 5
|
||||
#define ALG_SID_ECDH_EPHEM 6
|
||||
#endif //(NTDDI_VERSION >= NTDDI_VISTA)
|
||||
|
||||
// Hash sub ids
|
||||
#define ALG_SID_MD2 1
|
||||
#define ALG_SID_MD4 2
|
||||
#define ALG_SID_MD5 3
|
||||
#define ALG_SID_SHA 4
|
||||
#define ALG_SID_SHA1 4
|
||||
#define ALG_SID_MAC 5
|
||||
#define ALG_SID_RIPEMD 6
|
||||
#define ALG_SID_RIPEMD160 7
|
||||
#define ALG_SID_SSL3SHAMD5 8
|
||||
#define ALG_SID_HMAC 9
|
||||
#define ALG_SID_TLS1PRF 10
|
||||
#define ALG_SID_MD2 1
|
||||
#define ALG_SID_MD4 2
|
||||
#define ALG_SID_MD5 3
|
||||
#define ALG_SID_SHA 4
|
||||
#define ALG_SID_SHA1 4
|
||||
#define ALG_SID_MAC 5
|
||||
#define ALG_SID_RIPEMD 6
|
||||
#define ALG_SID_RIPEMD160 7
|
||||
#define ALG_SID_SSL3SHAMD5 8
|
||||
#define ALG_SID_HMAC 9
|
||||
#define ALG_SID_TLS1PRF 10
|
||||
#if (NTDDI_VERSION >= NTDDI_WINXP)
|
||||
#define ALG_SID_HASH_REPLACE_OWF 11
|
||||
#define ALG_SID_HASH_REPLACE_OWF 11
|
||||
#endif //(NTDDI_VERSION >= NTDDI_WINXP)
|
||||
#if (NTDDI_VERSION > NTDDI_WINXPSP2)
|
||||
#define ALG_SID_SHA_256 12
|
||||
#define ALG_SID_SHA_384 13
|
||||
#define ALG_SID_SHA_512 14
|
||||
#define ALG_SID_SHA_256 12
|
||||
#define ALG_SID_SHA_384 13
|
||||
#define ALG_SID_SHA_512 14
|
||||
#endif //(NTDDI_VERSION > NTDDI_WINXPSP2)
|
||||
|
||||
// secure channel sub ids
|
||||
#define ALG_SID_SSL3_MASTER 1
|
||||
#define ALG_SID_SCHANNEL_MASTER_HASH 2
|
||||
#define ALG_SID_SCHANNEL_MAC_KEY 3
|
||||
#define ALG_SID_PCT1_MASTER 4
|
||||
#define ALG_SID_SSL2_MASTER 5
|
||||
#define ALG_SID_TLS1_MASTER 6
|
||||
#define ALG_SID_SCHANNEL_ENC_KEY 7
|
||||
#define ALG_SID_SSL3_MASTER 1
|
||||
#define ALG_SID_SCHANNEL_MASTER_HASH 2
|
||||
#define ALG_SID_SCHANNEL_MAC_KEY 3
|
||||
#define ALG_SID_PCT1_MASTER 4
|
||||
#define ALG_SID_SSL2_MASTER 5
|
||||
#define ALG_SID_TLS1_MASTER 6
|
||||
#define ALG_SID_SCHANNEL_ENC_KEY 7
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
||||
// misc ECC sub ids
|
||||
#define ALG_SID_ECMQV 1
|
||||
#define ALG_SID_ECMQV 1
|
||||
#endif //(NTDDI_VERSION >= NTDDI_VISTA)
|
||||
|
||||
// Our silly example sub-id
|
||||
#define ALG_SID_EXAMPLE 80
|
||||
#define ALG_SID_EXAMPLE 80
|
||||
|
||||
// certenrolls_begin -- PROV_ENUMALGS_EX
|
||||
#ifndef ALGIDDEF
|
||||
@@ -194,132 +194,139 @@ typedef unsigned int ALG_ID;
|
||||
// certenrolls_end
|
||||
|
||||
// algorithm identifier definitions
|
||||
#define CALG_MD2 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD2)
|
||||
#define CALG_MD4 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD4)
|
||||
#define CALG_MD5 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5)
|
||||
#define CALG_SHA (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA)
|
||||
#define CALG_SHA1 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA1)
|
||||
#define CALG_MAC (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MAC) // Deprecated. Don't use.
|
||||
#define CALG_RSA_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_RSA | ALG_SID_RSA_ANY)
|
||||
#define CALG_DSS_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_DSS_ANY)
|
||||
#define CALG_MD2 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD2)
|
||||
#define CALG_MD4 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD4)
|
||||
#define CALG_MD5 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5)
|
||||
#define CALG_SHA (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA)
|
||||
#define CALG_SHA1 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA1)
|
||||
#define CALG_MAC (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MAC) // Deprecated. Don't use.
|
||||
#define CALG_RSA_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_RSA | ALG_SID_RSA_ANY)
|
||||
#define CALG_DSS_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_DSS_ANY)
|
||||
#if (NTDDI_VERSION >= NTDDI_WINXP)
|
||||
#define CALG_NO_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_ANY | ALG_SID_ANY)
|
||||
#define CALG_NO_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_ANY | ALG_SID_ANY)
|
||||
#endif //(NTDDI_VERSION >= NTDDI_WINXP)
|
||||
#define CALG_RSA_KEYX (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_RSA|ALG_SID_RSA_ANY)
|
||||
#define CALG_DES (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_DES)
|
||||
#define CALG_3DES_112 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_3DES_112)
|
||||
#define CALG_3DES (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_3DES)
|
||||
#define CALG_DESX (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_DESX)
|
||||
#define CALG_RC2 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC2)
|
||||
#define CALG_RC4 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_STREAM|ALG_SID_RC4)
|
||||
#define CALG_SEAL (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_STREAM|ALG_SID_SEAL)
|
||||
#define CALG_DH_SF (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_DH_SANDF)
|
||||
#define CALG_DH_EPHEM (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_DH_EPHEM)
|
||||
#define CALG_AGREEDKEY_ANY (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_AGREED_KEY_ANY)
|
||||
#define CALG_KEA_KEYX (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_KEA)
|
||||
#define CALG_HUGHES_MD5 (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_ANY|ALG_SID_MD5)
|
||||
#define CALG_SKIPJACK (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_SKIPJACK)
|
||||
#define CALG_TEK (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_TEK)
|
||||
#define CALG_CYLINK_MEK (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_CYLINK_MEK) // Deprecated. Do not use
|
||||
#define CALG_SSL3_SHAMD5 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SSL3SHAMD5)
|
||||
#define CALG_SSL3_MASTER (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SSL3_MASTER)
|
||||
#define CALG_SCHANNEL_MASTER_HASH (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_MASTER_HASH)
|
||||
#define CALG_SCHANNEL_MAC_KEY (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_MAC_KEY)
|
||||
#define CALG_SCHANNEL_ENC_KEY (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_ENC_KEY)
|
||||
#define CALG_PCT1_MASTER (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_PCT1_MASTER)
|
||||
#define CALG_SSL2_MASTER (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SSL2_MASTER)
|
||||
#define CALG_TLS1_MASTER (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_TLS1_MASTER)
|
||||
#define CALG_RC5 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC5)
|
||||
#define CALG_HMAC (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HMAC)
|
||||
#define CALG_TLS1PRF (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_TLS1PRF)
|
||||
#define CALG_RSA_KEYX (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_RSA | ALG_SID_RSA_ANY)
|
||||
#define CALG_DES (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DES)
|
||||
#define CALG_3DES_112 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_3DES_112)
|
||||
#define CALG_3DES (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_3DES)
|
||||
#define CALG_DESX (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DESX)
|
||||
#define CALG_RC2 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_RC2)
|
||||
#define CALG_RC4 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_RC4)
|
||||
#define CALG_SEAL (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_SEAL)
|
||||
#define CALG_DH_SF (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_DH_SANDF)
|
||||
#define CALG_DH_EPHEM (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_DH_EPHEM)
|
||||
#define CALG_AGREEDKEY_ANY (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_AGREED_KEY_ANY)
|
||||
#define CALG_KEA_KEYX (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_KEA)
|
||||
#define CALG_HUGHES_MD5 (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ANY | ALG_SID_MD5)
|
||||
#define CALG_SKIPJACK (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_SKIPJACK)
|
||||
#define CALG_TEK (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_TEK)
|
||||
#define CALG_CYLINK_MEK (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_CYLINK_MEK) // Deprecated. Do not use
|
||||
#define CALG_SSL3_SHAMD5 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SSL3SHAMD5)
|
||||
#define CALG_SSL3_MASTER (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SSL3_MASTER)
|
||||
#define CALG_SCHANNEL_MASTER_HASH (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SCHANNEL_MASTER_HASH)
|
||||
#define CALG_SCHANNEL_MAC_KEY (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SCHANNEL_MAC_KEY)
|
||||
#define CALG_SCHANNEL_ENC_KEY (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SCHANNEL_ENC_KEY)
|
||||
#define CALG_PCT1_MASTER (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_PCT1_MASTER)
|
||||
#define CALG_SSL2_MASTER (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SSL2_MASTER)
|
||||
#define CALG_TLS1_MASTER (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_TLS1_MASTER)
|
||||
#define CALG_RC5 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_RC5)
|
||||
#define CALG_HMAC (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HMAC)
|
||||
#define CALG_TLS1PRF (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_TLS1PRF)
|
||||
#if (NTDDI_VERSION >= NTDDI_WINXP)
|
||||
#define CALG_HASH_REPLACE_OWF (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HASH_REPLACE_OWF)
|
||||
#define CALG_AES_128 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_128)
|
||||
#define CALG_AES_192 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_192)
|
||||
#define CALG_AES_256 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_256)
|
||||
#define CALG_AES (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES)
|
||||
#define CALG_HASH_REPLACE_OWF (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HASH_REPLACE_OWF)
|
||||
#define CALG_AES_128 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_128)
|
||||
#define CALG_AES_192 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_192)
|
||||
#define CALG_AES_256 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256)
|
||||
#define CALG_AES (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES)
|
||||
#endif //(NTDDI_VERSION >= NTDDI_WINXP)
|
||||
#if (NTDDI_VERSION > NTDDI_WINXPSP2)
|
||||
#define CALG_SHA_256 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
|
||||
#define CALG_SHA_384 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384)
|
||||
#define CALG_SHA_512 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512)
|
||||
#define CALG_SHA_256 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
|
||||
#define CALG_SHA_384 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384)
|
||||
#define CALG_SHA_512 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512)
|
||||
#endif //(NTDDI_VERSION > NTDDI_WINXPSP2)
|
||||
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
||||
#define CALG_ECDH (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_ECDH)
|
||||
#define CALG_ECDH_EPHEM (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ECDH | ALG_SID_ECDH_EPHEM)
|
||||
#define CALG_ECMQV (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ANY | ALG_SID_ECMQV)
|
||||
#define CALG_ECDSA (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_ECDSA)
|
||||
#define CALG_NULLCIPHER (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_ANY | 0)
|
||||
#define CALG_ECDH (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_ECDH)
|
||||
#define CALG_ECDH_EPHEM (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ECDH | ALG_SID_ECDH_EPHEM)
|
||||
#define CALG_ECMQV (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ANY | ALG_SID_ECMQV)
|
||||
#define CALG_ECDSA (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_ECDSA)
|
||||
#define CALG_NULLCIPHER (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_ANY | 0)
|
||||
#endif //(NTDDI_VERSION >= NTDDI_VISTA)
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
#define CALG_THIRDPARTY_KEY_EXCHANGE (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
|
||||
#define CALG_THIRDPARTY_SIGNATURE (ALG_CLASS_SIGNATURE | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
|
||||
#define CALG_THIRDPARTY_CIPHER (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
|
||||
#define CALG_THIRDPARTY_HASH (ALG_CLASS_HASH | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
|
||||
#define CALG_THIRDPARTY_KEY_EXCHANGE (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
|
||||
#define CALG_THIRDPARTY_SIGNATURE (ALG_CLASS_SIGNATURE | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
|
||||
#define CALG_THIRDPARTY_CIPHER (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
|
||||
#define CALG_THIRDPARTY_HASH (ALG_CLASS_HASH | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
|
||||
#endif //(NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
|
||||
#include <pshpack8.h>
|
||||
|
||||
typedef struct _MINCRYPTOAPI_BLOB
|
||||
{
|
||||
UINT32 Size;
|
||||
_Field_size_bytes_(Size) UINT8* Data;
|
||||
_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;
|
||||
_Field_size_bytes_(Size) PCHAR Data;
|
||||
USHORT Size;
|
||||
|
||||
}MINCRYPT_NAME_BLOB, * PMINCRYPT_NAME_BLOB;
|
||||
} MINCRYPT_NAME_BLOB, *PMINCRYPT_NAME_BLOB;
|
||||
|
||||
#define MINCRYPT_MAX_HASH_LENGTH (64)
|
||||
#define MINCRYPT_SHA1_LENGTH (160/8)
|
||||
#define MINCRYPT_SHA256_LENGTH (256/8)
|
||||
#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
|
||||
{
|
||||
ALG_ID HashAlgId; // CALG_SHA1, CALG_SHA_256
|
||||
UINT32 HashSize; // SHA1(160bit/8), SHA256(256bit/8)
|
||||
UINT8 Hash[MINCRYPT_MAX_HASH_LENGTH];
|
||||
ALG_ID HashAlgId; // CALG_SHA1, CALG_SHA_256
|
||||
UINT32 HashSize; // SHA1(160bit/8), SHA256(256bit/8)
|
||||
UINT8 Hash[MINCRYPT_MAX_HASH_LENGTH];
|
||||
|
||||
MINCRYPT_NAME_BLOB Subject;
|
||||
MINCRYPT_NAME_BLOB Issuer;
|
||||
|
||||
MINCERT_BLOB Certificate;
|
||||
MINCERT_BLOB Certificate;
|
||||
|
||||
}MINCRYPT_CHAIN_ELEMENT, * PMINCRYPT_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;
|
||||
MINCERT_BLOB* PublicKeys;
|
||||
UINT32 NumberOfPublicKeys;
|
||||
|
||||
MINCERT_BLOB* EKUs;
|
||||
UINT32 NumberOfEKUs;
|
||||
MINCERT_BLOB* EKUs;
|
||||
UINT32 NumberOfEKUs;
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
|
||||
PMINCRYPT_CHAIN_ELEMENT ChainElements;
|
||||
UINT32 NumberOfChainElement;
|
||||
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
|
||||
|
||||
@@ -333,60 +340,66 @@ typedef struct _MINCRYPT_CHAIN_INFO
|
||||
|
||||
// ChainElements[NumberOfChainElement]
|
||||
|
||||
}MINCRYPT_CHAIN_INFO, * PMINCRYPT_CHAIN_INFO;
|
||||
} MINCRYPT_CHAIN_INFO, *PMINCRYPT_CHAIN_INFO;
|
||||
|
||||
typedef struct _MINCRYPT_POLICY_INFO
|
||||
{
|
||||
UINT32 Size;
|
||||
UINT32 VerificationStatus;
|
||||
UINT32 PolicyBits;
|
||||
UINT32 Size;
|
||||
NTSTATUS VerificationStatus; // Check API return status separately.
|
||||
UINT32 PolicyBits;
|
||||
|
||||
MINCRYPT_CHAIN_INFO* ChainInfo;
|
||||
|
||||
LARGE_INTEGER RevocationTime; // When was the certificate revoked (if applicable)
|
||||
LARGE_INTEGER RevocationTime; // When was the certificate revoked (if applicable)
|
||||
|
||||
#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_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_ PVOID SecurityDirectory,
|
||||
_In_ UINT32 SizeOfSecurityDirectory,
|
||||
_In_reads_bytes_(HashSize) PVOID Hash,
|
||||
_In_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_reads_bytes_(SizeOfSecurityDirectory) PVOID SecurityDirectory,
|
||||
_In_ UINT32 SizeOfSecurityDirectory,
|
||||
_Out_ MINCRYPT_POLICY_INFO* PolicyInfo,
|
||||
_Out_ LARGE_INTEGER* SigningTime,
|
||||
_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_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_ BOOLEAN IsReloadCatalogs,
|
||||
_In_ UINT32 Always0,
|
||||
_In_ UINT32 Always2007F,
|
||||
_In_reads_bytes_(HashSize) PVOID Hash,
|
||||
_In_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_ BOOLEAN ReloadCatalogs,
|
||||
_In_ UINT32 RequireTrustedCatalog,
|
||||
_In_ UINT32 AcceptedCatalogPolicyBits,
|
||||
_Out_ MINCRYPT_POLICY_INFO* PolicyInfo,
|
||||
_Out_opt_ UNICODE_STRING* CatalogName,
|
||||
_Out_ LARGE_INTEGER* SigningTime,
|
||||
_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,32 +574,37 @@ 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_ FILE_OBJECT* FileObject,
|
||||
_In_ UINT32 ValidationFlags,
|
||||
_In_ UINT32 RequestedSigningLevel,
|
||||
_Out_ MINCRYPT_POLICY_INFO* PolicyInfo,
|
||||
_Out_ MINCRYPT_POLICY_INFO* TimeStampPolicyInfo,
|
||||
_Out_ LARGE_INTEGER* SigningTime,
|
||||
_Out_ UINT8* Hash,
|
||||
_Inout_ UINT32* HashSize,
|
||||
_Out_ ALG_ID* HashAlgId
|
||||
_Out_ LARGE_INTEGER* SigningTime,
|
||||
_Out_writes_bytes_(*HashSize) UINT8* Hash,
|
||||
_Inout_ UINT32* HashSize,
|
||||
_Out_ ALG_ID* HashAlgId
|
||||
);
|
||||
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
|
||||
+222
-44
@@ -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
|
||||
);
|
||||
|
||||
|
||||
/* ___ _ _
|
||||
* / __|_ _ _ _ _ __| |_ ___ __ _ _ _ __ _ _ __| |_ _ _
|
||||
* | (__| '_| || | '_ \ _/ _ \/ _` | '_/ _` | '_ \ ' \ || |
|
||||
@@ -97,9 +88,9 @@ DomitoSetMemoryFunctions(
|
||||
* |__/|_| |___/ |_| |__/
|
||||
*/
|
||||
|
||||
//
|
||||
// This structure encapsulates a signature used in verifying executable files.
|
||||
//
|
||||
//
|
||||
// This structure encapsulates a signature used in verifying executable files.
|
||||
//
|
||||
#if !defined(WIN_CERTIFICATE)
|
||||
typedef struct _WIN_CERTIFICATE
|
||||
{
|
||||
@@ -115,13 +106,13 @@ typedef struct _WIN_CERTIFICATE
|
||||
//
|
||||
|
||||
#if !defined(WIN_CERT_TYPE_X509)
|
||||
#define WIN_CERT_TYPE_X509 (0x0001) // The bCertificate member contains an X.509 certificate.
|
||||
#define WIN_CERT_TYPE_X509 (0x0001) // The bCertificate member contains an X.509 certificate.
|
||||
#endif
|
||||
#if !defined(WIN_CERT_TYPE_PKCS_SIGNED_DATA)
|
||||
#define WIN_CERT_TYPE_PKCS_SIGNED_DATA (0x0002) // The bCertificate member contains a PKCS SignedData structure.
|
||||
#define WIN_CERT_TYPE_PKCS_SIGNED_DATA (0x0002) // The bCertificate member contains a PKCS SignedData structure.
|
||||
#endif
|
||||
#if !defined(WIN_CERT_TYPE_PKCS1_SIGN)
|
||||
#define WIN_CERT_TYPE_PKCS1_SIGN (0x0009) // The bCertificate member contains PKCS1_MODULE_SIGN fields.
|
||||
#define WIN_CERT_TYPE_PKCS1_SIGN (0x0009) // The bCertificate member contains PKCS1_MODULE_SIGN fields.
|
||||
#endif
|
||||
|
||||
#if !defined(CALG_SHA1)
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
/* __ __ _
|
||||
* | \/ (_)___ __
|
||||
* | |\/| | (_-</ _|_
|
||||
@@ -237,9 +382,9 @@ 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_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
@@ -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
|
||||
+824
-274
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
|
||||
+304
-156
@@ -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,19 +51,27 @@ _IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
void
|
||||
DomitoShutdown()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
DomitoFindModuleBaseAddress(
|
||||
_In_ PANSI_STRING ModuleName,
|
||||
_Inout_opt_ PVOID * ModuleBase
|
||||
_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)
|
||||
@@ -136,59 +154,146 @@ NTSTATUS
|
||||
DomitoFindExportedFunctionAddress(
|
||||
_In_ PVOID ModuleBase,
|
||||
_In_ PANSI_STRING FunctionName,
|
||||
_Inout_opt_ PVOID * FunctionAddress
|
||||
_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);
|
||||
|
||||
const ULONG functionRva = functionAddresses[i];
|
||||
const PVOID functionAddress = (PVOID)((ULONG_PTR)ModuleBase + functionRva);
|
||||
|
||||
RtlInitAnsiString(¤tFunctionName, functionName);
|
||||
|
||||
if (0 == RtlCompareString(FunctionName, ¤tFunctionName, TRUE))
|
||||
{
|
||||
if (FunctionAddress)
|
||||
{
|
||||
status = STATUS_SUCCESS;
|
||||
*FunctionAddress = functionAddress;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
return status;
|
||||
// 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)
|
||||
);
|
||||
|
||||
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)
|
||||
{
|
||||
*FunctionAddress = base + functionRva;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
@@ -201,7 +306,7 @@ DomitoMemorySearchPattern(
|
||||
_In_ SIZE_T puLen,
|
||||
_In_ PVOID pcBase,
|
||||
_In_ SIZE_T puSize,
|
||||
_Outptr_result_maybenull_ PVOID * ppMatch
|
||||
_Outptr_result_maybenull_ PVOID* ppMatch
|
||||
)
|
||||
{
|
||||
ASSERT(ppMatch != NULL && pcPattern != NULL && pcBase != NULL);
|
||||
@@ -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;
|
||||
}
|
||||
if (size < sizeof(UNICODE_STRING))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto image = static_cast<PUNICODE_STRING>(G_Memory.Allocate(size));
|
||||
|
||||
/* Query the actual size of the process path */
|
||||
status = G_Common.ZwQueryInformationProcess(
|
||||
hProcess,
|
||||
ProcessImageFileName,
|
||||
NULL, // buffer
|
||||
0, // buffer size
|
||||
&returnedLength
|
||||
);
|
||||
if (!image)
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
else
|
||||
{
|
||||
ULONG returned = 0;
|
||||
status = G_Common.ZwQueryInformationProcess(process, ProcessImageFileName, image, size, &returned);
|
||||
|
||||
if (STATUS_INFO_LENGTH_MISMATCH != status)
|
||||
{
|
||||
goto cleanUp;
|
||||
}
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
const ULONG_PTR base = reinterpret_cast<ULONG_PTR>(image);
|
||||
const ULONG_PTR buffer = reinterpret_cast<ULONG_PTR>(image->Buffer);
|
||||
|
||||
*ProcessImageName = (PUNICODE_STRING)G_Memory.Allocate(returnedLength);
|
||||
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 (*ProcessImageName == NULL)
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto cleanUp;
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
*ProcessImageName = image;
|
||||
}
|
||||
else
|
||||
{
|
||||
G_Memory.Free(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Retrieve the process path from the handle to the process */
|
||||
if (!NT_SUCCESS(status = G_Common.ZwQueryInformationProcess(
|
||||
hProcess,
|
||||
ProcessImageFileName,
|
||||
*ProcessImageName,
|
||||
returnedLength,
|
||||
&returnedLength
|
||||
)))
|
||||
{
|
||||
G_Memory.Free(*ProcessImageName);
|
||||
}
|
||||
|
||||
cleanUp:
|
||||
|
||||
if (hProcess)
|
||||
else if (NT_SUCCESS(status))
|
||||
{
|
||||
ZwClose(hProcess);
|
||||
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