Files
2025-05-18 13:04:45 +08:00

441 lines
13 KiB
C++

// 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 <algorithm>
// 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<const IMAGE_DOS_HEADER*>(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<const IMAGE_NT_HEADERS*>(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<IMAGE_NT_HEADERS*>(GetNtHeader(const_cast<const executable::Image*>(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<const void*>(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<void*>(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<int64_t>(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<const IMAGE_BASE_RELOCATION*>(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<const WORD*>(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<WORD>(entries[i] >> 12u);
const WORD offset = static_cast<WORD>(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<uint32_t*>(base, blockOffset + offset);
*relocation += static_cast<uint32_t>(baseDelta);
}
else if (type == IMAGE_REL_BASED_DIR64)
{
uint64_t* relocation = pointer::Offset<uint64_t*>(base, blockOffset + offset);
*relocation += static_cast<uint64_t>(baseDelta);
}
}
baseRelocations = pointer::Offset<const IMAGE_BASE_RELOCATION*>(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<const IMAGE_IMPORT_DESCRIPTOR*>(Filesystem::GetMemoryMappedFileData(image), baseImportModule);
while (importModules->Name != 0)
{
const DWORD fileOffset = RvaToFileOffset(imageSections, importModules->Name);
const char* importModuleName = pointer::Offset<const char*>(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<const IMAGE_DEBUG_DIRECTORY*>(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<const PDB70Header*>(Filesystem::GetMemoryMappedFileData(image), debugDirectory->PointerToRawData);
if (pdbHeader->signature == RSDS)
{
// PDB filename follows right after the header data
const char* pdbPath = pointer::Offset<const char*>(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<DllEntryPoint*>(moduleBase, entryPointRva);
entryPoint(static_cast<HINSTANCE>(moduleBase), DLL_PROCESS_ATTACH, NULL);
}
#endif // LC_VERSION