From f25ae48fcd03c048438184a122857aa531312fcf Mon Sep 17 00:00:00 2001 From: Ladislav Zezula Date: Mon, 14 Jun 2021 15:33:58 +0200 Subject: [PATCH 1/3] Data Directories are copied up do 16, regardless of NumberOfRvaAndSizes, because of .NET framework doesn't care --- include/retdec/pelib/ImageLoader.h | 1 + src/pelib/ImageLoader.cpp | 81 ++++++++++++++++++------------ 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/include/retdec/pelib/ImageLoader.h b/include/retdec/pelib/ImageLoader.h index d6589be2c..51e395c5d 100644 --- a/include/retdec/pelib/ImageLoader.h +++ b/include/retdec/pelib/ImageLoader.h @@ -382,6 +382,7 @@ class ImageLoader int captureImageSections(ByteBuffer & fileData); int captureOptionalHeader32(std::uint8_t * fileData, std::uint8_t * filePtr, std::uint8_t * fileEnd); int captureOptionalHeader64(std::uint8_t * fileData, std::uint8_t * filePtr, std::uint8_t * fileEnd); + std::uint32_t copyDataDirectories(std::uint8_t * optionalHeaderPtr, std::uint8_t * dataDirectoriesPtr, std::size_t optionalHeaderMax, std::uint32_t numberOfRvaAndSizes); int verifyDosHeader(PELIB_IMAGE_DOS_HEADER & hdr, std::size_t fileSize); int verifyDosHeader(std::istream & fs, std::streamoff fileOffset, std::size_t fileSize); diff --git a/src/pelib/ImageLoader.cpp b/src/pelib/ImageLoader.cpp index 6bb7bb866..9bc72c1e2 100644 --- a/src/pelib/ImageLoader.cpp +++ b/src/pelib/ImageLoader.cpp @@ -879,7 +879,7 @@ int PeLib::ImageLoader::Load( if(fileError != ERROR_NONE) return fileError; - // Check and capture NT headers. Don't go any further than here if the NT headers were detected as bad. + // Check and capture NT headers. Don't go any fuhrter than here if the NT headers were detected as bad. // Sample: retdec-regression-tests\tools\fileinfo\features\pe-loader-corruptions\001-pe-header-cut-001.ex_ fileError = captureNtHeaders(fileData); if(fileError != ERROR_NONE || ldrError == LDR_ERROR_NTHEADER_OUT_OF_FILE) @@ -2178,15 +2178,42 @@ int PeLib::ImageLoader::loadImageAsIs(ByteBuffer & fileData) return ERROR_NONE; } +// While copying the data directories, we take into account possible out-of-bounds +// data directory entries, as long as they fit into sizeOfOptionalHeader +// Sample: 53b13d7cfb97b5475e21717397c85376a1de2b18c2eeb6532b160fb8aa3a393d +// This sample has NumberOfRvaAndSizes set to 0x0E, but the IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR (0x0E), +// but the .NET framework (_CorExeMain) does not care about NumberOfRvaAndSizes +// and directly takes the DataDirectory without checking NumberOfRvaAndSizes +std::uint32_t PeLib::ImageLoader::copyDataDirectories( + uint8_t * optionalHeaderPtr, + uint8_t * dataDirectoriesPtr, + size_t optionalHeaderMax, // How many bytes do we have from the beginning of the optional header till the end of the file + uint32_t numberOfRvaAndSizes) +{ + uint8_t * dataDirectoriesEnd = dataDirectoriesPtr + PELIB_IMAGE_NUMBEROF_DIRECTORY_ENTRIES * sizeof(PELIB_IMAGE_DATA_DIRECTORY); + + // Do not leave numberOfRvaAndSizes higher than the maximum possible value + if(numberOfRvaAndSizes > PELIB_IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + numberOfRvaAndSizes = PELIB_IMAGE_NUMBEROF_DIRECTORY_ENTRIES; + + // Determine the end of data directories based on file size. + // Note that the SizeOfOptionalHeader does NOT have any meaning in this + if(dataDirectoriesEnd > (optionalHeaderPtr + optionalHeaderMax)) + dataDirectoriesEnd = (optionalHeaderPtr + optionalHeaderMax); + + // Copy data directories, up to SizeOfOptionalHeader + if(dataDirectoriesEnd > dataDirectoriesPtr) + memcpy(optionalHeader.DataDirectory, dataDirectoriesPtr, (dataDirectoriesEnd - dataDirectoriesPtr)); + return numberOfRvaAndSizes; +} + int PeLib::ImageLoader::captureOptionalHeader64( uint8_t * fileBegin, uint8_t * filePtr, uint8_t * fileEnd) { PELIB_IMAGE_OPTIONAL_HEADER64 optionalHeader64{}; - uint8_t * dataDirectoryPtr; uint32_t sizeOfOptionalHeader = sizeof(PELIB_IMAGE_OPTIONAL_HEADER64); - uint32_t numberOfRvaAndSizes; // Capture optional header. Note that IMAGE_FILE_HEADER::SizeOfOptionalHeader // is not taken into account by the Windows loader - it simply assumes that the entire optional header is present @@ -2230,18 +2257,10 @@ int PeLib::ImageLoader::captureOptionalHeader64( optionalHeader.NumberOfRvaAndSizes = optionalHeader64.NumberOfRvaAndSizes; // Copy data directories - if((numberOfRvaAndSizes = optionalHeader64.NumberOfRvaAndSizes) > PELIB_IMAGE_NUMBEROF_DIRECTORY_ENTRIES) - numberOfRvaAndSizes = PELIB_IMAGE_NUMBEROF_DIRECTORY_ENTRIES; - memcpy(optionalHeader.DataDirectory, optionalHeader64.DataDirectory, sizeof(PELIB_IMAGE_DATA_DIRECTORY) * numberOfRvaAndSizes); - - // Cut the real number of data directory entries by the file size - dataDirectoryPtr = filePtr + offsetof(PELIB_IMAGE_OPTIONAL_HEADER64, DataDirectory); - if(dataDirectoryPtr < fileEnd) - { - if((dataDirectoryPtr + numberOfRvaAndSizes * sizeof(PELIB_IMAGE_DATA_DIRECTORY)) > fileEnd) - numberOfRvaAndSizes = (fileEnd - dataDirectoryPtr + sizeof(PELIB_IMAGE_DATA_DIRECTORY) - 1) / sizeof(PELIB_IMAGE_DATA_DIRECTORY); - } - realNumberOfRvaAndSizes = numberOfRvaAndSizes; + realNumberOfRvaAndSizes = copyDataDirectories((uint8_t *)(&optionalHeader64), + (uint8_t *)(&optionalHeader64.DataDirectory[0]), + fileEnd - filePtr, + optionalHeader64.NumberOfRvaAndSizes); // Remember the offset of the checksum field checkSumFileOffset = (filePtr - fileBegin) + offsetof(PELIB_IMAGE_OPTIONAL_HEADER64, CheckSum); @@ -2255,9 +2274,7 @@ int PeLib::ImageLoader::captureOptionalHeader32( uint8_t * fileEnd) { PELIB_IMAGE_OPTIONAL_HEADER32 optionalHeader32{}; - uint8_t * dataDirectoryPtr; uint32_t sizeOfOptionalHeader = sizeof(PELIB_IMAGE_OPTIONAL_HEADER32); - uint32_t numberOfRvaAndSizes; // Capture optional header. Note that IMAGE_FILE_HEADER::SizeOfOptionalHeader // is not taken into account by the Windows loader - it simply assumes that the entire optional header is present @@ -2302,18 +2319,10 @@ int PeLib::ImageLoader::captureOptionalHeader32( optionalHeader.NumberOfRvaAndSizes = optionalHeader32.NumberOfRvaAndSizes; // Copy data directories - if((numberOfRvaAndSizes = optionalHeader32.NumberOfRvaAndSizes) > PELIB_IMAGE_NUMBEROF_DIRECTORY_ENTRIES) - numberOfRvaAndSizes = PELIB_IMAGE_NUMBEROF_DIRECTORY_ENTRIES; - memcpy(optionalHeader.DataDirectory, optionalHeader32.DataDirectory, sizeof(PELIB_IMAGE_DATA_DIRECTORY) * numberOfRvaAndSizes); - - // Cut the real number of data directory entries by the file size - dataDirectoryPtr = filePtr + offsetof(PELIB_IMAGE_OPTIONAL_HEADER32, DataDirectory); - if(dataDirectoryPtr < fileEnd) - { - if((dataDirectoryPtr + numberOfRvaAndSizes * sizeof(PELIB_IMAGE_DATA_DIRECTORY)) > fileEnd) - numberOfRvaAndSizes = (fileEnd - dataDirectoryPtr + sizeof(PELIB_IMAGE_DATA_DIRECTORY) - 1) / sizeof(PELIB_IMAGE_DATA_DIRECTORY); - } - realNumberOfRvaAndSizes = numberOfRvaAndSizes; + realNumberOfRvaAndSizes = copyDataDirectories((uint8_t *)(&optionalHeader32), + (uint8_t *)(&optionalHeader32.DataDirectory[0]), + fileEnd - filePtr, + optionalHeader32.NumberOfRvaAndSizes); // Remember the offset of the checksum field checkSumFileOffset = (filePtr - fileBegin) + offsetof(PELIB_IMAGE_OPTIONAL_HEADER32, CheckSum); @@ -2799,9 +2808,13 @@ bool PeLib::ImageLoader::checkForSectionTablesWithinHeader(uint32_t e_lfanew) { uint32_t OffsetToSectionTable = sizeof(uint32_t) + sizeof(PELIB_IMAGE_FILE_HEADER) + fileHeader.SizeOfOptionalHeader; uint32_t NumberOfSubsections = fileHeader.NumberOfSections; - uint32_t NtHeaderSize = PELIB_PAGE_SIZE - e_lfanew; + uint32_t NtHeaderSize; + + // Sample: retdec-regression-tests\features\corkami\inputs\96emptysections.ex + // Must count with more pages if the header size is greater than one page + NtHeaderSize = AlignToSize(optionalHeader.SizeOfHeaders, PELIB_PAGE_SIZE) - e_lfanew; - // If this condition is true, then the image header contains data up fo SizeofHeaders + // If this condition is true, then the image header contains data up to SizeofHeaders // If not, the image header contains the entire page. if((e_lfanew + OffsetToSectionTable + (NumberOfSubsections + 1) * sizeof(PELIB_IMAGE_SECTION_HEADER)) <= NtHeaderSize) return false; @@ -2886,8 +2899,10 @@ void PeLib::ImageLoader::compareWithWindowsMappedImage( size_t mismatchOffset; size_t rva = 0; - // Are both loaded? - if(winImageData != nullptr && isImageMappedOk()) + // Check if the image was loaded by both Windows and us + // Note that in Windows 7, the image can actually be mapped at base address 0 + // Sample: retdec-regression-tests\features\corkami\inputs\ibnullXP.ex + if((winImageData || imageSize) && isImageMappedOk()) { // Check whether the image size is the same if(imageSize != getSizeOfImageAligned()) From 91614d5c540abbf78f1ad4df6c173036541297d7 Mon Sep 17 00:00:00 2001 From: Ladislav Zezula Date: Tue, 15 Jun 2021 09:50:46 +0200 Subject: [PATCH 2/3] Added handling of .NET edge cases --- .../fileformat/file_format/pe/pe_format.h | 1 + .../file_format/pe/pe_format_parser.h | 7 +++++ include/retdec/pelib/ImageLoader.h | 18 +++++++++++ src/fileformat/file_format/pe/pe_format.cpp | 30 +++++++++++++++++-- src/pelib/ComHeaderDirectory.cpp | 4 +-- src/pelib/PeFile.cpp | 4 +-- 6 files changed, 57 insertions(+), 7 deletions(-) diff --git a/include/retdec/fileformat/file_format/pe/pe_format.h b/include/retdec/fileformat/file_format/pe/pe_format.h index c3d6f0d87..1db489593 100644 --- a/include/retdec/fileformat/file_format/pe/pe_format.h +++ b/include/retdec/fileformat/file_format/pe/pe_format.h @@ -187,6 +187,7 @@ class PeFormat : public FileFormat bool getNumberOfRelocations(unsigned long long &relocs) const; bool getDataDirectoryRelative(unsigned long long index, unsigned long long &relAddr, unsigned long long &size) const; bool getDataDirectoryAbsolute(unsigned long long index, unsigned long long &absAddr, unsigned long long &size) const; + bool getComDirectoryRelative(unsigned long long &relAddr, unsigned long long &size) const; const PeCoffSection* getPeSection(const std::string &secName) const; const PeCoffSection* getPeSection(unsigned long long secIndex) const; const CLRHeader* getCLRHeader() const; diff --git a/include/retdec/fileformat/file_format/pe/pe_format_parser.h b/include/retdec/fileformat/file_format/pe/pe_format_parser.h index ddf1d8e3f..e8365e977 100644 --- a/include/retdec/fileformat/file_format/pe/pe_format_parser.h +++ b/include/retdec/fileformat/file_format/pe/pe_format_parser.h @@ -292,6 +292,13 @@ class PeFormatParser return (relAddr != 0); } + bool getComDirectoryRelative(unsigned long long &relAddr, unsigned long long &size) const + { + relAddr = peFile->imageLoader().getComDirRva(); + size = peFile->imageLoader().getComDirSize(); + return (relAddr != 0); + } + bool getDataDirectoryAbsolute(unsigned long long index, unsigned long long &absAddr, unsigned long long &size) const { if(getDataDirectoryRelative(index, absAddr, size)) diff --git a/include/retdec/pelib/ImageLoader.h b/include/retdec/pelib/ImageLoader.h index 51e395c5d..89fe1da1c 100644 --- a/include/retdec/pelib/ImageLoader.h +++ b/include/retdec/pelib/ImageLoader.h @@ -324,6 +324,24 @@ class ImageLoader return (optionalHeader.NumberOfRvaAndSizes > dataDirIndex) ? optionalHeader.DataDirectory[dataDirIndex].Size : 0; } + std::uint32_t getComDirRva() const + { + // For 32-bit binaries, the COM directory is valid even if NumberOfRvaAndSizes < IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR + // Sample: 58b0147d7dd3cd73cb8bf8df077e244650621174f7ff788ad06fd0c1f82aac40 + if(optionalHeader.Magic == PELIB_IMAGE_NT_OPTIONAL_HDR32_MAGIC) + return optionalHeader.DataDirectory[PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress; + return getDataDirRva(PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR); + } + + std::uint32_t getComDirSize() const + { + // For 32-bit binaries, the COM directory is valid even if NumberOfRvaAndSizes < IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR + // Sample: 58b0147d7dd3cd73cb8bf8df077e244650621174f7ff788ad06fd0c1f82aac40 + if(optionalHeader.Magic == PELIB_IMAGE_NT_OPTIONAL_HDR32_MAGIC) + return optionalHeader.DataDirectory[PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].Size; + return getDataDirSize(PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR); + } + std::uint64_t getVirtualAddressMasked(std::uint32_t rva) { std::uint64_t virtualAddress = getImageBase() + rva; diff --git a/src/fileformat/file_format/pe/pe_format.cpp b/src/fileformat/file_format/pe/pe_format.cpp index 3bb993c1f..0a0a13fca 100644 --- a/src/fileformat/file_format/pe/pe_format.cpp +++ b/src/fileformat/file_format/pe/pe_format.cpp @@ -1835,17 +1835,27 @@ void PeFormat::loadDotnetHeaders() { std::uint64_t metadataHeaderAddress = 0; - // If our file contains CLR header, then use it + // If our file contains CLR header, then use it. Note that .NET framework doesn't + // verify the OPTIONAL_HEADER::NumberOfRvaAndSizes, so we must do it the same way. unsigned long long comHeaderAddress, comHeaderSize; - if(getDataDirectoryRelative(PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, comHeaderAddress, comHeaderSize) && comHeaderSize) + if(getComDirectoryRelative(comHeaderAddress, comHeaderSize) && comHeaderSize) { clrHeader = formatParser->getClrHeader(); metadataHeaderAddress = formatParser->getImageBaseAddress() + clrHeader->getMetadataDirectoryAddress(); } + else + { + return; + } + // If not, then try to guess whether the file could possibly be .NET file based on imports and try to search for metadata header + // LZ: Don't. This will lead to the situation when totally unrelated .NET metadata will be read from the binary, + // for example from a binary embedded in resources or in overlay. + // Sample: 76360c777ac93d7fc7398b5d140c4117eb08501cac30d170f33ab260e1788e74 + /* else { - if (importTable && importTable->getNumberOfImportsInLibraryCaseInsensitive("mscoree.dll")) + if (importTable && importTable->getImport("mscoree.dll")) { metadataHeaderAddress = detectPossibleMetadataHeaderAddress(); if (metadataHeaderAddress == 0) @@ -1856,6 +1866,7 @@ void PeFormat::loadDotnetHeaders() return; } } + */ // This explicit initialization needs to be here, because clang 4.0 has bug in optimizer and it causes problem in valgrind. std::uint64_t signature = 0; @@ -3294,6 +3305,19 @@ bool PeFormat::getDataDirectoryAbsolute(unsigned long long index, unsigned long return formatParser->getDataDirectoryAbsolute(index, absAddr, size); } +/** +* Special for .NET data directory to correctly process data directory on 32-bit binaries +* @param relAddr Into this parameter is stored relative virtual address of directory +* @param size Into this parameter is stored size of directory +* @return @c true if index of selected directory is valid, @c false otherwise +* +* If method returns @c false, @a relAddr and @a size are left unchanged. +*/ +bool PeFormat::getComDirectoryRelative(unsigned long long &relAddr, unsigned long long &size) const +{ + return formatParser->getComDirectoryRelative(relAddr, size); +} + /** * Get information about section with name @a secName * @param secName Name of section diff --git a/src/pelib/ComHeaderDirectory.cpp b/src/pelib/ComHeaderDirectory.cpp index ff40af3ee..deb083ee0 100644 --- a/src/pelib/ComHeaderDirectory.cpp +++ b/src/pelib/ComHeaderDirectory.cpp @@ -22,8 +22,8 @@ namespace PeLib int ComHeaderDirectory::read(ImageLoader & imageLoader) { - std::uint32_t rva = imageLoader.getDataDirRva(PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR); - std::uint32_t size = imageLoader.getDataDirSize(PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR); + std::uint32_t rva = imageLoader.getComDirRva(); + std::uint32_t size = imageLoader.getComDirSize(); std::uint32_t sizeOfImage = imageLoader.getSizeOfImage(); if(rva >= sizeOfImage || (rva + size) > sizeOfImage) { diff --git a/src/pelib/PeFile.cpp b/src/pelib/PeFile.cpp index 85557d131..b7cc473da 100644 --- a/src/pelib/PeFile.cpp +++ b/src/pelib/PeFile.cpp @@ -380,8 +380,8 @@ namespace PeLib int PeFileT::readComHeaderDirectory() { - if(m_imageLoader.getDataDirRva(PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR) && - m_imageLoader.getDataDirSize(PELIB_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)) + // Need to do this regardless of NumberOf + if(m_imageLoader.getComDirRva() && m_imageLoader.getComDirSize()) { return comDir().read(m_imageLoader); } From 4694d57fd18603b209f29029be74cbed979d0b5b Mon Sep 17 00:00:00 2001 From: Ladislav Zezula Date: Fri, 18 Jun 2021 09:38:21 +0200 Subject: [PATCH 3/3] Added image loader fix for Windows 11 --- include/retdec/pelib/ImageLoader.h | 1 + src/pelib/ImageLoader.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/retdec/pelib/ImageLoader.h b/include/retdec/pelib/ImageLoader.h index 89fe1da1c..0b7da1534 100644 --- a/include/retdec/pelib/ImageLoader.h +++ b/include/retdec/pelib/ImageLoader.h @@ -490,6 +490,7 @@ class ImageLoader bool forceIntegrityCheckCertificate; // If true, extra check for certificate will be provided bool checkNonLegacyDllCharacteristics; // If true, extra checks will be performed on DllCharacteristics bool checkImagePostMapping; // If true, extra checks will be performed after the image is mapped + bool alignSingleSectionImagesToPage; // Align single-section images to page size in 64-bit windows }; } // namespace PeLib diff --git a/src/pelib/ImageLoader.cpp b/src/pelib/ImageLoader.cpp index 9bc72c1e2..1b7308586 100644 --- a/src/pelib/ImageLoader.cpp +++ b/src/pelib/ImageLoader.cpp @@ -116,6 +116,9 @@ PeLib::ImageLoader::ImageLoader(uint32_t versionInfo) // Since build 18362, extra checks are performed on non-intel platforms checkNonLegacyDllCharacteristics = (windowsBuildNumber >= 18362); + + // Since build 21996, single-section images only contain data up to the image size + alignSingleSectionImagesToPage = !(windowsBuildNumber >= 21996); } } @@ -2118,7 +2121,7 @@ int PeLib::ImageLoader::captureImageSections(ByteBuffer & fileData) // * Windows 10: no align // If the image is smaller than one page, it is aligned to one page sizeOfImage = AlignToSize(sizeOfImage, ssiImageAlignment32); - if(is64BitWindows) + if(is64BitWindows && alignSingleSectionImagesToPage) sizeOfImage = AlignToSize(sizeOfImage, PELIB_PAGE_SIZE); if(sizeOfImage < PELIB_PAGE_SIZE) sizeOfImage = PELIB_PAGE_SIZE;