// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file implements PEImage, a generic class to manipulate PE files. // This file was adapted from GreenBorder's Code. #include "base/win/pe_image.h" #include #include #include #include #include "base/no_destructor.h" #include "base/win/current_module.h" namespace base { namespace win { // Structure to perform imports enumerations. struct EnumAllImportsStorage { PEImage::EnumImportsFunction callback; PVOID cookie; }; namespace { // PdbInfo Signature const DWORD kPdbInfoSignature = 'SDSR'; // Compare two strings byte by byte on an unsigned basis. // if s1 == s2, return 0 // if s1 < s2, return negative // if s1 > s2, return positive // Exception if inputs are invalid. int StrCmpByByte(LPCSTR s1, LPCSTR s2) { while (*s1 != '\0' && *s1 == *s2) { ++s1; ++s2; } return (*reinterpret_cast(s1) - *reinterpret_cast(s2)); } struct PdbInfo { DWORD Signature; GUID Guid; DWORD Age; char PdbFileName[1]; }; #define LDR_IS_DATAFILE(handle) (((ULONG_PTR)(handle)) & (ULONG_PTR)1) #define LDR_IS_IMAGEMAPPING(handle) (((ULONG_PTR)(handle)) & (ULONG_PTR)2) #define LDR_IS_RESOURCE(handle) \ (LDR_IS_IMAGEMAPPING(handle) || LDR_IS_DATAFILE(handle)) } // namespace // Callback used to enumerate imports. See EnumImportChunksFunction. bool ProcessImportChunk(const PEImage& image, LPCSTR module, PIMAGE_THUNK_DATA name_table, PIMAGE_THUNK_DATA iat, PVOID cookie) { EnumAllImportsStorage& storage = *reinterpret_cast(cookie); return image.EnumOneImportChunk(storage.callback, module, name_table, iat, storage.cookie); } // Callback used to enumerate delay imports. See EnumDelayImportChunksFunction. bool ProcessDelayImportChunk(const PEImage& image, PImgDelayDescr delay_descriptor, LPCSTR module, PIMAGE_THUNK_DATA name_table, PIMAGE_THUNK_DATA iat, PVOID cookie) { EnumAllImportsStorage& storage = *reinterpret_cast(cookie); return image.EnumOneDelayImportChunk(storage.callback, delay_descriptor, module, name_table, iat, storage.cookie); } void PEImage::set_module(HMODULE module) { module_ = module; } PIMAGE_DOS_HEADER PEImage::GetDosHeader() const { return reinterpret_cast(module_); } PIMAGE_NT_HEADERS PEImage::GetNTHeaders() const { PIMAGE_DOS_HEADER dos_header = GetDosHeader(); return reinterpret_cast( reinterpret_cast(dos_header) + dos_header->e_lfanew); } PIMAGE_SECTION_HEADER PEImage::GetSectionHeader(UINT section) const { PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); PIMAGE_SECTION_HEADER first_section = IMAGE_FIRST_SECTION(nt_headers); if (section < nt_headers->FileHeader.NumberOfSections) return first_section + section; else return nullptr; } WORD PEImage::GetNumSections() const { return GetNTHeaders()->FileHeader.NumberOfSections; } DWORD PEImage::GetImageDirectoryEntrySize(UINT directory) const { const IMAGE_DATA_DIRECTORY* const entry = GetDataDirectory(directory); return entry ? entry->Size : 0; } PVOID PEImage::GetImageDirectoryEntryAddr(UINT directory) const { const IMAGE_DATA_DIRECTORY* const entry = GetDataDirectory(directory); return entry ? RVAToAddr(entry->VirtualAddress) : nullptr; } PIMAGE_SECTION_HEADER PEImage::GetImageSectionFromAddr(PVOID address) const { PBYTE target = reinterpret_cast(address); PIMAGE_SECTION_HEADER section; for (UINT i = 0; nullptr != (section = GetSectionHeader(i)); i++) { // Don't use the virtual RVAToAddr. PBYTE start = reinterpret_cast(PEImage::RVAToAddr(section->VirtualAddress)); DWORD size = section->Misc.VirtualSize; if ((start <= target) && (start + size > target)) return section; } return nullptr; } PIMAGE_SECTION_HEADER PEImage::GetImageSectionHeaderByName( LPCSTR section_name) const { if (nullptr == section_name) return nullptr; PIMAGE_SECTION_HEADER ret = nullptr; int num_sections = GetNumSections(); for (int i = 0; i < num_sections; i++) { PIMAGE_SECTION_HEADER section = GetSectionHeader(i); if (_strnicmp(reinterpret_cast(section->Name), section_name, sizeof(section->Name)) == 0) { ret = section; break; } } return ret; } bool PEImage::GetDebugId(LPGUID guid, LPDWORD age, LPCSTR* pdb_filename, size_t* pdb_filename_length) const { DWORD debug_directory_size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DEBUG); PIMAGE_DEBUG_DIRECTORY debug_directory = reinterpret_cast( GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_DEBUG)); if (!debug_directory) return false; size_t directory_count = debug_directory_size / sizeof(IMAGE_DEBUG_DIRECTORY); for (size_t index = 0; index < directory_count; ++index) { const IMAGE_DEBUG_DIRECTORY& entry = debug_directory[index]; if (entry.Type != IMAGE_DEBUG_TYPE_CODEVIEW) continue; // Unsupported debugging info format. if (entry.SizeOfData < sizeof(PdbInfo)) continue; // The data is too small to hold PDB info. const PdbInfo* pdb_info = reinterpret_cast(RVAToAddr(entry.AddressOfRawData)); if (!pdb_info) continue; // The data is not present in a mapped section. if (pdb_info->Signature != kPdbInfoSignature) continue; // Unsupported PdbInfo signature if (guid) *guid = pdb_info->Guid; if (age) *age = pdb_info->Age; if (pdb_filename) { const size_t length_max = entry.SizeOfData - offsetof(PdbInfo, PdbFileName); const char* eos = pdb_info->PdbFileName; for (const char* const end = pdb_info->PdbFileName + length_max; eos < end && *eos; ++eos) ; *pdb_filename_length = eos - pdb_info->PdbFileName; *pdb_filename = pdb_info->PdbFileName; } return true; } return false; } PDWORD PEImage::GetExportEntry(LPCSTR name) const { PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); if (nullptr == exports) return nullptr; WORD ordinal = 0; if (!GetProcOrdinal(name, &ordinal)) return nullptr; PDWORD functions = reinterpret_cast(RVAToAddr(exports->AddressOfFunctions)); return functions + ordinal - exports->Base; } FARPROC PEImage::GetProcAddress(LPCSTR function_name) const { PDWORD export_entry = GetExportEntry(function_name); if (nullptr == export_entry) return nullptr; PBYTE function = reinterpret_cast(RVAToAddr(*export_entry)); PBYTE exports = reinterpret_cast( GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT)); DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); if (!exports || !size) return nullptr; // Check for forwarded exports as a special case. if (exports <= function && exports + size > function) return reinterpret_cast(-1); return reinterpret_cast(function); } bool PEImage::GetProcOrdinal(LPCSTR function_name, WORD* ordinal) const { if (nullptr == ordinal) return false; PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); if (nullptr == exports) return false; if (IsOrdinal(function_name)) { *ordinal = ToOrdinal(function_name); } else { PDWORD names = reinterpret_cast(RVAToAddr(exports->AddressOfNames)); PDWORD lower = names; PDWORD upper = names + exports->NumberOfNames; int cmp = -1; // Binary Search for the name. while (lower != upper) { PDWORD middle = lower + (upper - lower) / 2; LPCSTR name = reinterpret_cast(RVAToAddr(*middle)); // This may be called by sandbox before MSVCRT dll loads, so can't use // CRT function here. cmp = StrCmpByByte(function_name, name); if (cmp == 0) { lower = middle; break; } if (cmp > 0) lower = middle + 1; else upper = middle; } if (cmp != 0) return false; PWORD ordinals = reinterpret_cast(RVAToAddr(exports->AddressOfNameOrdinals)); *ordinal = ordinals[lower - names] + static_cast(exports->Base); } return true; } bool PEImage::EnumSections(EnumSectionsFunction callback, PVOID cookie) const { PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); UINT num_sections = nt_headers->FileHeader.NumberOfSections; PIMAGE_SECTION_HEADER section = GetSectionHeader(0); for (UINT i = 0; i < num_sections; i++, section++) { PVOID section_start = RVAToAddr(section->VirtualAddress); DWORD size = section->Misc.VirtualSize; if (!callback(*this, section, section_start, size, cookie)) return false; } return true; } bool PEImage::EnumExports(EnumExportsFunction callback, PVOID cookie) const { PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT); DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); // Check if there are any exports at all. if (!directory || !size) return true; PIMAGE_EXPORT_DIRECTORY exports = reinterpret_cast(directory); UINT ordinal_base = exports->Base; UINT num_funcs = exports->NumberOfFunctions; UINT num_names = exports->NumberOfNames; PDWORD functions = reinterpret_cast(RVAToAddr(exports->AddressOfFunctions)); PDWORD names = reinterpret_cast(RVAToAddr(exports->AddressOfNames)); PWORD ordinals = reinterpret_cast(RVAToAddr(exports->AddressOfNameOrdinals)); for (UINT count = 0; count < num_funcs; count++) { PVOID func = RVAToAddr(functions[count]); if (nullptr == func) continue; // Check for a name. LPCSTR name = nullptr; UINT hint; for (hint = 0; hint < num_names; hint++) { if (ordinals[hint] == count) { name = reinterpret_cast(RVAToAddr(names[hint])); break; } } if (name == nullptr) hint = 0; // Check for forwarded exports. LPCSTR forward = nullptr; if (reinterpret_cast(func) >= reinterpret_cast(directory) && reinterpret_cast(func) <= reinterpret_cast(directory) + size) { forward = reinterpret_cast(func); func = nullptr; } if (!callback(*this, ordinal_base + count, hint, name, func, forward, cookie)) return false; } return true; } bool PEImage::EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const { PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_BASERELOC); DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BASERELOC); if (!directory || !size) return true; PIMAGE_BASE_RELOCATION base = reinterpret_cast(directory); while (size >= sizeof(IMAGE_BASE_RELOCATION) && base->SizeOfBlock && size >= base->SizeOfBlock) { PWORD reloc = reinterpret_cast(base + 1); UINT num_relocs = (base->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); for (UINT i = 0; i < num_relocs; i++, reloc++) { WORD type = *reloc >> 12; PVOID address = RVAToAddr(base->VirtualAddress + (*reloc & 0x0FFF)); if (!callback(*this, type, address, cookie)) return false; } size -= base->SizeOfBlock; base = reinterpret_cast( reinterpret_cast(base) + base->SizeOfBlock); } return true; } bool PEImage::EnumImportChunks(EnumImportChunksFunction callback, PVOID cookie, LPCSTR target_module_name) const { DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_IMPORT); PIMAGE_IMPORT_DESCRIPTOR import = GetFirstImportChunk(); if (import == nullptr || size < sizeof(IMAGE_IMPORT_DESCRIPTOR)) return true; for (; import->FirstThunk; import++) { LPCSTR module_name = reinterpret_cast(RVAToAddr(import->Name)); PIMAGE_THUNK_DATA name_table = reinterpret_cast( RVAToAddr(import->OriginalFirstThunk)); PIMAGE_THUNK_DATA iat = reinterpret_cast(RVAToAddr(import->FirstThunk)); if (target_module_name == nullptr || (lstrcmpiA(module_name, target_module_name) == 0)) { if (!callback(*this, module_name, name_table, iat, cookie)) return false; } } return true; } bool PEImage::EnumOneImportChunk(EnumImportsFunction callback, LPCSTR module_name, PIMAGE_THUNK_DATA name_table, PIMAGE_THUNK_DATA iat, PVOID cookie) const { if (nullptr == name_table) return false; for (; name_table && name_table->u1.Ordinal; name_table++, iat++) { LPCSTR name = nullptr; WORD ordinal = 0; WORD hint = 0; if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { ordinal = static_cast(IMAGE_ORDINAL32(name_table->u1.Ordinal)); } else { PIMAGE_IMPORT_BY_NAME import = reinterpret_cast( RVAToAddr(name_table->u1.ForwarderString)); hint = import->Hint; name = reinterpret_cast(&import->Name); } if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) return false; } return true; } bool PEImage::EnumAllImports(EnumImportsFunction callback, PVOID cookie, LPCSTR target_module_name) const { EnumAllImportsStorage temp = {callback, cookie}; return EnumImportChunks(ProcessImportChunk, &temp, target_module_name); } bool PEImage::EnumDelayImportChunks(EnumDelayImportChunksFunction callback, PVOID cookie, LPCSTR target_module_name) const { PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); if (!directory || !size) return true; PImgDelayDescr delay_descriptor = reinterpret_cast(directory); for (; delay_descriptor->rvaHmod; delay_descriptor++) { PIMAGE_THUNK_DATA name_table; PIMAGE_THUNK_DATA iat; LPCSTR module_name; // check if VC7-style imports, using RVAs instead of // VC6-style addresses. bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; if (rvas) { module_name = reinterpret_cast(RVAToAddr(delay_descriptor->rvaDLLName)); name_table = reinterpret_cast( RVAToAddr(delay_descriptor->rvaINT)); iat = reinterpret_cast( RVAToAddr(delay_descriptor->rvaIAT)); } else { // Values in IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT are 32-bit, even on 64-bit // platforms. See section 4.8 of PECOFF image spec rev 8.3. module_name = reinterpret_cast( static_cast(delay_descriptor->rvaDLLName)); name_table = reinterpret_cast( static_cast(delay_descriptor->rvaINT)); iat = reinterpret_cast( static_cast(delay_descriptor->rvaIAT)); } if (target_module_name == nullptr || (lstrcmpiA(module_name, target_module_name) == 0)) { if (target_module_name) { // Ensure all imports are properly loaded for the target module so that // the callback is operating on a fully-realized set of imports. // This call only loads the imports for the module where this code is // executing, so it is only helpful or meaningful to do this if the // current module is the module whose IAT we are enumerating. // Use the module_name as retrieved from the IAT because this method // is case sensitive. if (module_ == CURRENT_MODULE() && !LDR_IS_RESOURCE(module_)) { static base::NoDestructor> loaded_dlls; // pair.second is true if this is a new element if (loaded_dlls.get()->emplace(module_name).second) ::__HrLoadAllImportsForDll(module_name); } } if (!callback(*this, delay_descriptor, module_name, name_table, iat, cookie)) return false; } } return true; } bool PEImage::EnumOneDelayImportChunk(EnumImportsFunction callback, PImgDelayDescr delay_descriptor, LPCSTR module_name, PIMAGE_THUNK_DATA name_table, PIMAGE_THUNK_DATA iat, PVOID cookie) const { for (; name_table->u1.Ordinal; name_table++, iat++) { LPCSTR name = nullptr; WORD ordinal = 0; WORD hint = 0; if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { ordinal = static_cast(IMAGE_ORDINAL32(name_table->u1.Ordinal)); } else { PIMAGE_IMPORT_BY_NAME import; bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; if (rvas) { import = reinterpret_cast( RVAToAddr(name_table->u1.ForwarderString)); } else { import = reinterpret_cast( name_table->u1.ForwarderString); } hint = import->Hint; name = reinterpret_cast(&import->Name); } if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) return false; } return true; } bool PEImage::EnumAllDelayImports(EnumImportsFunction callback, PVOID cookie, LPCSTR target_module_name) const { EnumAllImportsStorage temp = {callback, cookie}; return EnumDelayImportChunks(ProcessDelayImportChunk, &temp, target_module_name); } bool PEImage::VerifyMagic() const { PIMAGE_DOS_HEADER dos_header = GetDosHeader(); if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) return false; PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); if (nt_headers->Signature != IMAGE_NT_SIGNATURE) return false; if (nt_headers->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) return false; if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) return false; return true; } bool PEImage::ImageRVAToOnDiskOffset(uintptr_t rva, DWORD* on_disk_offset) const { LPVOID address = RVAToAddr(rva); return ImageAddrToOnDiskOffset(address, on_disk_offset); } bool PEImage::ImageAddrToOnDiskOffset(LPVOID address, DWORD* on_disk_offset) const { if (nullptr == address) return false; // Get the section that this address belongs to. PIMAGE_SECTION_HEADER section_header = GetImageSectionFromAddr(address); if (nullptr == section_header) return false; // Don't follow the virtual RVAToAddr, use the one on the base. DWORD offset_within_section = static_cast(reinterpret_cast(address)) - static_cast(reinterpret_cast( PEImage::RVAToAddr(section_header->VirtualAddress))); *on_disk_offset = section_header->PointerToRawData + offset_within_section; return true; } PVOID PEImage::RVAToAddr(uintptr_t rva) const { if (rva == 0) return nullptr; return reinterpret_cast(module_) + rva; } const IMAGE_DATA_DIRECTORY* PEImage::GetDataDirectory(UINT directory) const { PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); // Does the image report that it includes this directory entry? if (directory >= nt_headers->OptionalHeader.NumberOfRvaAndSizes) return nullptr; // Is there space for this directory entry in the optional header? if (nt_headers->FileHeader.SizeOfOptionalHeader < (offsetof(IMAGE_OPTIONAL_HEADER, DataDirectory) + (directory + 1) * sizeof(IMAGE_DATA_DIRECTORY))) { return nullptr; } return &nt_headers->OptionalHeader.DataDirectory[directory]; } PVOID PEImageAsData::RVAToAddr(uintptr_t rva) const { if (rva == 0) return nullptr; PVOID in_memory = PEImage::RVAToAddr(rva); DWORD disk_offset; if (!ImageAddrToOnDiskOffset(in_memory, &disk_offset)) return nullptr; return PEImage::RVAToAddr(disk_offset); } } // namespace win } // namespace base