michael@0: // Copyright (c) 2010 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: // This file implements PEImage, a generic class to manipulate PE files. michael@0: // This file was adapted from GreenBorder's Code. michael@0: michael@0: #include "base/win/pe_image.h" michael@0: michael@0: namespace base { michael@0: namespace win { michael@0: michael@0: #if defined(_WIN64) && !defined(NACL_WIN64) michael@0: // TODO(jschuh): crbug.com/167707 Make sure this is ok. michael@0: #pragma message ("Warning: \ michael@0: This code is not tested on x64. Please make sure all the base unit tests\ michael@0: pass before doing any real work. The current unit tests don't test the\ michael@0: differences between 32- and 64-bits implementations. Bugs may slip through.\ michael@0: You need to improve the coverage before continuing.") michael@0: #endif michael@0: michael@0: // Structure to perform imports enumerations. michael@0: struct EnumAllImportsStorage { michael@0: PEImage::EnumImportsFunction callback; michael@0: PVOID cookie; michael@0: }; michael@0: michael@0: namespace { michael@0: michael@0: // Compare two strings byte by byte on an unsigned basis. michael@0: // if s1 == s2, return 0 michael@0: // if s1 < s2, return negative michael@0: // if s1 > s2, return positive michael@0: // Exception if inputs are invalid. michael@0: int StrCmpByByte(LPCSTR s1, LPCSTR s2) { michael@0: while (*s1 != '\0' && *s1 == *s2) { michael@0: ++s1; michael@0: ++s2; michael@0: } michael@0: michael@0: return (*reinterpret_cast(s1) - michael@0: *reinterpret_cast(s2)); michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: // Callback used to enumerate imports. See EnumImportChunksFunction. michael@0: bool ProcessImportChunk(const PEImage &image, LPCSTR module, michael@0: PIMAGE_THUNK_DATA name_table, michael@0: PIMAGE_THUNK_DATA iat, PVOID cookie) { michael@0: EnumAllImportsStorage &storage = *reinterpret_cast( michael@0: cookie); michael@0: michael@0: return image.EnumOneImportChunk(storage.callback, module, name_table, iat, michael@0: storage.cookie); michael@0: } michael@0: michael@0: // Callback used to enumerate delay imports. See EnumDelayImportChunksFunction. michael@0: bool ProcessDelayImportChunk(const PEImage &image, michael@0: PImgDelayDescr delay_descriptor, michael@0: LPCSTR module, PIMAGE_THUNK_DATA name_table, michael@0: PIMAGE_THUNK_DATA iat, PIMAGE_THUNK_DATA bound_iat, michael@0: PIMAGE_THUNK_DATA unload_iat, PVOID cookie) { michael@0: EnumAllImportsStorage &storage = *reinterpret_cast( michael@0: cookie); michael@0: michael@0: return image.EnumOneDelayImportChunk(storage.callback, delay_descriptor, michael@0: module, name_table, iat, bound_iat, michael@0: unload_iat, storage.cookie); michael@0: } michael@0: michael@0: void PEImage::set_module(HMODULE module) { michael@0: module_ = module; michael@0: } michael@0: michael@0: PIMAGE_DOS_HEADER PEImage::GetDosHeader() const { michael@0: return reinterpret_cast(module_); michael@0: } michael@0: michael@0: PIMAGE_NT_HEADERS PEImage::GetNTHeaders() const { michael@0: PIMAGE_DOS_HEADER dos_header = GetDosHeader(); michael@0: michael@0: return reinterpret_cast( michael@0: reinterpret_cast(dos_header) + dos_header->e_lfanew); michael@0: } michael@0: michael@0: PIMAGE_SECTION_HEADER PEImage::GetSectionHeader(UINT section) const { michael@0: PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); michael@0: PIMAGE_SECTION_HEADER first_section = IMAGE_FIRST_SECTION(nt_headers); michael@0: michael@0: if (section < nt_headers->FileHeader.NumberOfSections) michael@0: return first_section + section; michael@0: else michael@0: return NULL; michael@0: } michael@0: michael@0: WORD PEImage::GetNumSections() const { michael@0: return GetNTHeaders()->FileHeader.NumberOfSections; michael@0: } michael@0: michael@0: DWORD PEImage::GetImageDirectoryEntrySize(UINT directory) const { michael@0: PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); michael@0: michael@0: return nt_headers->OptionalHeader.DataDirectory[directory].Size; michael@0: } michael@0: michael@0: PVOID PEImage::GetImageDirectoryEntryAddr(UINT directory) const { michael@0: PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); michael@0: michael@0: return RVAToAddr( michael@0: nt_headers->OptionalHeader.DataDirectory[directory].VirtualAddress); michael@0: } michael@0: michael@0: PIMAGE_SECTION_HEADER PEImage::GetImageSectionFromAddr(PVOID address) const { michael@0: PBYTE target = reinterpret_cast(address); michael@0: PIMAGE_SECTION_HEADER section; michael@0: michael@0: for (UINT i = 0; NULL != (section = GetSectionHeader(i)); i++) { michael@0: // Don't use the virtual RVAToAddr. michael@0: PBYTE start = reinterpret_cast( michael@0: PEImage::RVAToAddr(section->VirtualAddress)); michael@0: michael@0: DWORD size = section->Misc.VirtualSize; michael@0: michael@0: if ((start <= target) && (start + size > target)) michael@0: return section; michael@0: } michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: PIMAGE_SECTION_HEADER PEImage::GetImageSectionHeaderByName( michael@0: LPCSTR section_name) const { michael@0: if (NULL == section_name) michael@0: return NULL; michael@0: michael@0: PIMAGE_SECTION_HEADER ret = NULL; michael@0: int num_sections = GetNumSections(); michael@0: michael@0: for (int i = 0; i < num_sections; i++) { michael@0: PIMAGE_SECTION_HEADER section = GetSectionHeader(i); michael@0: if (0 == _strnicmp(reinterpret_cast(section->Name), section_name, michael@0: sizeof(section->Name))) { michael@0: ret = section; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: PDWORD PEImage::GetExportEntry(LPCSTR name) const { michael@0: PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); michael@0: michael@0: if (NULL == exports) michael@0: return NULL; michael@0: michael@0: WORD ordinal = 0; michael@0: if (!GetProcOrdinal(name, &ordinal)) michael@0: return NULL; michael@0: michael@0: PDWORD functions = reinterpret_cast( michael@0: RVAToAddr(exports->AddressOfFunctions)); michael@0: michael@0: return functions + ordinal - exports->Base; michael@0: } michael@0: michael@0: FARPROC PEImage::GetProcAddress(LPCSTR function_name) const { michael@0: PDWORD export_entry = GetExportEntry(function_name); michael@0: if (NULL == export_entry) michael@0: return NULL; michael@0: michael@0: PBYTE function = reinterpret_cast(RVAToAddr(*export_entry)); michael@0: michael@0: PBYTE exports = reinterpret_cast( michael@0: GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT)); michael@0: DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); michael@0: michael@0: // Check for forwarded exports as a special case. michael@0: if (exports <= function && exports + size > function) michael@0: #pragma warning(push) michael@0: #pragma warning(disable: 4312) michael@0: // This cast generates a warning because it is 32 bit specific. michael@0: return reinterpret_cast(0xFFFFFFFF); michael@0: #pragma warning(pop) michael@0: michael@0: return reinterpret_cast(function); michael@0: } michael@0: michael@0: bool PEImage::GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const { michael@0: if (NULL == ordinal) michael@0: return false; michael@0: michael@0: PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); michael@0: michael@0: if (NULL == exports) michael@0: return false; michael@0: michael@0: if (IsOrdinal(function_name)) { michael@0: *ordinal = ToOrdinal(function_name); michael@0: } else { michael@0: PDWORD names = reinterpret_cast(RVAToAddr(exports->AddressOfNames)); michael@0: PDWORD lower = names; michael@0: PDWORD upper = names + exports->NumberOfNames; michael@0: int cmp = -1; michael@0: michael@0: // Binary Search for the name. michael@0: while (lower != upper) { michael@0: PDWORD middle = lower + (upper - lower) / 2; michael@0: LPCSTR name = reinterpret_cast(RVAToAddr(*middle)); michael@0: michael@0: // This may be called by sandbox before MSVCRT dll loads, so can't use michael@0: // CRT function here. michael@0: cmp = StrCmpByByte(function_name, name); michael@0: michael@0: if (cmp == 0) { michael@0: lower = middle; michael@0: break; michael@0: } michael@0: michael@0: if (cmp > 0) michael@0: lower = middle + 1; michael@0: else michael@0: upper = middle; michael@0: } michael@0: michael@0: if (cmp != 0) michael@0: return false; michael@0: michael@0: michael@0: PWORD ordinals = reinterpret_cast( michael@0: RVAToAddr(exports->AddressOfNameOrdinals)); michael@0: michael@0: *ordinal = ordinals[lower - names] + static_cast(exports->Base); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::EnumSections(EnumSectionsFunction callback, PVOID cookie) const { michael@0: PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); michael@0: UINT num_sections = nt_headers->FileHeader.NumberOfSections; michael@0: PIMAGE_SECTION_HEADER section = GetSectionHeader(0); michael@0: michael@0: for (UINT i = 0; i < num_sections; i++, section++) { michael@0: PVOID section_start = RVAToAddr(section->VirtualAddress); michael@0: DWORD size = section->Misc.VirtualSize; michael@0: michael@0: if (!callback(*this, section, section_start, size, cookie)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::EnumExports(EnumExportsFunction callback, PVOID cookie) const { michael@0: PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT); michael@0: DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); michael@0: michael@0: // Check if there are any exports at all. michael@0: if (NULL == directory || 0 == size) michael@0: return true; michael@0: michael@0: PIMAGE_EXPORT_DIRECTORY exports = reinterpret_cast( michael@0: directory); michael@0: UINT ordinal_base = exports->Base; michael@0: UINT num_funcs = exports->NumberOfFunctions; michael@0: UINT num_names = exports->NumberOfNames; michael@0: PDWORD functions = reinterpret_cast(RVAToAddr( michael@0: exports->AddressOfFunctions)); michael@0: PDWORD names = reinterpret_cast(RVAToAddr(exports->AddressOfNames)); michael@0: PWORD ordinals = reinterpret_cast(RVAToAddr( michael@0: exports->AddressOfNameOrdinals)); michael@0: michael@0: for (UINT count = 0; count < num_funcs; count++) { michael@0: PVOID func = RVAToAddr(functions[count]); michael@0: if (NULL == func) michael@0: continue; michael@0: michael@0: // Check for a name. michael@0: LPCSTR name = NULL; michael@0: UINT hint; michael@0: for (hint = 0; hint < num_names; hint++) { michael@0: if (ordinals[hint] == count) { michael@0: name = reinterpret_cast(RVAToAddr(names[hint])); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (name == NULL) michael@0: hint = 0; michael@0: michael@0: // Check for forwarded exports. michael@0: LPCSTR forward = NULL; michael@0: if (reinterpret_cast(func) >= reinterpret_cast(directory) && michael@0: reinterpret_cast(func) <= reinterpret_cast(directory) + michael@0: size) { michael@0: forward = reinterpret_cast(func); michael@0: func = 0; michael@0: } michael@0: michael@0: if (!callback(*this, ordinal_base + count, hint, name, func, forward, michael@0: cookie)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const { michael@0: PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_BASERELOC); michael@0: DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BASERELOC); michael@0: PIMAGE_BASE_RELOCATION base = reinterpret_cast( michael@0: directory); michael@0: michael@0: if (directory == NULL || size < sizeof(IMAGE_BASE_RELOCATION)) michael@0: return true; michael@0: michael@0: while (base->SizeOfBlock) { michael@0: PWORD reloc = reinterpret_cast(base + 1); michael@0: UINT num_relocs = (base->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / michael@0: sizeof(WORD); michael@0: michael@0: for (UINT i = 0; i < num_relocs; i++, reloc++) { michael@0: WORD type = *reloc >> 12; michael@0: PVOID address = RVAToAddr(base->VirtualAddress + (*reloc & 0x0FFF)); michael@0: michael@0: if (!callback(*this, type, address, cookie)) michael@0: return false; michael@0: } michael@0: michael@0: base = reinterpret_cast( michael@0: reinterpret_cast(base) + base->SizeOfBlock); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::EnumImportChunks(EnumImportChunksFunction callback, michael@0: PVOID cookie) const { michael@0: DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_IMPORT); michael@0: PIMAGE_IMPORT_DESCRIPTOR import = GetFirstImportChunk(); michael@0: michael@0: if (import == NULL || size < sizeof(IMAGE_IMPORT_DESCRIPTOR)) michael@0: return true; michael@0: michael@0: for (; import->FirstThunk; import++) { michael@0: LPCSTR module_name = reinterpret_cast(RVAToAddr(import->Name)); michael@0: PIMAGE_THUNK_DATA name_table = reinterpret_cast( michael@0: RVAToAddr(import->OriginalFirstThunk)); michael@0: PIMAGE_THUNK_DATA iat = reinterpret_cast( michael@0: RVAToAddr(import->FirstThunk)); michael@0: michael@0: if (!callback(*this, module_name, name_table, iat, cookie)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::EnumOneImportChunk(EnumImportsFunction callback, michael@0: LPCSTR module_name, michael@0: PIMAGE_THUNK_DATA name_table, michael@0: PIMAGE_THUNK_DATA iat, PVOID cookie) const { michael@0: if (NULL == name_table) michael@0: return false; michael@0: michael@0: for (; name_table && name_table->u1.Ordinal; name_table++, iat++) { michael@0: LPCSTR name = NULL; michael@0: WORD ordinal = 0; michael@0: WORD hint = 0; michael@0: michael@0: if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { michael@0: ordinal = static_cast(IMAGE_ORDINAL32(name_table->u1.Ordinal)); michael@0: } else { michael@0: PIMAGE_IMPORT_BY_NAME import = reinterpret_cast( michael@0: RVAToAddr(name_table->u1.ForwarderString)); michael@0: michael@0: hint = import->Hint; michael@0: name = reinterpret_cast(&import->Name); michael@0: } michael@0: michael@0: if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::EnumAllImports(EnumImportsFunction callback, PVOID cookie) const { michael@0: EnumAllImportsStorage temp = { callback, cookie }; michael@0: return EnumImportChunks(ProcessImportChunk, &temp); michael@0: } michael@0: michael@0: bool PEImage::EnumDelayImportChunks(EnumDelayImportChunksFunction callback, michael@0: PVOID cookie) const { michael@0: PVOID directory = GetImageDirectoryEntryAddr( michael@0: IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); michael@0: DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); michael@0: PImgDelayDescr delay_descriptor = reinterpret_cast(directory); michael@0: michael@0: if (directory == NULL || size == 0) michael@0: return true; michael@0: michael@0: for (; delay_descriptor->rvaHmod; delay_descriptor++) { michael@0: PIMAGE_THUNK_DATA name_table; michael@0: PIMAGE_THUNK_DATA iat; michael@0: PIMAGE_THUNK_DATA bound_iat; // address of the optional bound IAT michael@0: PIMAGE_THUNK_DATA unload_iat; // address of optional copy of original IAT michael@0: LPCSTR module_name; michael@0: michael@0: // check if VC7-style imports, using RVAs instead of michael@0: // VC6-style addresses. michael@0: bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; michael@0: michael@0: if (rvas) { michael@0: module_name = reinterpret_cast( michael@0: RVAToAddr(delay_descriptor->rvaDLLName)); michael@0: name_table = reinterpret_cast( michael@0: RVAToAddr(delay_descriptor->rvaINT)); michael@0: iat = reinterpret_cast( michael@0: RVAToAddr(delay_descriptor->rvaIAT)); michael@0: bound_iat = reinterpret_cast( michael@0: RVAToAddr(delay_descriptor->rvaBoundIAT)); michael@0: unload_iat = reinterpret_cast( michael@0: RVAToAddr(delay_descriptor->rvaUnloadIAT)); michael@0: } else { michael@0: #pragma warning(push) michael@0: #pragma warning(disable: 4312) michael@0: // These casts generate warnings because they are 32 bit specific. michael@0: module_name = reinterpret_cast(delay_descriptor->rvaDLLName); michael@0: name_table = reinterpret_cast( michael@0: delay_descriptor->rvaINT); michael@0: iat = reinterpret_cast(delay_descriptor->rvaIAT); michael@0: bound_iat = reinterpret_cast( michael@0: delay_descriptor->rvaBoundIAT); michael@0: unload_iat = reinterpret_cast( michael@0: delay_descriptor->rvaUnloadIAT); michael@0: #pragma warning(pop) michael@0: } michael@0: michael@0: if (!callback(*this, delay_descriptor, module_name, name_table, iat, michael@0: bound_iat, unload_iat, cookie)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::EnumOneDelayImportChunk(EnumImportsFunction callback, michael@0: PImgDelayDescr delay_descriptor, michael@0: LPCSTR module_name, michael@0: PIMAGE_THUNK_DATA name_table, michael@0: PIMAGE_THUNK_DATA iat, michael@0: PIMAGE_THUNK_DATA bound_iat, michael@0: PIMAGE_THUNK_DATA unload_iat, michael@0: PVOID cookie) const { michael@0: UNREFERENCED_PARAMETER(bound_iat); michael@0: UNREFERENCED_PARAMETER(unload_iat); michael@0: michael@0: for (; name_table->u1.Ordinal; name_table++, iat++) { michael@0: LPCSTR name = NULL; michael@0: WORD ordinal = 0; michael@0: WORD hint = 0; michael@0: michael@0: if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { michael@0: ordinal = static_cast(IMAGE_ORDINAL32(name_table->u1.Ordinal)); michael@0: } else { michael@0: PIMAGE_IMPORT_BY_NAME import; michael@0: bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; michael@0: michael@0: if (rvas) { michael@0: import = reinterpret_cast( michael@0: RVAToAddr(name_table->u1.ForwarderString)); michael@0: } else { michael@0: #pragma warning(push) michael@0: #pragma warning(disable: 4312) michael@0: // This cast generates a warning because it is 32 bit specific. michael@0: import = reinterpret_cast( michael@0: name_table->u1.ForwarderString); michael@0: #pragma warning(pop) michael@0: } michael@0: michael@0: hint = import->Hint; michael@0: name = reinterpret_cast(&import->Name); michael@0: } michael@0: michael@0: if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::EnumAllDelayImports(EnumImportsFunction callback, michael@0: PVOID cookie) const { michael@0: EnumAllImportsStorage temp = { callback, cookie }; michael@0: return EnumDelayImportChunks(ProcessDelayImportChunk, &temp); michael@0: } michael@0: michael@0: bool PEImage::VerifyMagic() const { michael@0: PIMAGE_DOS_HEADER dos_header = GetDosHeader(); michael@0: michael@0: if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) michael@0: return false; michael@0: michael@0: PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); michael@0: michael@0: if (nt_headers->Signature != IMAGE_NT_SIGNATURE) michael@0: return false; michael@0: michael@0: if (nt_headers->FileHeader.SizeOfOptionalHeader != michael@0: sizeof(IMAGE_OPTIONAL_HEADER)) michael@0: return false; michael@0: michael@0: if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool PEImage::ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const { michael@0: LPVOID address = RVAToAddr(rva); michael@0: return ImageAddrToOnDiskOffset(address, on_disk_offset); michael@0: } michael@0: michael@0: bool PEImage::ImageAddrToOnDiskOffset(LPVOID address, michael@0: DWORD *on_disk_offset) const { michael@0: if (NULL == address) michael@0: return false; michael@0: michael@0: // Get the section that this address belongs to. michael@0: PIMAGE_SECTION_HEADER section_header = GetImageSectionFromAddr(address); michael@0: if (NULL == section_header) michael@0: return false; michael@0: michael@0: #pragma warning(push) michael@0: #pragma warning(disable: 4311) michael@0: // These casts generate warnings because they are 32 bit specific. michael@0: // Don't follow the virtual RVAToAddr, use the one on the base. michael@0: DWORD offset_within_section = reinterpret_cast(address) - michael@0: reinterpret_cast(PEImage::RVAToAddr( michael@0: section_header->VirtualAddress)); michael@0: #pragma warning(pop) michael@0: michael@0: *on_disk_offset = section_header->PointerToRawData + offset_within_section; michael@0: return true; michael@0: } michael@0: michael@0: PVOID PEImage::RVAToAddr(DWORD rva) const { michael@0: if (rva == 0) michael@0: return NULL; michael@0: michael@0: return reinterpret_cast(module_) + rva; michael@0: } michael@0: michael@0: PVOID PEImageAsData::RVAToAddr(DWORD rva) const { michael@0: if (rva == 0) michael@0: return NULL; michael@0: michael@0: PVOID in_memory = PEImage::RVAToAddr(rva); michael@0: DWORD disk_offset; michael@0: michael@0: if (!ImageAddrToOnDiskOffset(in_memory, &disk_offset)) michael@0: return NULL; michael@0: michael@0: return PEImage::RVAToAddr(disk_offset); michael@0: } michael@0: michael@0: } // namespace win michael@0: } // namespace base