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:
1 parent
7985b23be5
commit
3fbfe31d15
38 files changed
+9201
-751
No files matched your search
@@ -0,0 +1,396 @@
|
||||
#include "Domito.Internal.h"
|
||||
|
||||
typedef struct _DOMITO_CERTIFICATE_DER_VIEW
|
||||
{
|
||||
const UCHAR* Data;
|
||||
ULONG Size;
|
||||
} DOMITO_CERTIFICATE_DER_VIEW;
|
||||
|
||||
static BOOLEAN
|
||||
DomitoCertificateDerTake(
|
||||
DOMITO_CERTIFICATE_DER_VIEW* Input,
|
||||
UCHAR Tag,
|
||||
DOMITO_CERTIFICATE_DER_VIEW* Content,
|
||||
DOMITO_CERTIFICATE_DER_VIEW* Encoded = nullptr
|
||||
)
|
||||
{
|
||||
if (Input->Size < 2 || Input->Data[0] != Tag)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ULONG length = Input->Data[1];
|
||||
ULONG header = 2;
|
||||
|
||||
if (length & 0x80)
|
||||
{
|
||||
const ULONG count = length & 0x7f;
|
||||
|
||||
if (!count || count > 4 || count > Input->Size - 2 || !Input->Data[2])
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
length = 0;
|
||||
|
||||
for (ULONG i = 0; i < count; ++i)
|
||||
{
|
||||
length = (length << 8) | Input->Data[header++];
|
||||
}
|
||||
|
||||
if (length < 128)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (length > Input->Size - header)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*Content = {Input->Data + header, length};
|
||||
|
||||
if (Encoded)
|
||||
{
|
||||
*Encoded = {Input->Data, header + length};
|
||||
}
|
||||
|
||||
Input->Data += header + length;
|
||||
Input->Size -= header + length;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoGetCertificateSpki(
|
||||
const UCHAR* CertData,
|
||||
ULONG CertSize,
|
||||
DOMITO_CERTIFICATE_SPKI* Spki
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Spki)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*Spki = {};
|
||||
|
||||
if (!CertData || !CertSize)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DOMITO_CERTIFICATE_DER_VIEW certificateInput = {CertData, CertSize};
|
||||
DOMITO_CERTIFICATE_DER_VIEW certificate;
|
||||
DOMITO_CERTIFICATE_DER_VIEW tbsCertificate;
|
||||
DOMITO_CERTIFICATE_DER_VIEW fieldContent;
|
||||
DOMITO_CERTIFICATE_DER_VIEW encodedSpki;
|
||||
DOMITO_CERTIFICATE_DER_VIEW spkiContent;
|
||||
// Validate complete certificate framing, without interpreting trust/algorithms.
|
||||
if (!DomitoCertificateDerTake(&certificateInput, 0x30, &certificate) || certificateInput.Size ||
|
||||
!DomitoCertificateDerTake(&certificate, 0x30, &tbsCertificate) ||
|
||||
!DomitoCertificateDerTake(&certificate, 0x30, &fieldContent) ||
|
||||
!DomitoCertificateDerTake(&certificate, 0x03, &fieldContent) || !fieldContent.Size ||
|
||||
fieldContent.Data[0] > 7 || certificate.Size)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
if (tbsCertificate.Size && tbsCertificate.Data[0] == 0xa0)
|
||||
{
|
||||
DOMITO_CERTIFICATE_DER_VIEW certificateVersion;
|
||||
|
||||
if (!DomitoCertificateDerTake(&tbsCertificate, 0xa0, &fieldContent) ||
|
||||
!DomitoCertificateDerTake(&fieldContent, 0x02, &certificateVersion) || fieldContent.Size ||
|
||||
certificateVersion.Size != 1 || certificateVersion.Data[0] > 2)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
const UCHAR tbsFieldTags[] = {0x02, 0x30, 0x30, 0x30, 0x30};
|
||||
|
||||
for (ULONG i = 0; i < RTL_NUMBER_OF(tbsFieldTags); ++i)
|
||||
{
|
||||
if (!DomitoCertificateDerTake(&tbsCertificate, tbsFieldTags[i], &fieldContent) ||
|
||||
(i == 0 && !fieldContent.Size))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!DomitoCertificateDerTake(&tbsCertificate, 0x30, &spkiContent, &encodedSpki))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
DOMITO_CERTIFICATE_DER_VIEW spkiFields = spkiContent;
|
||||
DOMITO_CERTIFICATE_DER_VIEW keyAlgorithm;
|
||||
DOMITO_CERTIFICATE_DER_VIEW algorithmOid;
|
||||
DOMITO_CERTIFICATE_DER_VIEW publicKeyBits;
|
||||
|
||||
if (!DomitoCertificateDerTake(&spkiFields, 0x30, &keyAlgorithm) ||
|
||||
!DomitoCertificateDerTake(&keyAlgorithm, 0x06, &algorithmOid) || !algorithmOid.Size ||
|
||||
!DomitoCertificateDerTake(&spkiFields, 0x03, &publicKeyBits) || publicKeyBits.Size < 2 ||
|
||||
publicKeyBits.Data[0] > 7 || spkiFields.Size)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
Spki->Encoded = {encodedSpki.Data, encodedSpki.Size};
|
||||
Spki->Content = {spkiContent.Data, spkiContent.Size};
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoHashBuffer(
|
||||
const UCHAR* Bytes,
|
||||
ULONG Size,
|
||||
ALG_ID Algorithm,
|
||||
UCHAR* Hash,
|
||||
ULONG* HashSize
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!HashSize)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const ULONG capacity = *HashSize;
|
||||
ULONG required;
|
||||
|
||||
switch (Algorithm)
|
||||
{
|
||||
case CALG_SHA1:
|
||||
required = 20;
|
||||
break;
|
||||
case CALG_SHA_256:
|
||||
required = 32;
|
||||
break;
|
||||
case CALG_SHA_384:
|
||||
required = 48;
|
||||
break;
|
||||
case CALG_SHA_512:
|
||||
required = 64;
|
||||
break;
|
||||
default:
|
||||
*HashSize = 0;
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (!Bytes && Size)
|
||||
{
|
||||
*HashSize = 0;
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!Hash || capacity < required)
|
||||
{
|
||||
*HashSize = required;
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
RtlZeroMemory(Hash, required);
|
||||
BCRYPT_ALG_HANDLE algorithm = nullptr;
|
||||
BCRYPT_HASH_HANDLE hash = nullptr;
|
||||
NTSTATUS status =
|
||||
BCryptOpenAlgorithmProvider(&algorithm, DOMITO_CALG_TO_BCRYPT_ALGORITHM(Algorithm), MS_PRIMITIVE_PROVIDER, 0);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
status = BCryptCreateHash(algorithm, &hash, nullptr, 0, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status) && Size)
|
||||
{
|
||||
status = BCryptHashData(hash, const_cast<PUCHAR>(Bytes), Size, 0);
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
status = BCryptFinishHash(hash, Hash, required, 0);
|
||||
}
|
||||
|
||||
if (hash)
|
||||
{
|
||||
BCryptDestroyHash(hash);
|
||||
}
|
||||
|
||||
if (algorithm)
|
||||
{
|
||||
BCryptCloseAlgorithmProvider(algorithm, 0);
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
*HashSize = required;
|
||||
}
|
||||
else
|
||||
{
|
||||
RtlZeroMemory(Hash, required);
|
||||
*HashSize = 0;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoHashCertificate(
|
||||
const UCHAR* CertData,
|
||||
ULONG CertSize,
|
||||
DOMITO_CERTIFICATE_HASH_KIND Kind,
|
||||
ALG_ID Algorithm,
|
||||
UCHAR* Hash,
|
||||
ULONG* HashSize
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!HashSize)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!CertData || !CertSize)
|
||||
{
|
||||
*HashSize = 0;
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DOMITO_CERTIFICATE_SPKI spki = {};
|
||||
|
||||
if (Kind == DomitoCertificateHashSpkiDer || Kind == DomitoCertificateHashSpkiContentLegacy)
|
||||
{
|
||||
NTSTATUS status = DomitoGetCertificateSpki(CertData, CertSize, &spki);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
*HashSize = 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
const DOMITO_BYTE_VIEW selected = Kind == DomitoCertificateHashSpkiDer ? spki.Encoded : spki.Content;
|
||||
return DomitoHashBuffer(selected.Data, selected.Size, Algorithm, Hash, HashSize);
|
||||
}
|
||||
|
||||
if (Kind != DomitoCertificateHashDer)
|
||||
{
|
||||
*HashSize = 0;
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// Whole-certificate fingerprint: hashes exact input bytes, no trust claim.
|
||||
return DomitoHashBuffer(CertData, CertSize, Algorithm, Hash, HashSize);
|
||||
}
|
||||
|
||||
static int
|
||||
DomitoHexDigit(
|
||||
WCHAR Value
|
||||
)
|
||||
{
|
||||
if (Value >= L'0' && Value <= L'9')
|
||||
{
|
||||
return Value - L'0';
|
||||
}
|
||||
|
||||
if (Value >= L'A' && Value <= L'F')
|
||||
{
|
||||
return Value - L'A' + 10;
|
||||
}
|
||||
|
||||
if (Value >= L'a' && Value <= L'f')
|
||||
{
|
||||
return Value - L'a' + 10;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoDecodeHex(
|
||||
const UNICODE_STRING* Hex,
|
||||
UCHAR* Bytes,
|
||||
ULONG ByteCount
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Bytes && ByteCount)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Bytes && ByteCount)
|
||||
{
|
||||
RtlZeroMemory(Bytes, ByteCount);
|
||||
}
|
||||
|
||||
if (!Hex || Hex->Length > Hex->MaximumLength || (Hex->Length && !Hex->Buffer) ||
|
||||
ByteCount > MAXUSHORT / (2 * sizeof(WCHAR)) || Hex->Length != ByteCount * 2 * sizeof(WCHAR))
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < ByteCount * 2; ++i)
|
||||
{
|
||||
if (DomitoHexDigit(Hex->Buffer[i]) < 0)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < ByteCount; ++i)
|
||||
{
|
||||
Bytes[i] =
|
||||
static_cast<UCHAR>((DomitoHexDigit(Hex->Buffer[2 * i]) << 4) | DomitoHexDigit(Hex->Buffer[2 * i + 1]));
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoEncodeHex(
|
||||
const UCHAR* Bytes,
|
||||
ULONG ByteCount,
|
||||
WCHAR* Hex,
|
||||
ULONG HexChars
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (Hex && HexChars)
|
||||
{
|
||||
Hex[0] = 0;
|
||||
}
|
||||
|
||||
if ((!Bytes && ByteCount) || !Hex)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!HexChars || ByteCount > (HexChars - 1) / 2)
|
||||
{
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
static const WCHAR digits[] = L"0123456789ABCDEF";
|
||||
WCHAR* destination = Hex;
|
||||
|
||||
for (ULONG i = 0; i < ByteCount; ++i)
|
||||
{
|
||||
*destination++ = digits[Bytes[i] >> 4];
|
||||
*destination++ = digits[Bytes[i] & 15];
|
||||
}
|
||||
|
||||
*destination = 0;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
#include "Domito.Internal.h"
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
|
||||
static UINT32
|
||||
DomitoCiDigestSize(
|
||||
ALG_ID Algorithm
|
||||
)
|
||||
{
|
||||
switch (Algorithm)
|
||||
{
|
||||
case CALG_SHA1:
|
||||
return MINCRYPT_SHA1_LENGTH;
|
||||
case CALG_SHA_256:
|
||||
return MINCRYPT_SHA256_LENGTH;
|
||||
case CALG_SHA_384:
|
||||
return MINCRYPT_SHA384_LENGTH;
|
||||
case CALG_SHA_512:
|
||||
return MINCRYPT_SHA512_LENGTH;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
DomitoClearCiDigest(
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Result->Hash, sizeof(Result->Hash));
|
||||
Result->HashSize = 0;
|
||||
Result->HashAlgId = 0;
|
||||
Result->SigningTime.QuadPart = 0;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
void
|
||||
DomitoFreeCiValidationResult(
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CiFreePolicyInfo(&Result->PolicyInfo);
|
||||
CiFreePolicyInfo(&Result->TimeStampPolicyInfo);
|
||||
RtlZeroMemory(Result, sizeof(*Result));
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoValidateFileObjectEx(
|
||||
PFILE_OBJECT FileObject,
|
||||
UINT32 ValidationFlags,
|
||||
UINT8 RequestedSigningLevel,
|
||||
DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
|
||||
if (!FileObject)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Selection != DomitoCiSignatureSelectionNative &&
|
||||
Selection != DomitoCiSignatureSelectionFirstSuccessfulExperimental)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Selection == DomitoCiSignatureSelectionFirstSuccessfulExperimental)
|
||||
{
|
||||
#if defined(_M_AMD64) || defined(_AMD64_)
|
||||
ValidationFlags |= DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL;
|
||||
#else
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
Result->FileObjectValidationFlags = ValidationFlags;
|
||||
Result->RequestedSigningLevel = RequestedSigningLevel;
|
||||
Result->HashSize = sizeof(Result->Hash);
|
||||
NTSTATUS status = CiValidateFileObject(
|
||||
FileObject,
|
||||
ValidationFlags,
|
||||
RequestedSigningLevel,
|
||||
&Result->PolicyInfo,
|
||||
&Result->TimeStampPolicyInfo,
|
||||
&Result->SigningTime,
|
||||
Result->Hash,
|
||||
&Result->HashSize,
|
||||
&Result->HashAlgId
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
const UINT32 expected = DomitoCiDigestSize(Result->HashAlgId);
|
||||
|
||||
if (!expected)
|
||||
{
|
||||
status = STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
else if (Result->HashSize != expected || Result->HashSize > sizeof(Result->Hash))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DomitoClearCiDigest(Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
RtlZeroMemory(Result->Hash + Result->HashSize, sizeof(Result->Hash) - Result->HashSize);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoValidatePortableExecutableEmbeddedSignature(
|
||||
const UCHAR* PeBytes,
|
||||
ULONG PeSize,
|
||||
ALG_ID HashAlgId,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
|
||||
if (!PeBytes)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const UINT32 expected = DomitoCiDigestSize(HashAlgId);
|
||||
|
||||
if (!expected)
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
PVOID digest = nullptr;
|
||||
ULONG digestSize = 0;
|
||||
ULONG securitySize = 0;
|
||||
LPWIN_CERTIFICATE certificate = nullptr;
|
||||
NTSTATUS status = DomitoCalculatePortableExecutableDigestEx(
|
||||
PeBytes, PeSize, HashAlgId, &digestSize, &digest, &certificate, &securitySize
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (!certificate || !securitySize)
|
||||
{
|
||||
status = STATUS_INVALID_IMAGE_HASH;
|
||||
}
|
||||
else if (!digest || digestSize != expected || digestSize > sizeof(Result->Hash))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = CiCheckSignedFile(
|
||||
digest,
|
||||
digestSize,
|
||||
HashAlgId,
|
||||
certificate,
|
||||
securitySize,
|
||||
&Result->PolicyInfo,
|
||||
&Result->SigningTime,
|
||||
&Result->TimeStampPolicyInfo
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
RtlCopyMemory(Result->Hash, digest, digestSize);
|
||||
Result->HashSize = digestSize;
|
||||
Result->HashAlgId = HashAlgId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (digest)
|
||||
{
|
||||
DomitoFreePortableExecutableDigest(digest);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DomitoClearCiDigest(Result);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||
+824
-274
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,395 @@
|
||||
#include "Domito.Internal.h"
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoFindModuleExportedFunctionAddress(
|
||||
PANSI_STRING ModuleName,
|
||||
PANSI_STRING FunctionName,
|
||||
PVOID* FunctionAddress
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!FunctionAddress)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*FunctionAddress = nullptr;
|
||||
|
||||
if (!ModuleName || !ModuleName->Buffer || !ModuleName->Length || ModuleName->Length > ModuleName->MaximumLength ||
|
||||
!FunctionName || !FunctionName->Buffer || !FunctionName->Length ||
|
||||
FunctionName->Length > FunctionName->MaximumLength)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
PVOID moduleBase = nullptr;
|
||||
NTSTATUS status = DomitoFindModuleBaseAddress(ModuleName, &moduleBase);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
return DomitoFindExportedFunctionAddress(moduleBase, FunctionName, FunctionAddress);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
void
|
||||
DomitoFreeProcessImageName(
|
||||
PUNICODE_STRING ImageName
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (ImageName)
|
||||
{
|
||||
G_Memory.Free(ImageName);
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoGetProcessImageNameEx(
|
||||
PEPROCESS Process,
|
||||
PUNICODE_STRING* ImageName
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!ImageName)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*ImageName = nullptr;
|
||||
|
||||
if (!Process)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
PUNICODE_STRING processImageName = nullptr;
|
||||
NTSTATUS status = SeLocateProcessImageName(Process, &processImageName);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (!processImageName || !processImageName->Buffer || !processImageName->Length ||
|
||||
(processImageName->Length & 1) || processImageName->Length > processImageName->MaximumLength ||
|
||||
processImageName->Length > MAXUSHORT - sizeof(WCHAR))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
const SIZE_T size = sizeof(UNICODE_STRING) + processImageName->Length + sizeof(WCHAR);
|
||||
PUNICODE_STRING imageName = static_cast<PUNICODE_STRING>(G_Memory.Allocate(size));
|
||||
|
||||
if (!imageName)
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
else
|
||||
{
|
||||
imageName->Buffer = reinterpret_cast<PWCH>(imageName + 1);
|
||||
imageName->Length = processImageName->Length;
|
||||
imageName->MaximumLength = processImageName->Length + sizeof(WCHAR);
|
||||
RtlCopyMemory(imageName->Buffer, processImageName->Buffer, processImageName->Length);
|
||||
imageName->Buffer[imageName->Length / sizeof(WCHAR)] = 0;
|
||||
*ImageName = imageName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (processImageName)
|
||||
{
|
||||
ExFreePool(processImageName);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoValidateFileHandleEx(
|
||||
HANDLE FileHandle,
|
||||
UINT32 ValidationFlags,
|
||||
UINT8 RequestedSigningLevel,
|
||||
DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
|
||||
if (!FileHandle)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
PFILE_OBJECT fileObject = nullptr;
|
||||
NTSTATUS status = ObReferenceObjectByHandle(
|
||||
FileHandle, FILE_READ_DATA, *IoFileObjectType, KernelMode, reinterpret_cast<PVOID*>(&fileObject), nullptr
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = DomitoValidateFileObjectEx(fileObject, ValidationFlags, RequestedSigningLevel, Selection, Result);
|
||||
ObDereferenceObject(fileObject);
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoValidateFilePathEx(
|
||||
const UNICODE_STRING* Path,
|
||||
UINT32 ValidationFlags,
|
||||
UINT8 RequestedSigningLevel,
|
||||
DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||
DOMITO_CI_VALIDATION_RESULT* Result
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!Result)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DomitoFreeCiValidationResult(Result);
|
||||
|
||||
if (!Path || !Path->Buffer || Path->Length < sizeof(WCHAR) || (Path->Length & 1) ||
|
||||
Path->Length > Path->MaximumLength || Path->Buffer[0] != L'\\')
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
for (USHORT i = 0; i < Path->Length / sizeof(WCHAR); ++i)
|
||||
{
|
||||
if (!Path->Buffer[i])
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
OBJECT_ATTRIBUTES attributes;
|
||||
InitializeObjectAttributes(
|
||||
&attributes, const_cast<PUNICODE_STRING>(Path), OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr
|
||||
);
|
||||
HANDLE fileHandle = nullptr;
|
||||
IO_STATUS_BLOCK iosb = {};
|
||||
NTSTATUS status = ZwOpenFile(
|
||||
&fileHandle,
|
||||
FILE_READ_DATA | SYNCHRONIZE,
|
||||
&attributes,
|
||||
&iosb,
|
||||
FILE_SHARE_READ,
|
||||
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = DomitoValidateFileHandleEx(fileHandle, ValidationFlags, RequestedSigningLevel, Selection, Result);
|
||||
ZwClose(fileHandle);
|
||||
return status;
|
||||
}
|
||||
|
||||
#if defined(_M_AMD64) || defined(_AMD64_)
|
||||
static BOOLEAN
|
||||
DomitoCiOwnedSpan(
|
||||
const MINCRYPT_CHAIN_INFO* Chain,
|
||||
const void* Data,
|
||||
SIZE_T Size
|
||||
)
|
||||
{
|
||||
const ULONG_PTR chainBaseAddress = reinterpret_cast<ULONG_PTR>(Chain);
|
||||
const ULONG_PTR blobAddress = reinterpret_cast<ULONG_PTR>(Data);
|
||||
// Empty blobs may be NULL; they are never dereferenced by this accessor.
|
||||
if (!Size)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return blobAddress >= chainBaseAddress && blobAddress - chainBaseAddress >= 56 &&
|
||||
blobAddress - chainBaseAddress <= Chain->Size && Size <= Chain->Size - (blobAddress - chainBaseAddress);
|
||||
}
|
||||
#endif
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoGetCiChainView(
|
||||
const MINCRYPT_POLICY_INFO* Policy,
|
||||
DOMITO_CI_CHAIN_LAYOUT Layout,
|
||||
DOMITO_CI_CHAIN_VIEW* View
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!View)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*View = {};
|
||||
|
||||
if (!Policy || Layout != DomitoCiChainLayoutObservedX64)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
#if defined(_M_AMD64) || defined(_AMD64_)
|
||||
C_ASSERT(sizeof(MINCRYPT_CHAIN_ELEMENT) == 120);
|
||||
C_ASSERT(FIELD_OFFSET(MINCRYPT_CHAIN_INFO, ChainElements) == 40);
|
||||
C_ASSERT(FIELD_OFFSET(MINCRYPT_CHAIN_INFO, NumberOfChainElement) == 48);
|
||||
|
||||
if (Policy->Size != sizeof(*Policy))
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
const MINCRYPT_CHAIN_INFO* chain = Policy->ChainInfo;
|
||||
|
||||
if (!chain)
|
||||
{
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (chain->Size < 56)
|
||||
{
|
||||
return STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
const ULONG count = chain->NumberOfChainElement;
|
||||
|
||||
if (!count)
|
||||
{
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (count > (chain->Size - 56) / sizeof(MINCRYPT_CHAIN_ELEMENT) ||
|
||||
!DomitoCiOwnedSpan(chain, chain->ChainElements, static_cast<SIZE_T>(count) * sizeof(MINCRYPT_CHAIN_ELEMENT)))
|
||||
{
|
||||
return STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < count; ++i)
|
||||
{
|
||||
const MINCRYPT_CHAIN_ELEMENT& element = chain->ChainElements[i];
|
||||
|
||||
if (element.HashSize > sizeof(element.Hash) || !element.Certificate.Size ||
|
||||
!DomitoCiOwnedSpan(chain, element.Certificate.Data, element.Certificate.Size) ||
|
||||
!DomitoCiOwnedSpan(chain, element.Subject.Data, element.Subject.Size) ||
|
||||
!DomitoCiOwnedSpan(chain, element.Issuer.Data, element.Issuer.Size))
|
||||
{
|
||||
return STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG rootId;
|
||||
RtlCopyMemory(&rootId, reinterpret_cast<const UCHAR*>(chain) + 52, sizeof(rootId));
|
||||
*View = {chain->ChainElements, count, rootId};
|
||||
return STATUS_SUCCESS;
|
||||
#else
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
static MINCRYPT_ALLOCATE_ROUTINE DomitoAllocatePublisher;
|
||||
|
||||
static PVOID NTAPI
|
||||
DomitoAllocatePublisher(
|
||||
SIZE_T Size
|
||||
)
|
||||
{
|
||||
#pragma warning(suppress : 4996)
|
||||
return ExAllocatePoolWithTag(PagedPool, Size, DOMITO_POOL_TAG);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
void
|
||||
DomitoFreeCertificatePublisherName(
|
||||
PUNICODE_STRING PublisherName
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!PublisherName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (PublisherName->Buffer)
|
||||
{
|
||||
ExFreePoolWithTag(PublisherName->Buffer, DOMITO_POOL_TAG);
|
||||
}
|
||||
|
||||
*PublisherName = {};
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DomitoGetCertificatePublisherName(
|
||||
const MINCERT_BLOB* Certificate,
|
||||
PUNICODE_STRING PublisherName
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
if (!PublisherName)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*PublisherName = {};
|
||||
|
||||
if (!Certificate || !Certificate->Data || !Certificate->Size)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
ANSI_STRING moduleName = RTL_CONSTANT_STRING("\\SystemRoot\\system32\\CI.dll");
|
||||
ANSI_STRING exportName = RTL_CONSTANT_STRING("CiGetCertPublisherName");
|
||||
PVOID publisherRoutine = nullptr;
|
||||
NTSTATUS status = DomitoFindModuleExportedFunctionAddress(&moduleName, &exportName, &publisherRoutine);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!publisherRoutine)
|
||||
{
|
||||
return STATUS_PROCEDURE_NOT_FOUND;
|
||||
}
|
||||
|
||||
typedef NTSTATUS(NTAPI * GET_PUBLISHER)(MINCERT_BLOB*, PMINCRYPT_ALLOCATE_ROUTINE, PUNICODE_STRING);
|
||||
const GET_PUBLISHER getPublisher = reinterpret_cast<GET_PUBLISHER>(publisherRoutine);
|
||||
status = getPublisher(const_cast<MINCERT_BLOB*>(Certificate), DomitoAllocatePublisher, PublisherName);
|
||||
|
||||
if (NT_SUCCESS(status) && (PublisherName->Length > PublisherName->MaximumLength || (PublisherName->Length & 1) ||
|
||||
!PublisherName->Length || !PublisherName->Buffer))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DomitoFreeCertificatePublisherName(PublisherName);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
+305
-157
@@ -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)
|
||||
@@ -27,7 +35,7 @@ DomitoInit()
|
||||
{
|
||||
//
|
||||
// Do those first since the follow-up code depends on them
|
||||
//
|
||||
//
|
||||
|
||||
G_Common.ZwQueryInformationProcess =
|
||||
(t_ZwQueryInformationProcess)MmGetSystemRoutineAddress((PUNICODE_STRING)&G_QipRoutineName);
|
||||
@@ -43,19 +51,27 @@ _IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
void
|
||||
DomitoShutdown()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
DomitoFindModuleBaseAddress(
|
||||
_In_ PANSI_STRING ModuleName,
|
||||
_Inout_opt_ PVOID * ModuleBase
|
||||
_Inout_opt_ PVOID* ModuleBase
|
||||
)
|
||||
{
|
||||
if (ModuleBase)
|
||||
{
|
||||
*ModuleBase = nullptr;
|
||||
}
|
||||
|
||||
if (!ModuleName || !ModuleName->Buffer || !ModuleName->Length || ModuleName->Length > ModuleName->MaximumLength)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
ULONG bufferSize = 0;
|
||||
PSYSTEM_MODULE_INFORMATION moduleInfo = NULL;
|
||||
|
||||
@@ -67,12 +83,7 @@ DomitoFindModuleBaseAddress(
|
||||
const ULONG SystemModuleInformation = 11;
|
||||
|
||||
// Query the required buffer size for module information
|
||||
NTSTATUS status = G_Common.ZwQuerySystemInformation(
|
||||
SystemModuleInformation,
|
||||
&bufferSize,
|
||||
0,
|
||||
&bufferSize
|
||||
);
|
||||
NTSTATUS status = G_Common.ZwQuerySystemInformation(SystemModuleInformation, &bufferSize, 0, &bufferSize);
|
||||
|
||||
if (status != STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
@@ -80,9 +91,7 @@ DomitoFindModuleBaseAddress(
|
||||
}
|
||||
|
||||
// Allocate memory for the module information
|
||||
moduleInfo = (PSYSTEM_MODULE_INFORMATION)G_Memory.Allocate(
|
||||
bufferSize
|
||||
);
|
||||
moduleInfo = (PSYSTEM_MODULE_INFORMATION)G_Memory.Allocate(bufferSize);
|
||||
|
||||
if (moduleInfo == NULL)
|
||||
{
|
||||
@@ -90,12 +99,7 @@ DomitoFindModuleBaseAddress(
|
||||
}
|
||||
|
||||
// Retrieve the module information
|
||||
status = G_Common.ZwQuerySystemInformation(
|
||||
SystemModuleInformation,
|
||||
moduleInfo,
|
||||
bufferSize,
|
||||
NULL
|
||||
);
|
||||
status = G_Common.ZwQuerySystemInformation(SystemModuleInformation, moduleInfo, bufferSize, NULL);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
@@ -120,6 +124,7 @@ DomitoFindModuleBaseAddress(
|
||||
{
|
||||
*ModuleBase = moduleInfo->Module[i].Base;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -129,6 +134,19 @@ DomitoFindModuleBaseAddress(
|
||||
return status;
|
||||
}
|
||||
|
||||
// This helper handles directory RVAs after the loader-mapped header establishes
|
||||
// SizeOfImage. ModuleBase is not an arbitrary file buffer (there is no size input).
|
||||
static BOOLEAN
|
||||
DomitoImageRvaSpan(
|
||||
ULONG ImageSize,
|
||||
ULONG Rva,
|
||||
ULONG Count,
|
||||
ULONG ElementSize
|
||||
)
|
||||
{
|
||||
return Rva <= ImageSize && ElementSize != 0 && Count <= (ImageSize - Rva) / ElementSize;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
@@ -136,59 +154,146 @@ NTSTATUS
|
||||
DomitoFindExportedFunctionAddress(
|
||||
_In_ PVOID ModuleBase,
|
||||
_In_ PANSI_STRING FunctionName,
|
||||
_Inout_opt_ PVOID * FunctionAddress
|
||||
_Inout_opt_ PVOID* FunctionAddress
|
||||
)
|
||||
{
|
||||
NTSTATUS status = STATUS_NOT_FOUND;
|
||||
ULONG exportSize;
|
||||
if (FunctionAddress)
|
||||
{
|
||||
*FunctionAddress = nullptr;
|
||||
}
|
||||
|
||||
if (G_Common.RtlImageDirectoryEntryToData == NULL)
|
||||
if (!ModuleBase || !FunctionName || !FunctionName->Buffer || !FunctionName->Length)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!G_Common.RtlImageDirectoryEntryToData)
|
||||
{
|
||||
return STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// Retrieve the export directory information
|
||||
const PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)G_Common.RtlImageDirectoryEntryToData(
|
||||
ModuleBase,
|
||||
TRUE,
|
||||
IMAGE_DIRECTORY_ENTRY_EXPORT,
|
||||
&exportSize
|
||||
);
|
||||
// Initial headers belong to a valid loaded module, as required by this API.
|
||||
const PUCHAR base = (PUCHAR)ModuleBase;
|
||||
IMAGE_DOS_HEADER dos;
|
||||
memcpy(&dos, base, sizeof(dos));
|
||||
|
||||
if (exportDirectory == NULL)
|
||||
if (dos.e_magic != IMAGE_DOS_SIGNATURE || dos.e_lfanew < (LONG)sizeof(dos))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
STRING currentFunctionName;
|
||||
const PUCHAR nt = base + dos.e_lfanew;
|
||||
ULONG signature;
|
||||
memcpy(&signature, nt, sizeof(signature));
|
||||
USHORT magic;
|
||||
memcpy(&magic, nt + sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER), sizeof(magic));
|
||||
|
||||
const PULONG functionAddresses = (PULONG)((ULONG_PTR)ModuleBase + exportDirectory->AddressOfFunctions);
|
||||
const PULONG functionNames = (PULONG)((ULONG_PTR)ModuleBase + exportDirectory->AddressOfNames);
|
||||
const PUSHORT functionOrdinals = (PUSHORT)((ULONG_PTR)ModuleBase + exportDirectory->AddressOfNameOrdinals);
|
||||
|
||||
for (ULONG i = 0; i < exportDirectory->NumberOfNames; i++)
|
||||
if (signature != IMAGE_NT_SIGNATURE ||
|
||||
(magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC && magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC))
|
||||
{
|
||||
const char* functionName = (const char*)((ULONG_PTR)ModuleBase + functionNames[i]);
|
||||
const USHORT functionOrdinal = functionOrdinals[i];
|
||||
UNREFERENCED_PARAMETER(functionOrdinal);
|
||||
|
||||
const ULONG functionRva = functionAddresses[i];
|
||||
const PVOID functionAddress = (PVOID)((ULONG_PTR)ModuleBase + functionRva);
|
||||
|
||||
RtlInitAnsiString(¤tFunctionName, functionName);
|
||||
|
||||
if (0 == RtlCompareString(FunctionName, ¤tFunctionName, TRUE))
|
||||
{
|
||||
if (FunctionAddress)
|
||||
{
|
||||
status = STATUS_SUCCESS;
|
||||
*FunctionAddress = functionAddress;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
return status;
|
||||
// SizeOfImage is at the same offset in PE32 and PE32+ optional headers.
|
||||
ULONG imageSize;
|
||||
memcpy(
|
||||
&imageSize,
|
||||
nt + sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + FIELD_OFFSET(IMAGE_OPTIONAL_HEADER32, SizeOfImage),
|
||||
sizeof(imageSize)
|
||||
);
|
||||
|
||||
if (!DomitoImageRvaSpan(
|
||||
imageSize,
|
||||
(ULONG)dos.e_lfanew,
|
||||
sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + FIELD_OFFSET(IMAGE_OPTIONAL_HEADER32, SizeOfImage) +
|
||||
sizeof(imageSize),
|
||||
1
|
||||
))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
ULONG exportSize = 0;
|
||||
const PVOID directory =
|
||||
G_Common.RtlImageDirectoryEntryToData(ModuleBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &exportSize);
|
||||
const ULONG_PTR directoryOffset = (ULONG_PTR)directory - (ULONG_PTR)ModuleBase;
|
||||
|
||||
if (!directory || directoryOffset > imageSize || exportSize < sizeof(IMAGE_EXPORT_DIRECTORY) ||
|
||||
exportSize > imageSize - directoryOffset)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
IMAGE_EXPORT_DIRECTORY exports;
|
||||
memcpy(&exports, directory, sizeof(exports));
|
||||
|
||||
if (!DomitoImageRvaSpan(imageSize, exports.AddressOfFunctions, exports.NumberOfFunctions, sizeof(ULONG)) ||
|
||||
!DomitoImageRvaSpan(imageSize, exports.AddressOfNames, exports.NumberOfNames, sizeof(ULONG)) ||
|
||||
!DomitoImageRvaSpan(imageSize, exports.AddressOfNameOrdinals, exports.NumberOfNames, sizeof(USHORT)))
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < exports.NumberOfNames; ++i)
|
||||
{
|
||||
ULONG nameRva;
|
||||
USHORT ordinal;
|
||||
memcpy(&nameRva, base + exports.AddressOfNames + i * sizeof(ULONG), sizeof(nameRva));
|
||||
memcpy(&ordinal, base + exports.AddressOfNameOrdinals + i * sizeof(USHORT), sizeof(ordinal));
|
||||
|
||||
if (nameRva >= imageSize || ordinal >= exports.NumberOfFunctions)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
ULONG length = 0;
|
||||
while (length < imageSize - nameRva && base[nameRva + length] != 0)
|
||||
{
|
||||
++length;
|
||||
}
|
||||
|
||||
if (length == imageSize - nameRva || length > MAXUSHORT)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
ANSI_STRING current;
|
||||
current.Buffer = (PCHAR)(base + nameRva);
|
||||
current.Length = (USHORT)length;
|
||||
current.MaximumLength = (USHORT)length;
|
||||
|
||||
if (RtlCompareString(FunctionName, ¤t, TRUE) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ULONG functionRva;
|
||||
memcpy(&functionRva, base + exports.AddressOfFunctions + ordinal * sizeof(ULONG), sizeof(functionRva));
|
||||
|
||||
if (!functionRva)
|
||||
{
|
||||
return STATUS_NOT_FOUND; // Empty EAT slot.
|
||||
}
|
||||
|
||||
if (functionRva >= imageSize)
|
||||
{
|
||||
return STATUS_INVALID_IMAGE_FORMAT;
|
||||
}
|
||||
|
||||
if (functionRva >= directoryOffset && functionRva - directoryOffset < exportSize)
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED; // Forwarder string, not a callable address.
|
||||
}
|
||||
|
||||
if (FunctionAddress)
|
||||
{
|
||||
*FunctionAddress = base + functionRva;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
@@ -201,7 +306,7 @@ DomitoMemorySearchPattern(
|
||||
_In_ SIZE_T puLen,
|
||||
_In_ PVOID pcBase,
|
||||
_In_ SIZE_T puSize,
|
||||
_Outptr_result_maybenull_ PVOID * ppMatch
|
||||
_Outptr_result_maybenull_ PVOID* ppMatch
|
||||
)
|
||||
{
|
||||
ASSERT(ppMatch != NULL && pcPattern != NULL && pcBase != NULL);
|
||||
@@ -221,6 +326,7 @@ DomitoMemorySearchPattern(
|
||||
for (SIZE_T i = 0; i + puLen <= puSize; i++)
|
||||
{
|
||||
BOOLEAN found = TRUE;
|
||||
|
||||
for (SIZE_T j = 0; j < puLen; j++)
|
||||
{
|
||||
if (pcPattern[j] != uWildcard && pcPattern[j] != ((PCUCHAR)pcBase)[i + j])
|
||||
@@ -240,6 +346,74 @@ DomitoMemorySearchPattern(
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
DomitoReadFileAt(
|
||||
_In_ HANDLE FileHandle,
|
||||
_Out_writes_bytes_(BufferSize) PVOID Buffer,
|
||||
_In_ ULONG BufferSize,
|
||||
_In_ const LARGE_INTEGER* ByteOffset
|
||||
)
|
||||
{
|
||||
if (!FileHandle || !ByteOffset || ByteOffset->QuadPart < 0 || (!Buffer && BufferSize))
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!BufferSize)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
HANDLE eventHandle = nullptr;
|
||||
OBJECT_ATTRIBUTES attributes;
|
||||
InitializeObjectAttributes(&attributes, nullptr, OBJ_KERNEL_HANDLE, nullptr, nullptr);
|
||||
NTSTATUS status = ZwCreateEvent(&eventHandle, EVENT_ALL_ACCESS, &attributes, NotificationEvent, FALSE);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
PVOID eventObject = nullptr;
|
||||
status = ObReferenceObjectByHandle(eventHandle, SYNCHRONIZE, *ExEventObjectType, KernelMode, &eventObject, nullptr);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
ZwClose(eventHandle);
|
||||
return status;
|
||||
}
|
||||
|
||||
IO_STATUS_BLOCK ioStatusBlock = {};
|
||||
LARGE_INTEGER offset = *ByteOffset;
|
||||
status =
|
||||
ZwReadFile(FileHandle, eventHandle, nullptr, nullptr, &ioStatusBlock, Buffer, BufferSize, &offset, nullptr);
|
||||
|
||||
if (status == STATUS_PENDING)
|
||||
{
|
||||
// A private event identifies this request even if the file has other I/O.
|
||||
// KernelMode + nonalertable + no timeout cannot return before completion.
|
||||
KeWaitForSingleObject(eventObject, Executive, KernelMode, FALSE, nullptr);
|
||||
status = ioStatusBlock.Status;
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
status = ioStatusBlock.Status;
|
||||
|
||||
if (NT_SUCCESS(status) && ioStatusBlock.Information != BufferSize)
|
||||
{
|
||||
status = STATUS_END_OF_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
ObDereferenceObject(eventObject);
|
||||
ZwClose(eventHandle);
|
||||
return status;
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
_Must_inspect_result_
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
@@ -250,33 +424,8 @@ DomitoReadFile(
|
||||
_In_ ULONG BufferSize
|
||||
)
|
||||
{
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
IO_STATUS_BLOCK ioStatusBlock;
|
||||
|
||||
// Read the file into memory using ZwReadFile
|
||||
if (!NT_SUCCESS(status = ZwReadFile(
|
||||
FileHandle,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&ioStatusBlock,
|
||||
Buffer,
|
||||
BufferSize,
|
||||
NULL,
|
||||
NULL
|
||||
)))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
// Check if the file was read successfully
|
||||
if (!NT_SUCCESS(ioStatusBlock.Status))
|
||||
{
|
||||
return ioStatusBlock.Status;
|
||||
}
|
||||
|
||||
// File read successfully
|
||||
return status;
|
||||
const LARGE_INTEGER offset = {};
|
||||
return DomitoReadFileAt(FileHandle, Buffer, BufferSize, &offset);
|
||||
}
|
||||
|
||||
_Success_(return == STATUS_SUCCESS)
|
||||
@@ -286,95 +435,94 @@ _IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
DomitoGetProcessImageName(
|
||||
_In_ ULONG ProcessId,
|
||||
_Inout_ PUNICODE_STRING * ProcessImageName
|
||||
_Outptr_result_maybenull_ PUNICODE_STRING* ProcessImageName
|
||||
)
|
||||
{
|
||||
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
||||
ULONG returnedLength;
|
||||
HANDLE hProcess = NULL;
|
||||
PAGED_CODE();
|
||||
|
||||
PAGED_CODE(); // this eliminates the possibility of the IDLE Thread/Process
|
||||
|
||||
if (ProcessId == 0 || ProcessId == 4)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER_1;
|
||||
}
|
||||
|
||||
if (ProcessImageName == NULL)
|
||||
if (!ProcessImageName)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER_2;
|
||||
}
|
||||
|
||||
CLIENT_ID cid;
|
||||
cid.UniqueProcess = (HANDLE)ProcessId;
|
||||
cid.UniqueThread = NULL;
|
||||
*ProcessImageName = nullptr;
|
||||
|
||||
OBJECT_ATTRIBUTES objAttr;
|
||||
InitializeObjectAttributes(
|
||||
&objAttr,
|
||||
NULL,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
if (!ProcessId || ProcessId == 4)
|
||||
{
|
||||
return STATUS_INVALID_PARAMETER_1;
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status = ZwOpenProcess(
|
||||
&hProcess,
|
||||
PROCESS_ALL_ACCESS,
|
||||
&objAttr,
|
||||
&cid
|
||||
)))
|
||||
if (!G_Common.ZwQueryInformationProcess)
|
||||
{
|
||||
return STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
CLIENT_ID cid = {};
|
||||
cid.UniqueProcess = ULongToHandle(ProcessId);
|
||||
OBJECT_ATTRIBUTES attributes;
|
||||
InitializeObjectAttributes(&attributes, nullptr, OBJ_KERNEL_HANDLE, nullptr, nullptr);
|
||||
HANDLE process = nullptr;
|
||||
// PROCESS_QUERY_LIMITED_INFORMATION (WinNT.h); not named by the WDK headers.
|
||||
const ACCESS_MASK queryLimitedInformation = 0x1000;
|
||||
NTSTATUS status = ZwOpenProcess(&process, queryLimitedInformation, &attributes, &cid);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (G_Common.ZwQueryInformationProcess == NULL)
|
||||
ULONG size = 0;
|
||||
status = G_Common.ZwQueryInformationProcess(process, ProcessImageFileName, nullptr, 0, &size);
|
||||
|
||||
if (status == STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
status = STATUS_NOT_IMPLEMENTED;
|
||||
goto cleanUp;
|
||||
}
|
||||
if (size < sizeof(UNICODE_STRING))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto image = static_cast<PUNICODE_STRING>(G_Memory.Allocate(size));
|
||||
|
||||
/* Query the actual size of the process path */
|
||||
status = G_Common.ZwQueryInformationProcess(
|
||||
hProcess,
|
||||
ProcessImageFileName,
|
||||
NULL, // buffer
|
||||
0, // buffer size
|
||||
&returnedLength
|
||||
);
|
||||
if (!image)
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
else
|
||||
{
|
||||
ULONG returned = 0;
|
||||
status = G_Common.ZwQueryInformationProcess(process, ProcessImageFileName, image, size, &returned);
|
||||
|
||||
if (STATUS_INFO_LENGTH_MISMATCH != status)
|
||||
{
|
||||
goto cleanUp;
|
||||
}
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
const ULONG_PTR base = reinterpret_cast<ULONG_PTR>(image);
|
||||
const ULONG_PTR buffer = reinterpret_cast<ULONG_PTR>(image->Buffer);
|
||||
|
||||
*ProcessImageName = (PUNICODE_STRING)G_Memory.Allocate(returnedLength);
|
||||
if (!image->Length || (image->Length & 1) || image->Length > image->MaximumLength ||
|
||||
buffer < base || buffer - base < sizeof(UNICODE_STRING) || buffer - base > size ||
|
||||
image->MaximumLength > size - (buffer - base))
|
||||
{
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if (*ProcessImageName == NULL)
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto cleanUp;
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
*ProcessImageName = image;
|
||||
}
|
||||
else
|
||||
{
|
||||
G_Memory.Free(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Retrieve the process path from the handle to the process */
|
||||
if (!NT_SUCCESS(status = G_Common.ZwQueryInformationProcess(
|
||||
hProcess,
|
||||
ProcessImageFileName,
|
||||
*ProcessImageName,
|
||||
returnedLength,
|
||||
&returnedLength
|
||||
)))
|
||||
{
|
||||
G_Memory.Free(*ProcessImageName);
|
||||
}
|
||||
|
||||
cleanUp:
|
||||
|
||||
if (hProcess)
|
||||
else if (NT_SUCCESS(status))
|
||||
{
|
||||
ZwClose(hProcess);
|
||||
status = STATUS_INVALID_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
ZwClose(process);
|
||||
return status;
|
||||
}
|
||||
#pragma code_seg()
|
||||
@@ -121,12 +121,18 @@
|
||||
<FilesToPackage Include="$(TargetPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Domito.Certificate.cpp" />
|
||||
<ClCompile Include="Domito.Consumer.cpp" />
|
||||
<ClCompile Include="Domito.CodeIntegrity.cpp" />
|
||||
<ClCompile Include="Domito.CodeIntegrity.Validation.cpp" />
|
||||
<ClCompile Include="Domito.cpp" />
|
||||
<ClCompile Include="Domito.Memory.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.h" />
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.26100.h" />
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.Historical.h" />
|
||||
<ClInclude Include="..\include\Domito.Consumer.h" />
|
||||
<ClInclude Include="..\include\Domito.h" />
|
||||
<ClInclude Include="Domito.Internal.h" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -28,6 +28,15 @@
|
||||
<ClCompile Include="Domito.Memory.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Domito.CodeIntegrity.Validation.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Domito.Certificate.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Domito.Consumer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Domito.CodeIntegrity.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -36,9 +45,18 @@
|
||||
<ClInclude Include="Domito.Internal.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.Consumer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.h">
|
||||
<Filter>Header Files\Public</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.26100.h">
|
||||
<Filter>Header Files\Public</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.Historical.h">
|
||||
<Filter>Header Files\Public</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\Domito.MinCrypt.h">
|
||||
<Filter>Header Files\Public</Filter>
|
||||
</ClInclude>
|
||||
|
||||
Reference in new issue
Block a user