// PE image loader. This file owns image mapping, relocation, import resolution, and export lookup. #include #include #include #include #include #include #include #include #include #include #ifndef LDR_DEBUG_VERBOSE #define LDR_DEBUG_VERBOSE 0 #endif #if LDR_DEBUG_VERBOSE #define LdrDbg(...) DbgPrint(__VA_ARGS__) #else #define LdrDbg(...) ((void)0) #endif int _stricmp(const char* a, const char* b); // PE32+ structures (just what we need). #define PE_SIGNATURE 0x00004550UL // 'PE\0\0' #define IMAGE_FILE_MACHINE_AMD64 0x8664 #define IMAGE_FILE_MACHINE_I386 0x014c #define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b #define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b #pragma pack(push, 1) typedef struct { uint16_t Magic; uint8_t MajorLinkerVersion; uint8_t MinorLinkerVersion; uint32_t SizeOfCode; uint32_t SizeOfInitializedData; uint32_t SizeOfUninitializedData; uint32_t AddressOfEntryPoint; uint32_t BaseOfCode; uint64_t ImageBase; uint32_t SectionAlignment; uint32_t FileAlignment; uint16_t MajorOSVersion; uint16_t MinorOSVersion; uint16_t MajorImageVersion; uint16_t MinorImageVersion; uint16_t MajorSubsystemVersion; uint16_t MinorSubsystemVersion; uint32_t Win32VersionValue; uint32_t SizeOfImage; uint32_t SizeOfHeaders; uint32_t CheckSum; uint16_t Subsystem; uint16_t DllCharacteristics; uint64_t SizeOfStackReserve; uint64_t SizeOfStackCommit; uint64_t SizeOfHeapReserve; uint64_t SizeOfHeapCommit; uint32_t LoaderFlags; uint32_t NumberOfRvaAndSizes; struct { uint32_t VirtualAddress; uint32_t Size; } DataDirectory[16]; } OPT_HEADER64; typedef struct { uint32_t Signature; uint16_t Machine; uint16_t NumberOfSections; uint32_t TimeDateStamp; uint32_t PointerToSymbolTable; uint32_t NumberOfSymbols; uint16_t SizeOfOptionalHeader; uint16_t Characteristics; OPT_HEADER64 OptionalHeader; } PE_HEADER; typedef struct { char Name[8]; uint32_t VirtualSize; uint32_t VirtualAddress; uint32_t SizeOfRawData; uint32_t PointerToRawData; uint32_t PointerToRelocations; uint32_t PointerToLinenumbers; uint16_t NumberOfRelocations; uint16_t NumberOfLinenumbers; uint32_t Characteristics; } SECTION_HEADER; // Implements the align Pe Size operation. static size_t KiAlignPeSize(size_t value) { return (value + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); } // Implements the compute Required Image Size operation. static KSTATUS KiComputeRequiredImageSize(const PE_HEADER* pe, size_t fileSize, size_t* outSize) { if (!pe || !outSize) return NTLSTATUS_INVALID_PARAMETER; const OPT_HEADER64* opt = &pe->OptionalHeader; size_t required = KiAlignPeSize(opt->SizeOfImage); size_t headers = KiAlignPeSize(opt->SizeOfHeaders); if (headers > required) required = headers; const SECTION_HEADER* sec = (const SECTION_HEADER*)((const uint8_t*)&pe->OptionalHeader + pe->SizeOfOptionalHeader); for (int i = 0; i < pe->NumberOfSections; i++) { size_t va = (size_t)sec[i].VirtualAddress; size_t virt = (size_t)sec[i].VirtualSize; size_t raw = (size_t)sec[i].SizeOfRawData; size_t span = virt ? virt : raw; if (raw > span) span = raw; if (!span) continue; if (va > (size_t)-1 - span) return NTLSTATUS_NO_MEMORY; size_t end = KiAlignPeSize(va + span); if (end > required) required = end; if (raw) { size_t rawBase = (size_t)sec[i].PointerToRawData; if (rawBase > fileSize || raw > fileSize - rawBase) return NTLSTATUS_FAILURE; } } if (!required) return NTLSTATUS_FAILURE; *outSize = required; return NTLSTATUS_SUCCESS; } typedef struct { uint32_t Characteristics; uint32_t TimeDateStamp; uint16_t MajorVersion; uint16_t MinorVersion; uint32_t Name; uint32_t Base; uint32_t NumberOfFunctions; uint32_t NumberOfNames; uint32_t AddressOfFunctions; uint32_t AddressOfNames; uint32_t AddressOfNameOrdinals; } EXPORT_DIR; typedef struct { uint32_t OriginalFirstThunk; uint32_t TimeDateStamp; uint32_t ForwarderChain; uint32_t Name; uint32_t FirstThunk; } IMPORT_DESC; typedef struct { uint32_t VirtualAddress; uint32_t SizeOfBlock; } BASE_RELOC; #pragma pack(pop) #define IMAGE_REL_BASED_DIR64 10 #define IMAGE_SCN_CNT_CODE 0x00000020u #define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040u #define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080u #define IMAGE_SCN_MEM_EXECUTE 0x20000000u #define IMAGE_SCN_MEM_READ 0x40000000u #define IMAGE_SCN_MEM_WRITE 0x80000000u // Global kernel-module registry. Entries are allocated as modules arrive so boot-driver count is not an ABI limit. #define LDR_KERNEL_MODULE_NAME_MAX 64u typedef struct MODULE_ENTRY { char Name[LDR_KERNEL_MODULE_NAME_MAX]; uintptr_t Base; size_t Size; bool ExportsAvailable; struct MODULE_ENTRY* Next; } MODULE_ENTRY; static MODULE_ENTRY* gModules; // Ensures a user image page is privately writable before the kernel patches it. static KSTATUS KiEnsureUserImageWrite(PROCESS* process, bool user, uintptr_t address, size_t size) { if (!user) return NTLSTATUS_SUCCESS; return MmVadMakeImageRangePrivate(process, (void*)address, size); } // Allocates and inserts one global kernel-module registry entry. static KSTATUS KiRegisterGlobalModule(const char* name, uintptr_t base, size_t size, bool exportsAvailable) { if (!name || !*name || !base || !size) return NTLSTATUS_INVALID_PARAMETER; size_t nameLength = 0; while (nameLength < LDR_KERNEL_MODULE_NAME_MAX && name[nameLength]) nameLength++; if (nameLength == 0 || nameLength >= LDR_KERNEL_MODULE_NAME_MAX || !SysSvIsUtf8Text(SysSvFromParts(name, nameLength))) return NTLSTATUS_INVALID_PARAMETER; MODULE_ENTRY* entry = (MODULE_ENTRY*)MmAllocatePool(sizeof(MODULE_ENTRY)); if (!entry) return NTLSTATUS_NO_MEMORY; memset(entry, 0, sizeof(*entry)); memcpy(entry->Name, name, nameLength); entry->Name[nameLength] = '\0'; entry->Base = base; entry->Size = size; entry->ExportsAvailable = exportsAvailable; entry->Next = gModules; gModules = entry; return NTLSTATUS_SUCCESS; } // Registers the two fixed bootstrap images described explicitly by BOOT_INFO. KSTATUS LdrRegisterModulesFromBootInfo(const BOOT_INFO* bootInfo) { if (!bootInfo) return NTLSTATUS_INVALID_PARAMETER; KSTATUS status = KiRegisterGlobalModule("kernel.exe", bootInfo->KernelImage.VirtualBase, bootInfo->KernelImage.ImageSize, true); if (!KSUCCESS(status)) return status; return KiRegisterGlobalModule("hal.dll", bootInfo->HalImage.VirtualBase, bootInfo->HalImage.ImageSize, true); } // Registers one loaded kernel module without imposing a fixed registry capacity. KSTATUS LdrRegisterModule(const char* name, uintptr_t base) { size_t size = base ? LdrGetImageSize(base) : 0; return KiRegisterGlobalModule(name, base, size, false); } // Makes an initialized module's exports visible to future import resolution. // Failed images remain mapped for diagnostics but must not provide exports. KSTATUS LdrSetModuleExportsAvailable(const char* name, bool available) { if (!name) return NTLSTATUS_INVALID_PARAMETER; for (MODULE_ENTRY* entry = gModules; entry; entry = entry->Next) { if (_stricmp(entry->Name, name) == 0) { entry->ExportsAvailable = available; return NTLSTATUS_SUCCESS; } } return NTLSTATUS_NOT_FOUND; } // Finds the loaded kernel module that owns an address. const char* LdrFindKernelModuleByAddress(uintptr_t address, uintptr_t* baseOut, size_t* sizeOut) { if (baseOut) *baseOut = 0; if (sizeOut) *sizeOut = 0; for (MODULE_ENTRY* module = gModules; module; module = module->Next) { uintptr_t base = module->Base; size_t size = module->Size; if (!base || !size || base > UINTPTR_MAX - size) continue; if (address >= base && address < base + size) { if (baseOut) *baseOut = base; if (sizeOut) *sizeOut = size; return module->Name; } } return "unknown"; } // Returns the loaded global kernel module base for one exact module name. static uintptr_t KiFindGlobalModuleBase(const char* name) { if (!name) return 0; for (MODULE_ENTRY* module = gModules; module; module = module->Next) { if (module->ExportsAvailable && _stricmp(module->Name, name) == 0) return module->Base; } return 0; } // Returns export At Base. static void* KiGetExportAtBase(uintptr_t base, const char* functionName) { if (!base) return (void*)0; uint32_t peOffset = *(uint32_t*)(base + 0x3C); PE_HEADER* pe = (PE_HEADER*)(base + peOffset); if (pe->Signature != PE_SIGNATURE) return (void*)0; OPT_HEADER64* opt = &pe->OptionalHeader; if (opt->NumberOfRvaAndSizes < 1) return (void*)0; uint32_t expRva = opt->DataDirectory[0].VirtualAddress; if (!expRva) return (void*)0; EXPORT_DIR* exp = (EXPORT_DIR*)(base + expRva); uint32_t* names = (uint32_t*)(base + exp->AddressOfNames); uint16_t* ordinals = (uint16_t*)(base + exp->AddressOfNameOrdinals); uint32_t* functions = (uint32_t*)(base + exp->AddressOfFunctions); for (uint32_t i = 0; i < exp->NumberOfNames; i++) { const char* name = (const char*)(base + names[i]); if (strcmp(name, functionName) == 0) { uint16_t ord = ordinals[i]; return (void*)(base + functions[ord]); } } return (void*)0; } // Resolves a named export without depending on the image's registration name. void* LdrGetImageExport(uintptr_t imageBase, const char* functionName) { return functionName ? KiGetExportAtBase(imageBase, functionName) : (void*)0; } // Export lookup. void* LdrGetExport(const char* moduleName, const char* functionName) { uintptr_t base = KiFindGlobalModuleBase(moduleName); return KiGetExportAtBase(base, functionName); } // Implements the ldr Get Image Size operation. size_t LdrGetImageSize(uintptr_t base) { if (!base) return 0; uint32_t peOffset = *(uint32_t*)(base + 0x3C); PE_HEADER* pe = (PE_HEADER*)(base + peOffset); if (pe->Signature != PE_SIGNATURE) return 0; return (size_t)pe->OptionalHeader.SizeOfImage; } // Implements the ldr Get Mapped Image Size operation. size_t LdrGetMappedImageSize(PROCESS* process, uintptr_t base) { if (!base) return 0; if (process) { size_t regionSize = MmVadGetSize(process, (const void*)base); if (regionSize) return regionSize; } size_t globalRegionSize = MmVadGetSizeByBase((const void*)base); if (globalRegionSize) return globalRegionSize; return LdrGetImageSize(base); } // Releases loaded Image For Process. static void KiFreeLoadedImageForProcess(PROCESS* process, uintptr_t imageBase, bool user) { if (!imageBase) return; size_t imageSize = user ? LdrGetMappedImageSize(process, imageBase) : LdrGetImageSize(imageBase); if (user) MmFreeUserImageForProcess(process, (void*)imageBase, imageSize); else MmFreePool((void*)imageBase); } // Returns the final page flags for a PE section. static uint64_t KiSectionPageProtection(const SECTION_HEADER* section, bool user) { uint64_t prot = PAGE_PRESENT | (user ? PAGE_USER : 0); // Some old toolchains mark BSS-like sections as uninitialized data without // setting MEM_WRITE. Treating those pages as read-only breaks programs that // legitimately write their zero-fill storage. if ((section->Characteristics & IMAGE_SCN_MEM_WRITE) || (section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA)) { prot |= PAGE_WRITE; } if (!((section->Characteristics & IMAGE_SCN_MEM_EXECUTE) || (section->Characteristics & IMAGE_SCN_CNT_CODE))) { prot |= PAGE_NX; } return prot; } // Applies final page protections to a mapped PE image. KSTATUS LdrProtectImageForProcess(PROCESS* process, uintptr_t imageBase, bool user) { if (!imageBase) return NTLSTATUS_INVALID_PARAMETER; if (user && !process) return NTLSTATUS_INVALID_PARAMETER; size_t mappedSize = user ? LdrGetMappedImageSize(process, imageBase) : LdrGetImageSize(imageBase); if (!mappedSize) return NTLSTATUS_FAILURE; uint32_t peOffset = *(uint32_t*)(imageBase + 0x3C); if ((size_t)peOffset > mappedSize || mappedSize - (size_t)peOffset < sizeof(uint32_t) + 20) return NTLSTATUS_FAILURE; PE_HEADER* pe = (PE_HEADER*)(imageBase + peOffset); if (pe->Signature != PE_SIGNATURE) return NTLSTATUS_FAILURE; size_t sectionTableOffset = (size_t)peOffset + 24u + (size_t)pe->SizeOfOptionalHeader; size_t sectionTableSize = (size_t)pe->NumberOfSections * sizeof(SECTION_HEADER); if (sectionTableOffset > mappedSize || sectionTableSize > mappedSize - sectionTableOffset) return NTLSTATUS_FAILURE; size_t pageCount = (mappedSize + PAGE_SIZE - 1) / PAGE_SIZE; uint64_t* pageProt = (uint64_t*)MmAllocatePoolTag(pageCount * sizeof(uint64_t), MM_POOL_TAG('i', 'p', 'r', 't')); if (!pageProt) return NTLSTATUS_NO_MEMORY; uint64_t headerProt = PAGE_PRESENT | (user ? PAGE_USER : 0) | PAGE_NX; for (size_t i = 0; i < pageCount; ++i) pageProt[i] = headerProt; SECTION_HEADER* sec = (SECTION_HEADER*)(imageBase + sectionTableOffset); for (int i = 0; i < pe->NumberOfSections; i++) { size_t protectSize = sec[i].VirtualSize ? sec[i].VirtualSize : sec[i].SizeOfRawData; if (!protectSize) continue; uintptr_t secBase = imageBase + (uintptr_t)sec[i].VirtualAddress; uintptr_t secEnd = secBase + protectSize; uintptr_t mapEnd = imageBase + mappedSize; if (secBase < imageBase || secBase >= mapEnd) { LdrDbg("LDR: protect skipping out-of-range section %d base=%016llx size=%llx image=%016llx mapped=%llx\n", i, (unsigned long long)secBase, (unsigned long long)protectSize, (unsigned long long)imageBase, (unsigned long long)mappedSize); continue; } if (secEnd < secBase) { MmFreePool(pageProt); return NTLSTATUS_NO_MEMORY; } if (secEnd > mapEnd) { LdrDbg("LDR: protect clamping section %d end=%016llx to mapEnd=%016llx\n", i, (unsigned long long)secEnd, (unsigned long long)mapEnd); secEnd = mapEnd; } size_t firstPage = (size_t)((secBase - imageBase) / PAGE_SIZE); size_t lastPage = (size_t)((secEnd - 1 - imageBase) / PAGE_SIZE); uint64_t prot = KiSectionPageProtection(&sec[i], user); for (size_t page = firstPage; page <= lastPage && page < pageCount; ++page) { // Protection is page-granular. If sections ever share a page, the // hardware page must allow the union of their access requirements. if (prot & PAGE_WRITE) pageProt[page] |= PAGE_WRITE; if ((prot & PAGE_NX) == 0) pageProt[page] &= ~PAGE_NX; } } size_t runStart = 0; KSTATUS s = NTLSTATUS_SUCCESS; while (runStart < pageCount) { uint64_t prot = pageProt[runStart]; size_t runEnd = runStart + 1; while (runEnd < pageCount && pageProt[runEnd] == prot) runEnd++; uintptr_t va = imageBase + runStart * PAGE_SIZE; size_t bytes = (runEnd - runStart) * PAGE_SIZE; if (user) s = MmVadProtect(process, (void*)va, bytes, prot); else s = MmPteProtectRange(va, bytes, prot); if (!KSUCCESS(s)) { LdrDbg("LDR: protect failed va=%016llx size=%llx prot=%016llx st=%d image=%016llx mapped=%llx\n", (unsigned long long)va, (unsigned long long)bytes, (unsigned long long)prot, s, (unsigned long long)imageBase, (unsigned long long)mappedSize); break; } runStart = runEnd; } MmFreePool(pageProt); return s; } // Entry point lookup. uintptr_t LdrGetEntryPoint(uintptr_t base) { uint32_t pe_offset = *(uint32_t*)(base + 0x3C); uint32_t entry_rva = *(uint32_t*)(base + pe_offset + 0x28); return base + entry_rva; } // Base Relocation. static KSTATUS LdrApplyRelocationsForProcess(PROCESS* process, uintptr_t base, uintptr_t delta, bool user) { uint32_t peOffset = *(uint32_t*)(base + 0x3C); PE_HEADER* pe = (PE_HEADER*)(base + peOffset); OPT_HEADER64* opt = &pe->OptionalHeader; if (opt->NumberOfRvaAndSizes < 6) return NTLSTATUS_SUCCESS; uint32_t relocRva = opt->DataDirectory[5].VirtualAddress; uint32_t relocSize = opt->DataDirectory[5].Size; if (!relocRva || !relocSize) return NTLSTATUS_SUCCESS; BASE_RELOC* block = (BASE_RELOC*)(base + relocRva); uintptr_t end = (uintptr_t)block + relocSize; while ((uintptr_t)block < end && block->SizeOfBlock > 0) { uint32_t count = (block->SizeOfBlock - sizeof(BASE_RELOC)) / sizeof(uint16_t); uint16_t* entries = (uint16_t*)(block + 1); for (uint32_t i = 0; i < count; i++) { int type = entries[i] >> 12; int offset = entries[i] & 0xFFF; // For 64-bit PEs, we mostly care about DIR64 (Type 10). if (type == IMAGE_REL_BASED_DIR64) { uint64_t* ptr = (uint64_t*)(base + block->VirtualAddress + offset); KSTATUS ws = KiEnsureUserImageWrite(process, user, (uintptr_t)ptr, sizeof(*ptr)); if (!KSUCCESS(ws)) return ws; *ptr += delta; } } block = (BASE_RELOC*)((uint8_t*)block + block->SizeOfBlock); } return NTLSTATUS_SUCCESS; } // Resolves imports for kernel images against the kernel module registry. KSTATUS LdrResolveImports(uintptr_t imageBase) { uint32_t peOffset = *(uint32_t*)(imageBase + 0x3C); PE_HEADER* pe = (PE_HEADER*)(imageBase + peOffset); OPT_HEADER64* opt = &pe->OptionalHeader; if (opt->NumberOfRvaAndSizes < 2) return NTLSTATUS_SUCCESS; uint32_t impRva = opt->DataDirectory[1].VirtualAddress; if (!impRva) return NTLSTATUS_SUCCESS; IMPORT_DESC* imp = (IMPORT_DESC*)(imageBase + impRva); while (imp->Name) { const char* dllName = (const char*)(imageBase + imp->Name); LdrDbg("LDR: resolve kernel import dll=%s image=%016llx\n", dllName, (unsigned long long)imageBase); uint64_t* thunk = (uint64_t*)(imageBase + imp->FirstThunk); uint64_t* origThunk = (uint64_t*)(imageBase + (imp->OriginalFirstThunk ? imp->OriginalFirstThunk : imp->FirstThunk)); while (*origThunk) { if (*origThunk & (1ULL << 63)) { DbgPrint("LdrResolveImports: Ordinal imports not supported! (%s)\n", dllName); return NTLSTATUS_NOT_SUPPORTED; } const char* funcName = (const char*)(imageBase + (*origThunk & 0xFFFFFFFF) + 2); void* addr = LdrGetExport(dllName, funcName); if (!addr) { DbgPrint("LdrResolveImports: Function not found! (%s!%s)\n", dllName, funcName); return NTLSTATUS_NOT_FOUND; } *thunk = (uint64_t)addr; thunk++; origThunk++; } imp++; } return NTLSTATUS_SUCCESS; } // Requires an already-mapped bootstrap image to have no import directory. KSTATUS LdrRequireNoImports(uintptr_t imageBase) { if (!imageBase) return NTLSTATUS_INVALID_PARAMETER; uint32_t peOffset = *(uint32_t*)(imageBase + 0x3C); PE_HEADER* pe = (PE_HEADER*)(imageBase + peOffset); if (pe->Signature != PE_SIGNATURE || pe->Machine != IMAGE_FILE_MACHINE_AMD64 || pe->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) return NTLSTATUS_FAILURE; if (pe->OptionalHeader.NumberOfRvaAndSizes >= 2 && (pe->OptionalHeader.DataDirectory[1].VirtualAddress != 0 || pe->OptionalHeader.DataDirectory[1].Size != 0)) return NTLSTATUS_NOT_SUPPORTED; if (pe->OptionalHeader.NumberOfRvaAndSizes >= 14 && (pe->OptionalHeader.DataDirectory[13].VirtualAddress != 0 || pe->OptionalHeader.DataDirectory[13].Size != 0)) return NTLSTATUS_NOT_SUPPORTED; return NTLSTATUS_SUCCESS; } // Loads a user image from an open file through the image-section object model. KSTATUS LdrLoadImageFileForProcess(PROCESS* process, FILE_OBJECT* file, uintptr_t* baseOut) { if (!process || !file || !baseOut) return NTLSTATUS_INVALID_PARAMETER; *baseOut = 0; IMAGE_SECTION_OBJECT* section = (IMAGE_SECTION_OBJECT*)0; KSTATUS s = LdrCreateImageSectionFromFile(file, §ion); if (!KSUCCESS(s)) return s; void* view = (void*)0; s = LdrMapImageSection(process, section, &view); if (!KSUCCESS(s)) { LdrDereferenceImageSection(section); return s; } const IMAGE_SECTION_METADATA* metadata = LdrImageSectionMetadata(section); uintptr_t imageBase = (uintptr_t)view; uintptr_t delta = imageBase - metadata->PreferredBase; if (delta != 0) { s = LdrApplyRelocationsForProcess(process, imageBase, delta, true); if (!KSUCCESS(s)) { KiFreeLoadedImageForProcess(process, imageBase, true); LdrDereferenceImageSection(section); return s; } } LdrDereferenceImageSection(section); *baseOut = imageBase; return NTLSTATUS_SUCCESS; } // Loads a boot/kernel PE image from a raw memory buffer into kernel space. static KSTATUS KiLdrLoadKernelImageFromBuffer(const void* buffer, size_t size, uintptr_t* baseOut) { if (!buffer || !size || !baseOut) return NTLSTATUS_INVALID_PARAMETER; if (size < 0x40) return NTLSTATUS_FAILURE; const uint8_t* bytes = (const uint8_t*)buffer; uint32_t peOffset = *(const uint32_t*)(bytes + 0x3C); if ((size_t)peOffset > size || size - (size_t)peOffset < 24) return NTLSTATUS_FAILURE; uint32_t signature = *(const uint32_t*)(bytes + peOffset); if (signature != PE_SIGNATURE) return NTLSTATUS_FAILURE; uint16_t machine = *(const uint16_t*)(bytes + peOffset + 4); uint16_t optionalSize = *(const uint16_t*)(bytes + peOffset + 20); if (optionalSize < sizeof(OPT_HEADER64) || (size_t)peOffset + 24 + optionalSize > size) { return NTLSTATUS_FAILURE; } uint16_t optionalMagic = *(const uint16_t*)(bytes + peOffset + 24); if (machine == IMAGE_FILE_MACHINE_I386 || optionalMagic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { DbgPrint("LdrLoadKernelImage: hard error: 32-bit PE images are not supported (machine=0x%X magic=0x%X)\n", machine, optionalMagic); return NTLSTATUS_NOT_SUPPORTED; } if (machine != IMAGE_FILE_MACHINE_AMD64 || optionalMagic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { DbgPrint("LdrLoadKernelImage: unsupported PE image (machine=0x%X magic=0x%X)\n", machine, optionalMagic); return NTLSTATUS_NOT_SUPPORTED; } PE_HEADER* pe = (PE_HEADER*)(bytes + peOffset); OPT_HEADER64* opt = &pe->OptionalHeader; size_t imageSize = 0; KSTATUS sizeStatus = KiComputeRequiredImageSize(pe, size, &imageSize); if (!KSUCCESS(sizeStatus)) return sizeStatus; LdrDbg("LDR: kernel image header sizeOfImage=%x required=%llx preferred=%016llx sections=%u\n", opt->SizeOfImage, (unsigned long long)imageSize, (unsigned long long)opt->ImageBase, pe->NumberOfSections); void* mem = MmAllocatePoolTag(imageSize, MM_POOL_TAG('i', 'm', 'g', ' ')); if (!mem) { DbgPrint("LDR: kernel image allocation failed required=%llx\n", (unsigned long long)imageSize); return NTLSTATUS_NO_MEMORY; } memset(mem, 0, imageSize); memcpy(mem, buffer, opt->SizeOfHeaders); SECTION_HEADER* sec = (SECTION_HEADER*)((uint8_t*)&pe->OptionalHeader + pe->SizeOfOptionalHeader); for (int i = 0; i < pe->NumberOfSections; i++) { void* dst = (uint8_t*)mem + sec[i].VirtualAddress; const void* src = (const uint8_t*)buffer + sec[i].PointerToRawData; if (sec[i].SizeOfRawData) { size_t copySize = sec[i].SizeOfRawData; if (sec[i].VirtualSize && sec[i].VirtualSize < copySize) copySize = sec[i].VirtualSize; memcpy(dst, src, copySize); } MmPteProtectRange((uintptr_t)mem + sec[i].VirtualAddress, sec[i].VirtualSize ? sec[i].VirtualSize : sec[i].SizeOfRawData, PAGE_PRESENT | PAGE_WRITE); } uintptr_t delta = (uintptr_t)mem - opt->ImageBase; if (delta != 0) { KSTATUS rs = LdrApplyRelocationsForProcess((PROCESS*)0, (uintptr_t)mem, delta, false); if (!KSUCCESS(rs)) { MmFreePool(mem); return rs; } } // Kernel drivers are still pool-backed images. Section protections cannot // safely make pages read-only yet because imports and driver-owned writable // image data are not separated from callback code by a section object. What // must be true is simpler: every page in the loaded image must be executable // enough for driver callbacks, including section-alignment slack near the // end of the image. KSTATUS xs = MmPteProtectRange((uintptr_t)mem, imageSize, PAGE_PRESENT | PAGE_WRITE); if (!KSUCCESS(xs)) { MmFreePool(mem); return xs; } *baseOut = (uintptr_t)mem; return NTLSTATUS_SUCCESS; } // Loads a kernel image from a bootloader or driver raw buffer. KSTATUS LdrLoadKernelImage(const void* buffer, size_t size, uintptr_t* baseOut) { return KiLdrLoadKernelImageFromBuffer(buffer, size, baseOut); }