// Tests extract the unchanged production bodies. Kernel I/O is simulated. #define WIN32_NO_STATUS #include #undef WIN32_NO_STATUS #include #include #include #include #include #include #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, 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, fileToken; static unsigned created, referenced, closed, dereferenced, reads, 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 Image() { std::vector 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}, 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& 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); }