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,395 @@
|
||||
// Tests extract the unchanged production bodies. Kernel I/O is simulated.
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
#include <ntstatus.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <cctype>
|
||||
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
||||
#define MAXUSHORT 0xffff
|
||||
#define Executive 0
|
||||
#define KernelMode 0
|
||||
#define NotificationEvent 0
|
||||
#define OBJ_KERNEL_HANDLE 0x200
|
||||
#define InitializeObjectAttributes(a, n, f, r, s) ((a)->Attributes = (f))
|
||||
|
||||
struct OBJECT_ATTRIBUTES
|
||||
{
|
||||
ULONG Attributes;
|
||||
};
|
||||
|
||||
struct ANSI_STRING
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PCHAR Buffer;
|
||||
};
|
||||
|
||||
typedef ANSI_STRING* PANSI_STRING;
|
||||
|
||||
struct IO_STATUS_BLOCK
|
||||
{
|
||||
NTSTATUS Status;
|
||||
ULONG_PTR Information;
|
||||
};
|
||||
|
||||
static int objectType;
|
||||
static void* objectTypeValue = &objectType;
|
||||
static void** ExEventObjectType = &objectTypeValue;
|
||||
static unsigned checks;
|
||||
|
||||
static void
|
||||
Check(
|
||||
bool value,
|
||||
const char* label
|
||||
)
|
||||
{
|
||||
++checks;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
std::fprintf(stderr, "FAIL: %s\n", label);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static LONG
|
||||
RtlCompareString(
|
||||
PANSI_STRING a,
|
||||
PANSI_STRING b,
|
||||
BOOLEAN insensitive
|
||||
)
|
||||
{
|
||||
if (a->Length != b->Length)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (USHORT i = 0; i < a->Length; ++i)
|
||||
{
|
||||
unsigned char x = (unsigned char)a->Buffer[i], y = (unsigned char)b->Buffer[i];
|
||||
|
||||
if (insensitive)
|
||||
{
|
||||
x = (unsigned char)std::tolower(x);
|
||||
y = (unsigned char)std::tolower(y);
|
||||
}
|
||||
|
||||
if (x != y)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void* exportPointer;
|
||||
static ULONG exportBytes;
|
||||
|
||||
static PVOID
|
||||
Directory(
|
||||
PVOID,
|
||||
BOOLEAN mapped,
|
||||
USHORT index,
|
||||
PULONG bytes
|
||||
)
|
||||
{
|
||||
Check(mapped && index == IMAGE_DIRECTORY_ENTRY_EXPORT, "mapped export lookup");
|
||||
*bytes = exportBytes;
|
||||
return exportPointer;
|
||||
}
|
||||
|
||||
struct
|
||||
{
|
||||
PVOID (*RtlImageDirectoryEntryToData)(
|
||||
PVOID,
|
||||
BOOLEAN,
|
||||
USHORT,
|
||||
PULONG
|
||||
);
|
||||
} G_Common = {Directory};
|
||||
|
||||
static int eventToken;
|
||||
static int fileToken;
|
||||
static unsigned created;
|
||||
static unsigned referenced;
|
||||
static unsigned closed;
|
||||
static unsigned dereferenced;
|
||||
static unsigned reads;
|
||||
static unsigned waits;
|
||||
static int ioMode;
|
||||
static bool outstanding;
|
||||
static IO_STATUS_BLOCK* pendingIosb;
|
||||
static ULONG requested;
|
||||
static LONGLONG expectedOffset;
|
||||
|
||||
static NTSTATUS
|
||||
ZwCreateEvent(
|
||||
HANDLE* event,
|
||||
ACCESS_MASK access,
|
||||
OBJECT_ATTRIBUTES* attributes,
|
||||
int type,
|
||||
BOOLEAN signaled
|
||||
)
|
||||
{
|
||||
Check(
|
||||
access == EVENT_ALL_ACCESS && attributes->Attributes == OBJ_KERNEL_HANDLE && type == NotificationEvent &&
|
||||
!signaled,
|
||||
"private kernel event"
|
||||
);
|
||||
|
||||
if (ioMode == 4)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
++created;
|
||||
*event = &eventToken;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
ObReferenceObjectByHandle(
|
||||
HANDLE event,
|
||||
ACCESS_MASK access,
|
||||
void* type,
|
||||
int mode,
|
||||
void** object,
|
||||
void*
|
||||
)
|
||||
{
|
||||
Check(
|
||||
event == &eventToken && access == SYNCHRONIZE && type == *ExEventObjectType && mode == KernelMode,
|
||||
"reference own event"
|
||||
);
|
||||
|
||||
if (ioMode == 5)
|
||||
{
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
++referenced;
|
||||
*object = &eventToken;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
ZwClose(
|
||||
HANDLE event
|
||||
)
|
||||
{
|
||||
Check(event == &eventToken && !outstanding, "close only after completion");
|
||||
++closed;
|
||||
}
|
||||
|
||||
static void
|
||||
ObDereferenceObject(
|
||||
void* object
|
||||
)
|
||||
{
|
||||
Check(object == &eventToken && !outstanding, "dereference only after completion");
|
||||
++dereferenced;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
ZwReadFile(
|
||||
HANDLE file,
|
||||
HANDLE event,
|
||||
void*,
|
||||
void*,
|
||||
IO_STATUS_BLOCK* iosb,
|
||||
void*,
|
||||
ULONG size,
|
||||
LARGE_INTEGER* offset,
|
||||
void*
|
||||
)
|
||||
{
|
||||
++reads;
|
||||
Check(
|
||||
file == &fileToken && event == &eventToken && offset && offset->QuadPart == expectedOffset,
|
||||
"file and explicit offset"
|
||||
);
|
||||
Check(iosb->Status == 0 && iosb->Information == 0, "initialized IOSB");
|
||||
requested = size;
|
||||
|
||||
if (ioMode == 6)
|
||||
{
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
if (ioMode == 1 || ioMode == 2)
|
||||
{
|
||||
outstanding = true;
|
||||
pendingIosb = iosb;
|
||||
return STATUS_PENDING;
|
||||
}
|
||||
|
||||
iosb->Status = STATUS_SUCCESS;
|
||||
iosb->Information = ioMode == 3 ? size - 1 : size;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
KeWaitForSingleObject(
|
||||
void* object,
|
||||
int reason,
|
||||
int mode,
|
||||
BOOLEAN alertable,
|
||||
LARGE_INTEGER* timeout
|
||||
)
|
||||
{
|
||||
++waits;
|
||||
Check(
|
||||
object == &eventToken && reason == Executive && mode == KernelMode && !alertable && !timeout && outstanding,
|
||||
"noninterruptible per-request wait"
|
||||
);
|
||||
outstanding = false;
|
||||
pendingIosb->Status = ioMode == 2 ? STATUS_END_OF_FILE : STATUS_SUCCESS;
|
||||
pendingIosb->Information = ioMode == 2 ? 0 : requested;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#include "common-under-test.inc"
|
||||
|
||||
static std::vector<UCHAR>
|
||||
Image()
|
||||
{
|
||||
std::vector<UCHAR> image(4096);
|
||||
auto dos = (IMAGE_DOS_HEADER*)image.data();
|
||||
dos->e_magic = IMAGE_DOS_SIGNATURE;
|
||||
dos->e_lfanew = 128;
|
||||
auto nt = (IMAGE_NT_HEADERS64*)(image.data() + 128);
|
||||
nt->Signature = IMAGE_NT_SIGNATURE;
|
||||
nt->OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC;
|
||||
nt->OptionalHeader.SizeOfImage = (ULONG)image.size();
|
||||
auto exports = (IMAGE_EXPORT_DIRECTORY*)(image.data() + 512);
|
||||
exports->NumberOfFunctions = 3;
|
||||
exports->NumberOfNames = 2;
|
||||
exports->AddressOfFunctions = 801;
|
||||
exports->AddressOfNames = 901;
|
||||
exports->AddressOfNameOrdinals = 1001;
|
||||
const ULONG functions[] = {2000, 2100, 2200};
|
||||
const ULONG names[] = {1100, 1120};
|
||||
const USHORT ordinals[] = {2, 0};
|
||||
std::memcpy(image.data() + 801, functions, sizeof(functions));
|
||||
std::memcpy(image.data() + 901, names, sizeof(names));
|
||||
std::memcpy(image.data() + 1001, ordinals, sizeof(ordinals));
|
||||
std::memcpy(image.data() + 1100, "Beta", 5);
|
||||
std::memcpy(image.data() + 1120, "Alpha", 6);
|
||||
return image;
|
||||
}
|
||||
|
||||
static NTSTATUS
|
||||
Lookup(
|
||||
std::vector<UCHAR>& image,
|
||||
const char* name,
|
||||
PVOID* output
|
||||
)
|
||||
{
|
||||
ANSI_STRING string = {(USHORT)std::strlen(name), (USHORT)std::strlen(name), (PCHAR)name};
|
||||
exportPointer = image.data() + 512;
|
||||
exportBytes = 256;
|
||||
return DomitoFindExportedFunctionAddress(image.data(), &string, output);
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
auto image = Image();
|
||||
PVOID output = nullptr;
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_SUCCESS && output == image.data() + 2200, "ordinal-indexed EAT");
|
||||
Check(Lookup(image, "aLpHa", &output) == STATUS_SUCCESS && output == image.data() + 2000, "case-insensitive name");
|
||||
Check(Lookup(image, "Alpha", nullptr) == STATUS_SUCCESS, "optional output discovery");
|
||||
Check(Lookup(image, "missing", &output) == STATUS_NOT_FOUND && !output, "notfound clears output");
|
||||
auto exports = (IMAGE_EXPORT_DIRECTORY*)(image.data() + 512);
|
||||
const USHORT invalidOrdinal = 3;
|
||||
std::memcpy(image.data() + 1001, &invalidOrdinal, 2);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT && !output, "ordinal bounded");
|
||||
image = Image();
|
||||
const ULONG forwarder = 600;
|
||||
std::memcpy(image.data() + 801 + 8, &forwarder, 4);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_NOT_SUPPORTED && !output, "forwarder rejected");
|
||||
image = Image();
|
||||
const ULONG zero = 0;
|
||||
std::memcpy(image.data() + 801 + 8, &zero, 4);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_NOT_FOUND, "empty EAT slot");
|
||||
image = Image();
|
||||
const ULONG outside = 4096;
|
||||
std::memcpy(image.data() + 801 + 8, &outside, 4);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "function RVA bounded");
|
||||
image = Image();
|
||||
exports = (IMAGE_EXPORT_DIRECTORY*)(image.data() + 512);
|
||||
exports->NumberOfNames = 0xffffffff;
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "table multiplication bounded");
|
||||
image = Image();
|
||||
exports = (IMAGE_EXPORT_DIRECTORY*)(image.data() + 512);
|
||||
exports->AddressOfFunctions = 4090;
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "EAT span bounded");
|
||||
image = Image();
|
||||
const ULONG lateName = 4094;
|
||||
std::memcpy(image.data() + 901, &lateName, 4);
|
||||
image[4094] = 'x';
|
||||
image[4095] = 'x';
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "unterminated name bounded");
|
||||
image = Image();
|
||||
std::memcpy(image.data() + 901, &outside, 4);
|
||||
Check(Lookup(image, "Beta", &output) == STATUS_INVALID_IMAGE_FORMAT, "name RVA bounded");
|
||||
image = Image();
|
||||
ANSI_STRING name = {4, 4, (PCHAR) "Beta"};
|
||||
exportPointer = image.data() + 512;
|
||||
exportBytes = 4096;
|
||||
Check(
|
||||
DomitoFindExportedFunctionAddress(image.data(), &name, &output) == STATUS_INVALID_IMAGE_FORMAT,
|
||||
"export directory span bounded"
|
||||
);
|
||||
output = (void*)1;
|
||||
Check(
|
||||
DomitoFindExportedFunctionAddress(nullptr, &name, &output) == STATUS_INVALID_PARAMETER && !output,
|
||||
"invalid arguments clear output"
|
||||
);
|
||||
char buffer[32];
|
||||
|
||||
for (ioMode = 0; ioMode <= 6; ++ioMode)
|
||||
{
|
||||
created = referenced = closed = dereferenced = reads = waits = 0;
|
||||
outstanding = false;
|
||||
expectedOffset = 123;
|
||||
LARGE_INTEGER offset;
|
||||
offset.QuadPart = expectedOffset;
|
||||
NTSTATUS status = DomitoReadFileAt(&fileToken, buffer, sizeof(buffer), &offset);
|
||||
const NTSTATUS expected[] = {
|
||||
STATUS_SUCCESS,
|
||||
STATUS_SUCCESS,
|
||||
STATUS_END_OF_FILE,
|
||||
STATUS_END_OF_FILE,
|
||||
STATUS_INSUFFICIENT_RESOURCES,
|
||||
STATUS_ACCESS_DENIED,
|
||||
STATUS_ACCESS_DENIED
|
||||
};
|
||||
Check(status == expected[ioMode], "read status and exact-byte contract");
|
||||
Check(created == closed && referenced == dereferenced && !outstanding, "event lifetime balanced");
|
||||
Check(waits == ((ioMode == 1 || ioMode == 2) ? 1u : 0u), "wait pending only");
|
||||
}
|
||||
|
||||
ioMode = 0;
|
||||
expectedOffset = 0;
|
||||
Check(DomitoReadFile(&fileToken, buffer, sizeof(buffer)) == STATUS_SUCCESS, "zero-offset wrapper");
|
||||
LARGE_INTEGER offset = {};
|
||||
offset.QuadPart = -1;
|
||||
Check(
|
||||
DomitoReadFileAt(&fileToken, buffer, sizeof(buffer), &offset) == STATUS_INVALID_PARAMETER,
|
||||
"negative offset rejected"
|
||||
);
|
||||
Check(
|
||||
DomitoReadFileAt(&fileToken, buffer, sizeof(buffer), nullptr) == STATUS_INVALID_PARAMETER,
|
||||
"null offset rejected"
|
||||
);
|
||||
offset.QuadPart = 0;
|
||||
Check(DomitoReadFileAt(&fileToken, nullptr, 0, &offset) == STATUS_SUCCESS, "zero byte read");
|
||||
Check(DomitoReadFileAt(&fileToken, nullptr, 1, &offset) == STATUS_INVALID_PARAMETER, "null buffer rejected");
|
||||
std::printf("PASS: %u common-helper checks (mock kernel I/O)\n", checks);
|
||||
}
|
||||
Reference in new issue
Block a user