Add reusable certificate and file validation helpers
Fix process-name ownership and module lookup outputs, add explicit SPKI hashing and bounded chain access, and improve WDM integration. Deprecate superseded entry points with migration guidance and align branch code and tests with the library style.
This commit is contained in:
1 parent
1f4d62ae95
commit
a491bbd3f8
32 files changed
+5390
-1122
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 />
|
||||
|
||||
@@ -38,6 +38,14 @@ 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.
|
||||
@@ -63,7 +71,11 @@ the expected signer/trust policy separately from a successful CI return status.
|
||||
|
||||
### 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 -->
|
||||
|
||||
@@ -55,8 +55,10 @@ NTSTATUS status = DomitoValidateFileObjectEx(
|
||||
DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE,
|
||||
0,
|
||||
DomitoCiSignatureSelectionFirstSuccessfulExperimental,
|
||||
&result);
|
||||
if (NT_SUCCESS(status)) {
|
||||
&result
|
||||
);
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
// Apply your expected-certificate/trust policy to result.PolicyInfo.
|
||||
// result.SigningTime belongs to that selected verification result.
|
||||
}
|
||||
@@ -83,9 +85,9 @@ successful catalog verification.
|
||||
|
||||
```c
|
||||
DOMITO_CI_VALIDATION_RESULT result = {0};
|
||||
NTSTATUS status = DomitoValidatePortableExecutableEmbeddedSignature(
|
||||
stablePeBytes, peSize, CALG_SHA_256, &result);
|
||||
if (NT_SUCCESS(status)) {
|
||||
NTSTATUS status = DomitoValidatePortableExecutableEmbeddedSignature(stablePeBytes, peSize, CALG_SHA_256, &result);
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
// Enforce the expected certificate and intended trust policy here.
|
||||
}
|
||||
DomitoFreeCiValidationResult(&result);
|
||||
|
||||
@@ -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.
|
||||
@@ -102,7 +102,26 @@ and compilation do not close the remaining work:
|
||||
|
||||
## Checks
|
||||
|
||||
Run the four host runners in `tests/README.md`. Kernel compilation also covers
|
||||
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,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
|
||||
+122
-67
@@ -13,18 +13,22 @@
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 8)
|
||||
typedef struct _DOMITO_CI26100_BLOB {
|
||||
|
||||
typedef struct _DOMITO_CI26100_BLOB
|
||||
{
|
||||
ULONG Size;
|
||||
UCHAR *Data;
|
||||
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. */
|
||||
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 {
|
||||
typedef struct _DOMITO_CI26100_CHAIN_ELEMENT
|
||||
{
|
||||
ULONG HashAlgId;
|
||||
ULONG HashSize;
|
||||
UCHAR Hash[64];
|
||||
@@ -33,72 +37,121 @@ typedef struct _DOMITO_CI26100_CHAIN_ELEMENT {
|
||||
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;
|
||||
typedef struct _DOMITO_CI26100_CHAIN_INFO
|
||||
{
|
||||
ULONG Size; /* Entire allocation, including trailing data. */
|
||||
DOMITO_CI26100_BLOB* PublicKeys;
|
||||
ULONG NumberOfPublicKeys;
|
||||
DOMITO_CI26100_BLOB *EKUs;
|
||||
DOMITO_CI26100_BLOB* EKUs;
|
||||
ULONG NumberOfEKUs;
|
||||
DOMITO_CI26100_CHAIN_ELEMENT *ChainElements;
|
||||
DOMITO_CI26100_CHAIN_ELEMENT* ChainElements;
|
||||
ULONG NumberOfChainElements;
|
||||
ULONG RootId; /* Opaque classification, NOT policy bits. */
|
||||
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 {
|
||||
typedef struct _DOMITO_CI26100_POLICY_INFO
|
||||
{
|
||||
ULONG Size;
|
||||
NTSTATUS VerificationStatus; /* Does not replace the API return status. */
|
||||
ULONG PolicyBits;
|
||||
DOMITO_CI26100_CHAIN_INFO *ChainInfo;
|
||||
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(
|
||||
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(
|
||||
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(
|
||||
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 VOID(NTAPI* DOMITO_CI26100_FREE_POLICY_INFO)(DOMITO_CI26100_POLICY_INFO* PolicyInfo);
|
||||
|
||||
typedef NTSTATUS (NTAPI *DOMITO_CI26100_VALIDATE_FILE_OBJECT)(
|
||||
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);
|
||||
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)(
|
||||
typedef NTSTATUS(NTAPI* DOMITO_CI26100_CHECK_SIGNED_FILE)(
|
||||
PVOID Hash,
|
||||
ULONG HashSize,
|
||||
ULONG HashAlgId,
|
||||
PVOID WinCertificate, /* WIN_CERTIFICATE header, not bCertificate. */
|
||||
PVOID WinCertificate, /* WIN_CERTIFICATE header, not bCertificate. */
|
||||
ULONG SizeOfSecurityDirectory,
|
||||
DOMITO_CI26100_POLICY_INFO *PolicyInfo,
|
||||
LARGE_INTEGER *SigningTime,
|
||||
DOMITO_CI26100_POLICY_INFO *TimeStampPolicyInfo);
|
||||
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
|
||||
@@ -106,49 +159,51 @@ typedef NTSTATUS (NTAPI *DOMITO_CI26100_CHECK_SIGNED_FILE)(
|
||||
* 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)(
|
||||
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);
|
||||
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,
|
||||
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. */
|
||||
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
|
||||
#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_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;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#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
|
||||
@@ -57,17 +58,63 @@ typedef struct _DOMITO_CI14393_CHAIN_INFO
|
||||
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(
|
||||
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(
|
||||
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);
|
||||
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
|
||||
);
|
||||
+202
-220
@@ -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,133 +194,127 @@ 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_SHA384_LENGTH (384/8)
|
||||
#define MINCRYPT_SHA512_LENGTH (512/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; // Observed 26100 x64: total allocation bytes, including trailing data.
|
||||
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;
|
||||
|
||||
// REQUIRES a confirmed runtime layout (10586 and later sampled profiles).
|
||||
// Sampled 10240 has only 56 header bytes: +0x38 begins variable payload.
|
||||
@@ -346,30 +340,29 @@ 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;
|
||||
NTSTATUS VerificationStatus; // Check API return status separately.
|
||||
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; // Observed: signer certificate validity start.
|
||||
LARGE_INTEGER NotAfter; // Observed: signer certificate validity end.
|
||||
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>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Release a known initialized policy's owned ChainInfo allocation and reset it.
|
||||
* Zero-initialize policy objects before first use. Release both signer and TSA
|
||||
@@ -384,13 +377,11 @@ typedef struct _MINCRYPT_POLICY_INFO
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
MINCRYPTAPI
|
||||
PVOID
|
||||
NTAPI
|
||||
PVOID NTAPI
|
||||
CiFreePolicyInfo(
|
||||
_Inout_ MINCRYPT_POLICY_INFO* PolicyInfo
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Digest-based workflow, also implemented on observed Windows 11 build 26100.
|
||||
* Prefer the file-object workflow when its policy fits. Older-build ABI and
|
||||
@@ -429,20 +420,18 @@ CiFreePolicyInfo(
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
MINCRYPTAPI
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
NTSTATUS NTAPI
|
||||
CiCheckSignedFile(
|
||||
_In_reads_bytes_(HashSize) PVOID Hash,
|
||||
_In_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_reads_bytes_(SizeOfSecurityDirectory) PVOID SecurityDirectory,
|
||||
_In_ UINT32 SizeOfSecurityDirectory,
|
||||
_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
|
||||
@@ -493,33 +482,29 @@ CiCheckSignedFile(
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
MINCRYPTAPI
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
NTSTATUS NTAPI
|
||||
CiVerifyHashInCatalog(
|
||||
_In_reads_bytes_(HashSize) PVOID Hash,
|
||||
_In_ UINT32 HashSize,
|
||||
_In_ ALG_ID HashAlgId,
|
||||
_In_ BOOLEAN ReloadCatalogs,
|
||||
_In_ UINT32 RequireTrustedCatalog,
|
||||
_In_ UINT32 AcceptedCatalogPolicyBits,
|
||||
_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;
|
||||
|
||||
/**
|
||||
@@ -548,12 +533,10 @@ 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
|
||||
);
|
||||
@@ -611,18 +594,17 @@ CiSetTrustedOriginClaimId(
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
MINCRYPTAPI
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
NTSTATUS NTAPI
|
||||
CiValidateFileObject(
|
||||
_In_ FILE_OBJECT* FileObject,
|
||||
_In_ UINT32 ValidationFlags,
|
||||
_In_ UINT32 RequestedSigningLevel,
|
||||
_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_ LARGE_INTEGER* SigningTime,
|
||||
_Out_writes_bytes_(*HashSize) UINT8* Hash,
|
||||
_Inout_ UINT32* HashSize,
|
||||
_Out_ ALG_ID* HashAlgId
|
||||
_Inout_ UINT32* HashSize,
|
||||
_Out_ ALG_ID* HashAlgId
|
||||
);
|
||||
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
|
||||
+43
-38
@@ -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(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ___ _ ___ _ _ _
|
||||
* / __|___ __| |___ |_ _|_ _| |_ ___ __ _ _ _(_) |_ _ _
|
||||
* | (__/ _ \/ _` / -_) | || ' \ _/ -_) _` | '_| | _| || |
|
||||
@@ -177,7 +168,9 @@ DOMITO_CALG_TO_BCRYPT_ALGORITHM(
|
||||
//
|
||||
_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,
|
||||
@@ -233,7 +226,9 @@ _Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
__declspec(deprecated(
|
||||
"Use DomitoCalculatePortableExecutableDigestEx with an explicit algorithm; review its supported PE layout and failure statuses."
|
||||
)) NTSTATUS
|
||||
DomitoCalculatePortableExecutableDigest(
|
||||
_In_ PUCHAR pPeBytes,
|
||||
_In_ ULONG PeSize,
|
||||
@@ -278,7 +273,7 @@ typedef struct _DOMITO_CI_VALIDATION_RESULT
|
||||
UINT32 HashSize;
|
||||
ALG_ID HashAlgId;
|
||||
UINT32 FileObjectValidationFlags; // Flags passed to CI export; zero for embedded helper.
|
||||
UINT8 RequestedSigningLevel; // File-object helper only.
|
||||
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.
|
||||
@@ -328,7 +323,7 @@ DomitoValidatePortableExecutableEmbeddedSignature(
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
|
||||
#define DOMITO_CI_SOURCE_EMBEDDED 1UL
|
||||
#define DOMITO_CI_SOURCE_CATALOG 2UL
|
||||
#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
|
||||
@@ -366,7 +361,9 @@ _Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
EXTERN_C
|
||||
NTSTATUS
|
||||
__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,
|
||||
@@ -378,7 +375,6 @@ DomitoValidateFileLegacyMode(
|
||||
_Inout_ MINCRYPT_POLICY_INFO* TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
|
||||
/* __ __ _
|
||||
* | \/ (_)___ __
|
||||
* | |\/| | (_-</ _|_
|
||||
@@ -386,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)
|
||||
@@ -469,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;
|
||||
}
|
||||
@@ -2,19 +2,30 @@
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
|
||||
static UINT32 DomitoCiDigestSize(ALG_ID Algorithm)
|
||||
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;
|
||||
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)
|
||||
static void
|
||||
DomitoClearCiDigest(
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Result->Hash, sizeof(Result->Hash));
|
||||
Result->HashSize = 0;
|
||||
@@ -23,27 +34,53 @@ static void DomitoClearCiDigest(DOMITO_CI_VALIDATION_RESULT* Result)
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
void DomitoFreeCiValidationResult(DOMITO_CI_VALIDATION_RESULT* Result)
|
||||
void
|
||||
DomitoFreeCiValidationResult(
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
if (!Result) return;
|
||||
|
||||
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)
|
||||
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;
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
if (!FileObject) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
if (!FileObject)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Selection != DomitoCiSignatureSelectionNative &&
|
||||
Selection != DomitoCiSignatureSelectionFirstSuccessfulExperimental)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Selection == DomitoCiSignatureSelectionFirstSuccessfulExperimental)
|
||||
{
|
||||
#if defined(_M_AMD64) || defined(_AMD64_)
|
||||
@@ -52,48 +89,109 @@ NTSTATUS DomitoValidateFileObjectEx(
|
||||
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);
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
NTSTATUS
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(
|
||||
const UCHAR* PeBytes,
|
||||
ULONG PeSize,
|
||||
ALG_ID HashAlgId,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
if (!Result) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
if (!PeBytes) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
if (!PeBytes)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const UINT32 expected = DomitoCiDigestSize(HashAlgId);
|
||||
if (!expected) return STATUS_NOT_SUPPORTED;
|
||||
|
||||
if (!expected)
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
PVOID digest = nullptr;
|
||||
ULONG digestSize = 0, securitySize = 0;
|
||||
ULONG digestSize = 0;
|
||||
ULONG securitySize = 0;
|
||||
LPWIN_CERTIFICATE certificate = nullptr;
|
||||
NTSTATUS status = DomitoCalculatePortableExecutableDigestEx(PeBytes, PeSize, HashAlgId,
|
||||
&digestSize, &digest, &certificate, &securitySize);
|
||||
NTSTATUS status = DomitoCalculatePortableExecutableDigestEx(
|
||||
PeBytes, PeSize, HashAlgId, &digestSize, &digest, &certificate, &securitySize
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (!certificate || !securitySize) status = STATUS_INVALID_IMAGE_HASH;
|
||||
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);
|
||||
status = CiCheckSignedFile(
|
||||
digest,
|
||||
digestSize,
|
||||
HashAlgId,
|
||||
certificate,
|
||||
securitySize,
|
||||
&Result->PolicyInfo,
|
||||
&Result->SigningTime,
|
||||
&Result->TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
RtlCopyMemory(Result->Hash, digest, digestSize);
|
||||
@@ -102,8 +200,17 @@ NTSTATUS DomitoValidatePortableExecutableEmbeddedSignature(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (digest) DomitoFreePortableExecutableDigest(digest);
|
||||
if (!NT_SUCCESS(status)) DomitoClearCiDigest(Result);
|
||||
|
||||
if (digest)
|
||||
{
|
||||
DomitoFreePortableExecutableDigest(digest);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DomitoClearCiDigest(Result);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
+426
-133
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
|
||||
+155
-108
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -131,7 +136,13 @@ DomitoFindModuleBaseAddress(
|
||||
|
||||
// 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)
|
||||
static BOOLEAN
|
||||
DomitoImageRvaSpan(
|
||||
ULONG ImageSize,
|
||||
ULONG Rva,
|
||||
ULONG Count,
|
||||
ULONG ElementSize
|
||||
)
|
||||
{
|
||||
return Rva <= ImageSize && ElementSize != 0 && Count <= (ImageSize - Rva) / ElementSize;
|
||||
}
|
||||
@@ -143,17 +154,19 @@ NTSTATUS
|
||||
DomitoFindExportedFunctionAddress(
|
||||
_In_ PVOID ModuleBase,
|
||||
_In_ PANSI_STRING FunctionName,
|
||||
_Inout_opt_ PVOID * FunctionAddress
|
||||
_Inout_opt_ PVOID* FunctionAddress
|
||||
)
|
||||
{
|
||||
if (FunctionAddress)
|
||||
{
|
||||
*FunctionAddress = nullptr;
|
||||
}
|
||||
|
||||
if (!ModuleBase || !FunctionName || !FunctionName->Buffer || !FunctionName->Length)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!G_Common.RtlImageDirectoryEntryToData)
|
||||
{
|
||||
return STATUS_NOT_IMPLEMENTED;
|
||||
@@ -163,95 +176,123 @@ DomitoFindExportedFunctionAddress(
|
||||
const PUCHAR base = (PUCHAR)ModuleBase;
|
||||
IMAGE_DOS_HEADER dos;
|
||||
memcpy(&dos, base, sizeof(dos));
|
||||
|
||||
if (dos.e_magic != IMAGE_DOS_SIGNATURE || dos.e_lfanew < (LONG)sizeof(dos))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (signature != IMAGE_NT_SIGNATURE ||
|
||||
(magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC && magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
// 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))
|
||||
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 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;
|
||||
}
|
||||
|
||||
@@ -265,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);
|
||||
@@ -285,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])
|
||||
@@ -310,7 +352,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
DomitoReadFileAt(
|
||||
_In_ HANDLE FileHandle,
|
||||
_Out_ PVOID Buffer,
|
||||
_Out_writes_bytes_(BufferSize) PVOID Buffer,
|
||||
_In_ ULONG BufferSize,
|
||||
_In_ const LARGE_INTEGER* ByteOffset
|
||||
)
|
||||
@@ -319,6 +361,7 @@ DomitoReadFileAt(
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!BufferSize)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
@@ -327,15 +370,16 @@ DomitoReadFileAt(
|
||||
HANDLE eventHandle = nullptr;
|
||||
OBJECT_ATTRIBUTES attributes;
|
||||
InitializeObjectAttributes(&attributes, nullptr, OBJ_KERNEL_HANDLE, nullptr, nullptr);
|
||||
NTSTATUS status = ZwCreateEvent(&eventHandle, EVENT_ALL_ACCESS, &attributes,
|
||||
NotificationEvent, FALSE);
|
||||
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);
|
||||
status = ObReferenceObjectByHandle(eventHandle, SYNCHRONIZE, *ExEventObjectType, KernelMode, &eventObject, nullptr);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
ZwClose(eventHandle);
|
||||
@@ -344,8 +388,9 @@ DomitoReadFileAt(
|
||||
|
||||
IO_STATUS_BLOCK ioStatusBlock = {};
|
||||
LARGE_INTEGER offset = *ByteOffset;
|
||||
status = ZwReadFile(FileHandle, eventHandle, nullptr, nullptr, &ioStatusBlock,
|
||||
Buffer, BufferSize, &offset, nullptr);
|
||||
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.
|
||||
@@ -353,14 +398,17 @@ DomitoReadFileAt(
|
||||
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;
|
||||
@@ -387,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;
|
||||
*ProcessImageName = nullptr;
|
||||
|
||||
if (!ProcessId || ProcessId == 4)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER_1;
|
||||
}
|
||||
|
||||
if (!G_Common.ZwQueryInformationProcess)
|
||||
{
|
||||
return STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
CLIENT_ID cid = {};
|
||||
cid.UniqueProcess = ULongToHandle(ProcessId);
|
||||
cid.UniqueThread = NULL;
|
||||
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);
|
||||
|
||||
OBJECT_ATTRIBUTES objAttr;
|
||||
InitializeObjectAttributes(
|
||||
&objAttr,
|
||||
NULL,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status = ZwOpenProcess(
|
||||
&hProcess,
|
||||
PROCESS_ALL_ACCESS,
|
||||
&objAttr,
|
||||
&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,6 +121,8 @@
|
||||
<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" />
|
||||
@@ -130,6 +132,7 @@
|
||||
<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>
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
<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>
|
||||
@@ -39,6 +45,9 @@
|
||||
<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>
|
||||
|
||||
@@ -80,3 +80,30 @@ 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.
|
||||
@@ -10,34 +10,76 @@
|
||||
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
||||
#define DOMITO_CI_PROBE_HOST_TEST
|
||||
typedef void* PFILE_OBJECT;
|
||||
struct MINCRYPT_POLICY_INFO {
|
||||
ULONG Size, VerificationStatus, PolicyBits;
|
||||
|
||||
struct MINCRYPT_POLICY_INFO
|
||||
{
|
||||
ULONG Size;
|
||||
ULONG VerificationStatus;
|
||||
ULONG PolicyBits;
|
||||
void* ChainInfo;
|
||||
};
|
||||
static unsigned calls, frees, outstanding, checks;
|
||||
|
||||
static unsigned calls;
|
||||
static unsigned frees;
|
||||
static unsigned outstanding;
|
||||
static unsigned checks;
|
||||
static bool failureMode;
|
||||
static int fileToken, chainToken;
|
||||
static void Check(bool value, const char* label)
|
||||
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); }
|
||||
|
||||
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)
|
||||
|
||||
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(
|
||||
!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");
|
||||
|
||||
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; }
|
||||
|
||||
if (*size < 64)
|
||||
{
|
||||
*size = 64;
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
policy->ChainInfo = timestamp->ChainInfo = &chainToken;
|
||||
outstanding = 2;
|
||||
policy->PolicyBits = 0xA5;
|
||||
@@ -45,22 +87,38 @@ static NTSTATUS CiValidateFileObject(PFILE_OBJECT file, ULONG flags, ULONG level
|
||||
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;
|
||||
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)
|
||||
|
||||
static void
|
||||
CiFreePolicyInfo(
|
||||
MINCRYPT_POLICY_INFO* policy
|
||||
)
|
||||
{
|
||||
++frees;
|
||||
if (policy->ChainInfo) { Check(outstanding > 0, "owned chain"); --outstanding; }
|
||||
|
||||
if (policy->ChainInfo)
|
||||
{
|
||||
Check(outstanding > 0, "owned chain");
|
||||
--outstanding;
|
||||
}
|
||||
|
||||
*policy = {};
|
||||
}
|
||||
|
||||
#include "ci-file-object-probe.cpp"
|
||||
|
||||
int main()
|
||||
int
|
||||
main()
|
||||
{
|
||||
for (unsigned pass = 0; pass < 2; ++pass)
|
||||
{
|
||||
@@ -70,27 +128,48 @@ int main()
|
||||
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;
|
||||
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) {
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto byte : r.Hash)
|
||||
{
|
||||
Check(byte == 0, "no digest from failed call");
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= 4)
|
||||
{
|
||||
Check(r.HashSize == 64, "required capacity captured");
|
||||
}
|
||||
if (i >= 4) Check(r.HashSize == 64, "required capacity captured");
|
||||
}
|
||||
}
|
||||
|
||||
std::printf("PASS: %u probe checks (mock CI; no runtime trust validation)\n", checks);
|
||||
}
|
||||
@@ -6,33 +6,51 @@
|
||||
#include "ci-file-object-probe.h"
|
||||
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
void DomitoProbeCiValidateFileObject(
|
||||
PFILE_OBJECT FileObject, DOMITO_CI_PROBE_RESULT* Results)
|
||||
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
|
||||
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 {
|
||||
|
||||
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.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.
|
||||
@@ -42,10 +60,12 @@ void DomitoProbeCiValidateFileObject(
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ typedef struct _DOMITO_CI_PROBE_RESULT
|
||||
// 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(
|
||||
void
|
||||
DomitoProbeCiValidateFileObject(
|
||||
_In_ PFILE_OBJECT FileObject,
|
||||
_Out_writes_(DOMITO_CI_PROBE_RESULT_COUNT) DOMITO_CI_PROBE_RESULT* Results);
|
||||
_Out_writes_(DOMITO_CI_PROBE_RESULT_COUNT) DOMITO_CI_PROBE_RESULT* Results
|
||||
);
|
||||
+421
-117
@@ -15,81 +15,207 @@
|
||||
#define PAGED_CODE() ((void)0)
|
||||
typedef void* PFILE_OBJECT;
|
||||
typedef void* LPWIN_CERTIFICATE;
|
||||
struct MINCRYPT_POLICY_INFO {
|
||||
|
||||
struct MINCRYPT_POLICY_INFO
|
||||
{
|
||||
ULONG Size;
|
||||
NTSTATUS VerificationStatus;
|
||||
ULONG PolicyBits;
|
||||
void* ChainInfo;
|
||||
};
|
||||
|
||||
#include "ci-validation-api.inc"
|
||||
|
||||
static unsigned checks, live, validationCalls, embeddedCalls, digestCalls, digestFrees;
|
||||
static NTSTATUS nativeStatus = STATUS_SUCCESS, embeddedStatus = STATUS_SUCCESS, digestStatus = STATUS_SUCCESS;
|
||||
static UINT32 nativeAlgorithm = CALG_SHA_256, nativeSize = 32;
|
||||
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, omitDigest, partialDigestOnFailure;
|
||||
static const UCHAR image[] = {1,2,3};
|
||||
static ULONG observedFlags, observedLevel, observedCapacity;
|
||||
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) {
|
||||
static void
|
||||
Check(
|
||||
bool value,
|
||||
const char* message
|
||||
)
|
||||
{
|
||||
++checks;
|
||||
if (!value) { std::fprintf(stderr, "FAIL: %s\n", message); std::exit(1); }
|
||||
|
||||
if (!value)
|
||||
{
|
||||
std::fprintf(stderr, "FAIL: %s\n", message);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
static void* Allocate(SIZE_T size) {
|
||||
|
||||
static void*
|
||||
Allocate(
|
||||
SIZE_T size
|
||||
)
|
||||
{
|
||||
void* value = std::malloc(size);
|
||||
Check(value != nullptr, "host allocation"); ++live;
|
||||
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);
|
||||
|
||||
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) {
|
||||
|
||||
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->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) {
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
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) {
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
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) {
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -99,113 +225,291 @@ static NTSTATUS CiCheckSignedFile(PVOID hash, UINT32 size, ALG_ID algorithm, LPW
|
||||
#endif
|
||||
#include "ci-validation-under-test.inc"
|
||||
|
||||
static void CheckEmptyDigest(const DOMITO_CI_VALIDATION_RESULT& result) {
|
||||
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");
|
||||
|
||||
for (auto value : result.Hash)
|
||||
{
|
||||
Check(!value, "failure hash bytes cleared");
|
||||
}
|
||||
}
|
||||
static void Clear(DOMITO_CI_VALIDATION_RESULT* result) {
|
||||
|
||||
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() {
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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);
|
||||
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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);
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
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");
|
||||
|
||||
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,
|
||||
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
|
||||
|
||||
+340
-97
@@ -14,139 +14,382 @@
|
||||
#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, MaximumLength; PCHAR Buffer; };
|
||||
#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; };
|
||||
|
||||
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) {
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
|
||||
static PVOID
|
||||
Directory(
|
||||
PVOID,
|
||||
BOOLEAN mapped,
|
||||
USHORT index,
|
||||
PULONG bytes
|
||||
)
|
||||
{
|
||||
Check(mapped && index == IMAGE_DIRECTORY_ENTRY_EXPORT, "mapped export lookup");
|
||||
*bytes = exportBytes; return exportPointer;
|
||||
*bytes = exportBytes;
|
||||
return exportPointer;
|
||||
}
|
||||
struct { PVOID (*RtlImageDirectoryEntryToData)(PVOID, BOOLEAN, USHORT, PULONG); } G_Common = { Directory };
|
||||
static int eventToken, fileToken;
|
||||
static unsigned created, referenced, closed, dereferenced, reads, waits;
|
||||
|
||||
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;
|
||||
|
||||
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 KeWaitForSingleObject(void* object, int reason, int mode, BOOLEAN alertable, LARGE_INTEGER* timeout) {
|
||||
|
||||
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");
|
||||
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() {
|
||||
|
||||
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 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}, names[] = {1100, 1120};
|
||||
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);
|
||||
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) {
|
||||
|
||||
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;
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
}
|
||||
+721
-190
File diff suppressed because it is too large.
Load diff
@@ -4,19 +4,49 @@
|
||||
|
||||
#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(
|
||||
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);
|
||||
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);
|
||||
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.
|
||||
|
||||
+25
-11
@@ -1,20 +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:
|
||||
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)
|
||||
(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)
|
||||
@@ -1,27 +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:
|
||||
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)
|
||||
(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)
|
||||
+28
-11
@@ -1,18 +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:
|
||||
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)
|
||||
(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")
|
||||
+40
-17
@@ -4,31 +4,54 @@ Extracts the actual production functions without changing their bodies, replacin
|
||||
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)
|
||||
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('))
|
||||
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('))
|
||||
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:
|
||||
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)
|
||||
(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