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.

Add CI validation helpers with explicit signature selection and owned results

Document runtime-confirmed CI signer selection and crypto reuse

Expand CI compatibility matrix to twenty verified samples

Extend CI export matrix with Windows 11 x64 and ARM64 samples

Document CI API availability and historical mincrypt layouts

Add explicit Authenticode workflows and correct CI helper contracts

Document CI validation flag 0x20 and add kernel probe

Harden CI digest parsing and failure cleanup
This commit is contained in:
nefarius committed 2026-10-27 00:17:25 +01:00
1 parent 7985b23be5
commit 3fbfe31d15
38 files changed
+9201 -751

No files matched your search

+395
View File
@@ -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