// Copyright 2011-2020 Molecular Matters GmbH, all rights reserved. #if LC_VERSION == 1 // BEGIN EPIC MOD //#include PCH_INCLUDE // END EPIC MOD #include "LC_Executable.h" #include "LC_MemoryMappedFile.h" // BEGIN EPIC MOD #include "LC_Assert.h" #include "LC_Logging.h" #include // END EPIC MOD // this is the default DLL entry point, taken from the CRT. we don't need it, but can extract its signature. extern "C" BOOL WINAPI _DllMainCRTStartup( HINSTANCE const instance, DWORD const reason, LPVOID const reserved ); namespace detail { static inline bool SortSectionByAscendingRVA(const executable::ImageSection& lhs, const executable::ImageSection& rhs) { return lhs.rva < rhs.rva; } static const IMAGE_NT_HEADERS* GetNtHeader(const executable::Image* image) { const void* base = Filesystem::GetMemoryMappedFileData(image); // PE image start with a DOS header const IMAGE_DOS_HEADER* dosHeader = static_cast(base); if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { LC_ERROR_USER("%s", "Image has unknown file format"); return nullptr; } const IMAGE_NT_HEADERS* ntHeader = pointer::Offset(dosHeader, dosHeader->e_lfanew); if (ntHeader->Signature != IMAGE_NT_SIGNATURE) { LC_ERROR_USER("%s", "Invalid .exe file"); return nullptr; } return ntHeader; } static IMAGE_NT_HEADERS* GetNtHeader(executable::Image* image) { return const_cast(GetNtHeader(const_cast(image))); } static const IMAGE_SECTION_HEADER* GetSectionHeader(const IMAGE_NT_HEADERS* ntHeader) { return IMAGE_FIRST_SECTION(ntHeader); } } uint32_t executable::GetEntryPointRva(const Image* image) { const IMAGE_NT_HEADERS* ntHeader = detail::GetNtHeader(image); if (!ntHeader) { return 0u; } return ntHeader->OptionalHeader.AddressOfEntryPoint; } executable::PreferredBase executable::GetPreferredBase(const Image* image) { const IMAGE_NT_HEADERS* ntHeader = detail::GetNtHeader(image); if (!ntHeader) { return 0ull; } return ntHeader->OptionalHeader.ImageBase; } executable::Header executable::GetHeader(const Image* image) { const IMAGE_NT_HEADERS* ntHeader = detail::GetNtHeader(image); if (!ntHeader) { return executable::Header {}; } const uint64_t sizeOnDisk = Filesystem::GetMemoryMappedFileSizeOnDisk(image); return executable::Header { sizeOnDisk, ntHeader->FileHeader, ntHeader->OptionalHeader }; } bool executable::IsValidHeader(const Header& header) { return header.imageHeader.NumberOfSections != 0; } uint32_t executable::GetSize(const Image* image) { const IMAGE_NT_HEADERS* ntHeader = detail::GetNtHeader(image); if (!ntHeader) { return 0ull; } return ntHeader->OptionalHeader.SizeOfImage; } uint32_t executable::RvaToFileOffset(const ImageSectionDB* database, uint32_t rva) { LC_ASSERT(rva != 0u, "RVA cannot be mapped to image."); const size_t count = database->sections.size(); for (size_t i = 0u; i < count; ++i) { const ImageSection& section = database->sections[i]; if ((rva >= section.rva) && (rva < section.rva + section.size)) { const uint32_t sectionOffset = rva - section.rva; if (sectionOffset >= section.rawDataSize) { // the offset relative to the section lies outside the section data stored in the image. // this can happen for sections like .bss/.data which don't store uninitialized data for // the symbols. return 0u; } return section.rawDataRva + sectionOffset; } } LC_ERROR_DEV("Cannot map RVA 0x%X to executable image file offset", rva); return 0u; } void executable::ReadFromFileOffset(const Image* image, uint32_t offset, void* destination, size_t byteCount) { const void* address = pointer::Offset(Filesystem::GetMemoryMappedFileData(image), offset); memcpy(destination, address, byteCount); } void executable::WriteToFileOffset(Image* image, uint32_t offset, const void* source, size_t byteCount) { void* address = pointer::Offset(Filesystem::GetMemoryMappedFileData(image), offset); memcpy(address, source, byteCount); } executable::Image* executable::OpenImage(const wchar_t* filename, Filesystem::OpenMode::Enum openMode) { return Filesystem::OpenMemoryMappedFile(filename, openMode); } void executable::CloseImage(Image*& image) { Filesystem::CloseMemoryMappedFile(image); } void executable::RebaseImage(Image* image, PreferredBase preferredBase) { void* base = Filesystem::GetMemoryMappedFileData(image); IMAGE_NT_HEADERS* ntHeader = detail::GetNtHeader(image); if (!ntHeader) { return; } const IMAGE_SECTION_HEADER* sectionHeader = detail::GetSectionHeader(ntHeader); if (!sectionHeader) { return; } ImageSectionDB* database = GatherImageSectionDB(image); // the image has been linked against a certain base address, namely ntHeader->OptionalHeader.ImageBase. // work out by how much all relocations need to be shifted if basing the image against the new // preferred base. const int64_t baseDelta = static_cast(preferredBase - ntHeader->OptionalHeader.ImageBase); // this is the easy part: simply set the new preferred base address in the image ntHeader->OptionalHeader.ImageBase = preferredBase; // now comes the hard part: patch all relocation entries in the image const DWORD relocSectionSize = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; if (relocSectionSize != 0u) { // .reloc section exists, patch it const DWORD baseRelocationOffset = RvaToFileOffset(database, ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); const IMAGE_BASE_RELOCATION* baseRelocations = pointer::Offset(base, baseRelocationOffset); DWORD blockSizeLeft = relocSectionSize; while (blockSizeLeft > 0u) { const DWORD pageRVA = baseRelocations->VirtualAddress; const DWORD blockSize = baseRelocations->SizeOfBlock; const DWORD blockOffset = RvaToFileOffset(database, pageRVA); // PE spec: Block size: The total number of bytes in the base relocation block, *including* the Page RVA and // Block Size fields and the Type/Offset fields that follow. const DWORD numberOfEntriesInThisBlock = (blockSize - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); const WORD* entries = pointer::Offset(baseRelocations, sizeof(IMAGE_BASE_RELOCATION)); for (DWORD i = 0u; i < numberOfEntriesInThisBlock; ++i) { // PE spec: Type: Stored in the high 4 bits of the WORD // Offset: Stored in the remaining 12 bits of the WORD const WORD low12BitMask = 0x0FFF; const WORD type = static_cast(entries[i] >> 12u); const WORD offset = static_cast(entries[i] & low12BitMask); // PE spec: // IMAGE_REL_BASED_ABSOLUTE: The base relocation is skipped. This type can be used to pad a block. // IMAGE_REL_BASED_HIGH: The base relocation adds the high 16 bits of the difference to the 16-bit field // at offset. The 16-bit field represents the high value of a 32-bit word. // IMAGE_REL_BASED_LOW: The base relocation adds the low 16 bits of the difference to the 16-bit field // at offset. The 16-bit field represents the low half of a 32-bit word. // IMAGE_REL_BASED_HIGHLOW: The base relocation applies all 32 bits of the difference to the 32-bit field at // offset. // IMAGE_REL_BASED_HIGHADJ: The base relocation adds the high 16 bits of the difference to the 16-bit field // at offset. The 16-bit field represents the high value of a 32-bit word. The low 16 bits of the 32-bit value // are stored in the 16-bit word that follows this base relocation. This means that this base relocation // occupies two slots. // IMAGE_REL_BASED_DIR64: The base relocation applies the difference to the 64-bit field at offset. if (type == IMAGE_REL_BASED_ABSOLUTE) { continue; } else if (type == IMAGE_REL_BASED_HIGHLOW) { uint32_t* relocation = pointer::Offset(base, blockOffset + offset); *relocation += static_cast(baseDelta); } else if (type == IMAGE_REL_BASED_DIR64) { uint64_t* relocation = pointer::Offset(base, blockOffset + offset); *relocation += static_cast(baseDelta); } } baseRelocations = pointer::Offset(baseRelocations, baseRelocations->SizeOfBlock); LC_ASSERT(blockSizeLeft >= blockSize, "Underflow while reading image relocations"); blockSizeLeft -= blockSize; } } DestroyImageSectionDB(database); } executable::ImageSectionDB* executable::GatherImageSectionDB(const Image* image) { const IMAGE_NT_HEADERS* ntHeader = detail::GetNtHeader(image); if (!ntHeader) { return nullptr; } const IMAGE_SECTION_HEADER* sectionHeader = detail::GetSectionHeader(ntHeader); if (!sectionHeader) { return nullptr; } ImageSectionDB* database = new ImageSectionDB; const size_t sectionCount = ntHeader->FileHeader.NumberOfSections; database->sections.reserve(sectionCount); for (size_t i = 0u; i < sectionCount; ++i) { database->sections.emplace_back(ImageSection { sectionHeader->VirtualAddress, sectionHeader->Misc.VirtualSize, sectionHeader->PointerToRawData, sectionHeader->SizeOfRawData }); ++sectionHeader; } std::sort(database->sections.begin(), database->sections.end(), &detail::SortSectionByAscendingRVA); return database; } void executable::DestroyImageSectionDB(ImageSectionDB* database) { delete database; } executable::ImportModuleDB* executable::GatherImportModuleDB(const Image* image, const ImageSectionDB* imageSections) { const IMAGE_NT_HEADERS* ntHeader = detail::GetNtHeader(image); if (!ntHeader) { return nullptr; } const IMAGE_SECTION_HEADER* sectionHeader = detail::GetSectionHeader(ntHeader); if (!sectionHeader) { return nullptr; } ImportModuleDB* database = new ImportModuleDB; // the import directory stores an array of IMAGE_IMPORT_DESCRIPTOR entries const size_t count = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size / sizeof(IMAGE_IMPORT_DESCRIPTOR); if (count == 0u) { // no import modules return database; } const DWORD baseImportModule = RvaToFileOffset(imageSections, ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); if (baseImportModule == 0u) { // no import modules return database; } database->modules.reserve(count); const IMAGE_IMPORT_DESCRIPTOR* importModules = pointer::Offset(Filesystem::GetMemoryMappedFileData(image), baseImportModule); while (importModules->Name != 0) { const DWORD fileOffset = RvaToFileOffset(imageSections, importModules->Name); const char* importModuleName = pointer::Offset(Filesystem::GetMemoryMappedFileData(image), fileOffset); ImportModule module; strcpy_s(module.path, importModuleName); database->modules.emplace_back(module); ++importModules; } return database; } void executable::DestroyImportModuleDB(ImportModuleDB* database) { delete database; } executable::PdbInfo* executable::GatherPdbInfo(const Image* image, const ImageSectionDB* imageSections) { const IMAGE_NT_HEADERS* ntHeader = detail::GetNtHeader(image); if (!ntHeader) { return nullptr; } const IMAGE_SECTION_HEADER* sectionHeader = detail::GetSectionHeader(ntHeader); if (!sectionHeader) { return nullptr; } // the debug directory stores an array of IMAGE_DEBUG_DIRECTORY entries const size_t count = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size / sizeof(IMAGE_DEBUG_DIRECTORY); if (count == 0u) { // no debug directories return nullptr; } const DWORD baseDebugDirectory = RvaToFileOffset(imageSections, ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress); if (baseDebugDirectory == 0u) { // no debug directories return nullptr; } const IMAGE_DEBUG_DIRECTORY* debugDirectory = pointer::Offset(Filesystem::GetMemoryMappedFileData(image), baseDebugDirectory); for (size_t i=0u; i < count; ++i) { // we are only interested in PDB files if (debugDirectory->Type == IMAGE_DEBUG_TYPE_CODEVIEW) { // try interpreting the raw data as PDB 7.0 info. if it belongs to PDB 7.0 data, the signature matches "RSDS" // http://www.debuginfo.com/articles/debuginfomatch.html struct PDB70Header { DWORD signature; // 'RSDS' GUID guid; DWORD age; }; const DWORD RSDS = 0x53445352u; const PDB70Header* pdbHeader = pointer::Offset(Filesystem::GetMemoryMappedFileData(image), debugDirectory->PointerToRawData); if (pdbHeader->signature == RSDS) { // PDB filename follows right after the header data const char* pdbPath = pointer::Offset(pdbHeader, sizeof(PDB70Header)); PdbInfo* pdbInfo = new PdbInfo; pdbInfo->guid = pdbHeader->guid; pdbInfo->age = pdbHeader->age; strcpy_s(pdbInfo->path, pdbPath); return pdbInfo; } } } return nullptr; } void executable::DestroyPdbInfo(PdbInfo* pdbInfo) { delete pdbInfo; } void executable::CallDllEntryPoint(void* moduleBase, uint32_t entryPointRva) { typedef decltype(_DllMainCRTStartup) DllEntryPoint; DllEntryPoint* entryPoint = pointer::Offset(moduleBase, entryPointRva); entryPoint(static_cast(moduleBase), DLL_PROCESS_ATTACH, NULL); } #endif // LC_VERSION