// ldr_image.c - PE32+ loader and export resolver #include #include #include #include #include #include // --------------------------------------------------------------------------- // PE32+ structures (just what we need) // --------------------------------------------------------------------------- #define PE_SIGNATURE 0x00004550UL // 'PE\0\0' #define IMAGE_FILE_MACHINE_AMD64 0x8664 #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; 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 // --------------------------------------------------------------------------- // Module registry // --------------------------------------------------------------------------- #define MAX_MODULES 32 typedef struct { char Name[32]; uintptr_t Base; } MODULE_ENTRY; static MODULE_ENTRY gModules[MAX_MODULES]; static int gModuleCount = 0; void LdrRegisterModulesFromBootInfo(const BOOT_INFO* bootInfo) { for (uint32_t i = 0; i < bootInfo->ModuleCount && i < MAX_MODULES; i++) { strncpy(gModules[gModuleCount].Name, bootInfo->Modules[i].Name, 31); gModules[gModuleCount].Base = bootInfo->Modules[i].VirtBase; gModuleCount++; } } void LdrRegisterModule(const char* name, uintptr_t base) { if (gModuleCount < MAX_MODULES) { strncpy(gModules[gModuleCount].Name, name, 31); gModules[gModuleCount].Base = base; gModuleCount++; } } static uintptr_t FindModuleBase(const char* name) { for (int i = 0; i < gModuleCount; i++) { if (_stricmp(gModules[i].Name, name) == 0) return gModules[i].Base; } return 0; } // --------------------------------------------------------------------------- // Export lookup // --------------------------------------------------------------------------- void* LdrGetExport(const char* moduleName, const char* functionName) { uintptr_t base = FindModuleBase(moduleName); 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; } // --------------------------------------------------------------------------- // 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 LdrApplyRelocations(uintptr_t base, uintptr_t delta) { uint32_t peOffset = *(uint32_t *)(base + 0x3C); PE_HEADER* pe = (PE_HEADER *)(base + peOffset); OPT_HEADER64* opt = &pe->OptionalHeader; if (opt->NumberOfRvaAndSizes < 6) return STATUS_SUCCESS; uint32_t relocRva = opt->DataDirectory[5].VirtualAddress; uint32_t relocSize = opt->DataDirectory[5].Size; if (!relocRva || !relocSize) return STATUS_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); *ptr += delta; } } block = (BASE_RELOC *)((uint8_t *)block + block->SizeOfBlock); } return STATUS_SUCCESS; } // --------------------------------------------------------------------------- // Import resolution // --------------------------------------------------------------------------- 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 STATUS_SUCCESS; // no imports uint32_t impRva = opt->DataDirectory[1].VirtualAddress; if (!impRva) return STATUS_SUCCESS; IMPORT_DESC* imp = (IMPORT_DESC *)(imageBase + impRva); while (imp->Name) { const char* dllName = (const char *)(imageBase + imp->Name); if (!_stricmp(dllName, "kernel32.dll") || !_stricmp(dllName, "user32.dll") || !_stricmp(dllName, "ole32.dll") || !_stricmp(dllName, "winmm.dll")) dllName = "win32.dll"; 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)) { // Import by ordinal - not supported. DbgPrint("LdrResolveImports: Ordinal imports not supported! (%s)\n", dllName); return STATUS_NOT_SUPPORTED; } const char* funcName = (const char *)(imageBase + (*origThunk & 0xFFFFFFFF) + 2); void* addr = LdrGetExport(dllName, funcName); if (!addr) { DbgPrint("LdrResolveImports: Function not found! (%s)\n", funcName); return STATUS_NOT_FOUND; } *thunk = (uint64_t)addr; thunk++; origThunk++; } imp++; } return STATUS_SUCCESS; } // LdrLoadImage - load a PE from a raw buffer into virtual space. KSTATUS LdrLoadImage(const void* buffer, size_t size, uintptr_t* baseOut, bool user) { if (!buffer || !size || !baseOut) return STATUS_INVALID_PARAMETER; uint32_t peOffset = *(uint32_t *)((uint8_t *)buffer + 0x3C); PE_HEADER* pe = (PE_HEADER *)((uint8_t *)buffer + peOffset); if (pe->Signature != PE_SIGNATURE) return STATUS_FAILURE; OPT_HEADER64* opt = &pe->OptionalHeader; size_t imageSize = opt->SizeOfImage; void* mem = user ? MmAllocateUserMemory(imageSize) : MmAllocatePool(imageSize); if (!mem) return STATUS_NO_MEMORY; memset(mem, 0, imageSize); // Copy headers and sections 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) { memcpy(dst, src, sec[i].SizeOfRawData < sec[i].VirtualSize ? sec[i].SizeOfRawData : sec[i].VirtualSize); } uint64_t prot = PAGE_PRESENT | PAGE_WRITE | (user ? PAGE_USER : 0); MmSetPageProtect((uintptr_t)mem + sec[i].VirtualAddress, sec[i].VirtualSize, prot); } // Apply Relocations uintptr_t delta = (uintptr_t)mem - opt->ImageBase; if (delta != 0) { LdrApplyRelocations((uintptr_t)mem, delta); } // Resolve Imports KSTATUS status = LdrResolveImports((uintptr_t)mem); if (status != STATUS_SUCCESS) return status; *baseOut = (uintptr_t)mem; return STATUS_SUCCESS; }