// User-mode regression harness. The runner extracts the production functions // unchanged; pool, file-I/O, and CI calls are test doubles. BCrypt is real CNG. #define WIN32_NO_STATUS #include #undef WIN32_NO_STATUS #include #include #include #include #include #include #include #define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0) #define ASSERT(x) Check(!!(x), #x) #define DOMITO_POOL_TAG 0x446f6d69 #define NonPagedPoolNx 0 #define PagedPool 1 #define PAGED_CODE() ((void)0) #define MINCRYPT_SHA1_LENGTH 20 #define MINCRYPT_SHA256_LENGTH 32 typedef const UCHAR* PCUCHAR; static unsigned checks, allocations, ciCalls, hashCalls; static int failOperation, failHashCall; static ULONG_PTR bytesRead; static void Check(bool ok, const char* message) { ++checks; if (!ok) { std::fprintf(stderr, "FAIL: %s\n", message); std::exit(1); } } static PVOID ExAllocatePoolWithTag(int, SIZE_T size, ULONG) { if (failOperation == 4) return nullptr; void* p = HeapAlloc(GetProcessHeap(), 0, size); if (p) ++allocations; return p; } static void ExFreePoolWithTag(PVOID p, ULONG) { Check(p != nullptr && allocations != 0, "allocation ownership"); --allocations; Check(HeapFree(GetProcessHeap(), 0, p) != FALSE, "heap free"); } static NTSTATUS TestCreateHash(BCRYPT_ALG_HANDLE a, BCRYPT_HASH_HANDLE* h, PUCHAR o, ULONG os, PUCHAR s, ULONG ss, ULONG f) { if (failOperation == 1) return STATUS_UNSUCCESSFUL; return BCryptCreateHash(a, h, o, os, s, ss, f); } static NTSTATUS TestHashData(BCRYPT_HASH_HANDLE h, PUCHAR b, ULONG n, ULONG f) { ++hashCalls; if (failOperation == 2 && hashCalls == (unsigned)failHashCall) return STATUS_UNSUCCESSFUL; return BCryptHashData(h, b, n, f); } static NTSTATUS TestFinishHash(BCRYPT_HASH_HANDLE h, PUCHAR b, ULONG n, ULONG f) { if (failOperation == 3) return STATUS_UNSUCCESSFUL; return BCryptFinishHash(h, b, n, f); } // Minimal dependencies for the legacy wrapper; no actual CI code is invoked. struct MINCRYPT_POLICY_INFO { ULONG Size; }; struct KAPC_STATE { ULONG unused; }; struct IO_STATUS_BLOCK { NTSTATUS Status; ULONG_PTR Information; }; static PVOID PsInitialSystemProcess = nullptr; static void CiFreePolicyInfo(MINCRYPT_POLICY_INFO* p) { p->Size = 0; } static void KeStackAttachProcess(PVOID, KAPC_STATE*) {} static void KeUnstackDetachProcess(KAPC_STATE*) {} static NTSTATUS ZwReadFile(HANDLE, PVOID, PVOID, PVOID, IO_STATUS_BLOCK* io, PVOID buffer, ULONG size, LARGE_INTEGER*, PVOID) { io->Status = STATUS_SUCCESS; io->Information = bytesRead; std::memset(buffer, 0, (size_t)min(bytesRead, (ULONG_PTR)size)); return STATUS_SUCCESS; } static NTSTATUS ZwWaitForSingleObject(HANDLE, BOOLEAN, PVOID) { return STATUS_SUCCESS; } static NTSTATUS CiCheckSignedFile(PVOID, UINT32, ALG_ID, PVOID, UINT32, MINCRYPT_POLICY_INFO*, LARGE_INTEGER*, MINCRYPT_POLICY_INFO*) { ++ciCalls; return STATUS_SUCCESS; } static NTSTATUS CiVerifyHashInCatalog(PVOID, UINT32, ALG_ID, BOOLEAN, UINT32, UINT32, MINCRYPT_POLICY_INFO*, PVOID, LARGE_INTEGER*, MINCRYPT_POLICY_INFO*) { ++ciCalls; return STATUS_SUCCESS; } #define BCryptCreateHash TestCreateHash #define BCryptHashData TestHashData #define BCryptFinishHash TestFinishHash #include "digest-under-test.inc" #undef BCryptCreateHash #undef BCryptHashData #undef BCryptFinishHash static std::vector Image(bool pe64, ULONG nt = 128, bool certificate = false) { std::vector b((size_t)nt + 896 + (certificate ? 32 : 0), 0); IMAGE_DOS_HEADER dos = {}; dos.e_magic = IMAGE_DOS_SIGNATURE; dos.e_lfanew = (LONG)nt; std::memcpy(b.data(), &dos, sizeof(dos)); ULONG signature = IMAGE_NT_SIGNATURE; std::memcpy(b.data() + nt, &signature, sizeof(signature)); IMAGE_FILE_HEADER f = {}; f.NumberOfSections = 1; f.SizeOfOptionalHeader = pe64 ? sizeof(IMAGE_OPTIONAL_HEADER64) : sizeof(IMAGE_OPTIONAL_HEADER32); std::memcpy(b.data() + nt + 4, &f, sizeof(f)); IMAGE_OPTIONAL_HEADER64 h64 = {}; IMAGE_OPTIONAL_HEADER32 h32 = {}; h64.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC; h32.Magic = IMAGE_NT_OPTIONAL_HDR32_MAGIC; h64.NumberOfRvaAndSizes = h32.NumberOfRvaAndSizes = 16; h64.SizeOfHeaders = h32.SizeOfHeaders = nt + 384; if (certificate) { h64.DataDirectory[4] = h32.DataDirectory[4] = { nt + 896, 32 }; ULONG length = 32; std::memcpy(b.data() + nt + 896, &length, sizeof(length)); } std::memcpy(b.data() + nt + 24, pe64 ? (void*)&h64 : (void*)&h32, f.SizeOfOptionalHeader); IMAGE_SECTION_HEADER section = {}; section.PointerToRawData = nt + 384; section.SizeOfRawData = 512; std::memcpy(b.data() + nt + 24 + f.SizeOfOptionalHeader, §ion, sizeof(section)); for (ULONG i = nt + 384; i < nt + 896; ++i) b[i] = (UCHAR)(i * 31); return b; } static void Set32(std::vector& b, size_t at, ULONG value) { std::memcpy(b.data() + at, &value, sizeof(value)); } static void Set16(std::vector& b, size_t at, USHORT value) { std::memcpy(b.data() + at, &value, sizeof(value)); } static std::vector Digest(PUCHAR b, ULONG size, NTSTATUS expected = STATUS_SUCCESS) { UINT32 algorithm = 0xdead; ULONG length = 0xdead, certSize = 0xdead; PVOID digest = (void*)1; LPWIN_CERTIFICATE cert = (LPWIN_CERTIFICATE)1; hashCalls = 0; NTSTATUS s = DomitoCalculatePortableExecutableDigest(b, size, &algorithm, &length, &digest, &cert, &certSize); Check(s == expected, "digest status"); std::vector result; if (NT_SUCCESS(s)) { Check(algorithm == CALG_SHA1 && length == 20 && digest != nullptr, "SHA1 output"); Check((cert == nullptr) == (certSize == 0), "certificate outputs agree"); if (cert) Check((PUCHAR)cert >= b && (PUCHAR)cert + certSize <= b + size, "borrowed certificate span"); result.assign((PUCHAR)digest, (PUCHAR)digest + length); DomitoFreePortableExecutableDigest(digest); } else { Check(!algorithm && !length && !digest && !cert && !certSize, "failure outputs cleared"); } Check(allocations == 0, "no leaked pool allocations"); return result; } static std::vector Reference(const std::vector& b, ULONG checksum, ULONG securitySlot, ULONG end) { // Explicit fixture ranges, independent of the production parser. std::vector bytes(b.begin(), b.begin() + checksum); bytes.insert(bytes.end(), b.begin() + checksum + 4, b.begin() + securitySlot); if (securitySlot != end) bytes.insert(bytes.end(), b.begin() + securitySlot + 8, b.begin() + end); std::vector digest(20); BCRYPT_ALG_HANDLE algorithm = nullptr; Check(NT_SUCCESS(BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_SHA1_ALGORITHM, nullptr, 0)), "reference algorithm"); Check(NT_SUCCESS(BCryptHash(algorithm, nullptr, 0, bytes.data(), (ULONG)bytes.size(), digest.data(), 20)), "reference digest"); BCryptCloseAlgorithmProvider(algorithm, 0); return digest; } static void GuardedInvalid(const std::vector& b, NTSTATUS expected) { SYSTEM_INFO si; GetSystemInfo(&si); const SIZE_T span = ((b.size() + si.dwPageSize - 1) / si.dwPageSize) * si.dwPageSize; UCHAR* region = (UCHAR*)VirtualAlloc(nullptr, span + si.dwPageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); Check(region != nullptr, "guard allocation"); DWORD old; Check(VirtualProtect(region + span, si.dwPageSize, PAGE_NOACCESS, &old) != FALSE, "guard protection"); UCHAR* input = region + span - b.size(); std::memcpy(input, b.data(), b.size()); Digest(input, (ULONG)b.size(), expected); VirtualFree(region, 0, MEM_RELEASE); } int main() { for (bool pe64 : {false, true}) { for (bool certificate : {false, true}) { auto b = Image(pe64, 128, certificate); auto result = Digest(b.data(), (ULONG)b.size()); Check(result == Reference(b, 216, pe64 ? 296 : 280, 1024), "fixture digest equals independent CNG reference"); Set32(b, 216, 0x12345678); Check(result == Digest(b.data(), (ULONG)b.size()), "checksum excluded"); if (certificate) { b.back() ^= 0x55; Check(result == Digest(b.data(), (ULONG)b.size()), "certificate contents excluded"); } } } auto large = Image(true, 4 * 1024 * 1024); Check(Digest(large.data(), (ULONG)large.size()) == Reference(large, 4194392, 4194472, (ULONG)large.size()), "large DOS stub"); auto b = Image(true); for (ULONG size = 0; size < 392; ++size) { std::vector prefix(b.begin(), b.begin() + size); if (size == 0) Digest(b.data(), 0, STATUS_INVALID_IMAGE_NOT_MZ); else GuardedInvalid(prefix, size < 64 ? STATUS_INVALID_IMAGE_NOT_MZ : STATUS_INVALID_IMAGE_FORMAT); } auto bad = b; Set32(bad, 60, 0xffffffff); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = b; Set32(bad, 60, 0x7fffffff); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = b; Set16(bad, 148, 1); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = b; Set16(bad, 152, 0x999); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = b; Set32(bad, 260, 17); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); for (ULONG offset : {200UL, 2048UL, 0xffffffffUL}) { bad = b; Set32(bad, 296, offset); Set32(bad, 300, 32); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); } bad = b; Set32(bad, 300, 32); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = b; Set32(bad, 296, 800); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = b; Set32(bad, 296, 800); Set32(bad, 300, 0xffffffff); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = Image(true, 128, true); Set32(bad, 1024, 33); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = Image(true, 128, true); Set32(bad, 1024, 7); GuardedInvalid(bad, STATUS_INVALID_IMAGE_FORMAT); bad = Image(true); Set32(bad, 260, 0); // No directory entry: don't exclude its physical bytes. Check(Digest(bad.data(), (ULONG)bad.size()) == Reference(bad, 216, 1024, 1024), "absent security directory"); for (int failure : {1, 2, 3, 4}) { failOperation = failure; for (int call = 1; call <= (failure == 2 ? 3 : 1); ++call) { failHashCall = call; Digest(b.data(), (ULONG)b.size(), failure == 4 ? STATUS_INSUFFICIENT_RESOURCES : STATUS_UNSUCCESSFUL); } } failOperation = 0; Digest(nullptr, 1024, STATUS_INVALID_PARAMETER); IMAGE_DATA_DIRECTORY directory = {1024, 32}; MINCRYPT_POLICY_INFO policy = {}, timestamp = {}; LARGE_INTEGER signing = {}; UCHAR hash[20] = {}; bytesRead = 16; ciCalls = 0; Check(DomitoValidateFileLegacyMode(nullptr, hash, 20, CALG_SHA1, &directory, &policy, &signing, ×tamp) == STATUS_END_OF_FILE, "short certificate read rejected"); Check(ciCalls == 0 && allocations == 0, "short read does not reach CI or leak"); bytesRead = 32; Check(NT_SUCCESS(DomitoValidateFileLegacyMode(nullptr, hash, 20, CALG_SHA1, &directory, &policy, &signing, ×tamp)), "complete certificate read accepted"); Check(ciCalls == 1 && allocations == 0, "complete read reaches CI once"); std::printf("PASS: %u checks (guarded malformed inputs, PE32/PE32+, CNG digests, failure cleanup, short reads).\n", checks); }