diff --git a/dump-pe/main.cpp b/dump-pe/main.cpp index 9560089..e1c8c3e 100644 --- a/dump-pe/main.cpp +++ b/dump-pe/main.cpp @@ -227,6 +227,17 @@ int printSymbols(void *N, return 0; } +int printRich(void *N, rich_entry r) { + static_cast(N); + + std::cout << std::setw(10) << "ProdId:" << std::setw(7) << r.ProductId; + std::cout << std::setw(10) << "Build:" << std::setw(7) << r.BuildNumber; + std::cout << std::setw(10) << "Name:" + << std::setw(20) << GetRichProductName(r.ProductId, r.BuildNumber); + std::cout << std::setw(10) << "Count:" << std::setw(7) << r.Count << "\n"; + return 0; +} + int printRsrc(void *N, resource r) { static_cast(N); @@ -288,6 +299,13 @@ int main(int argc, char *argv[]) { parsed_pe *p = ParsePEFromFile(argv[1]); if (p != NULL) { + // Print Rich header info + if(p->peHeader.rich.isPresent) { + std::cout << "Rich header: present\n"; + IterRich(p, printRich, NULL); + } else { + std::cout << "Rich header: not present\n"; + } // print out some things DUMP_FIELD(Signature); DUMP_FIELD(FileHeader.Machine); diff --git a/pe-parser-library/include/parser-library/nt-headers.h b/pe-parser-library/include/parser-library/nt-headers.h index ee94091..2c714ab 100644 --- a/pe-parser-library/include/parser-library/nt-headers.h +++ b/pe-parser-library/include/parser-library/nt-headers.h @@ -26,6 +26,7 @@ THE SOFTWARE. #include #include +#include #define _offset(t, f) \ static_cast( \ @@ -36,6 +37,9 @@ THE SOFTWARE. // some constant definitions // clang-format off namespace peparse { +constexpr std::uint32_t RICH_MAGIC_END = 0x68636952; +constexpr std::uint32_t RICH_MAGIC_START = 0x536e6144; +constexpr std::uint32_t RICH_OFFSET = 0x80; constexpr std::uint16_t MZ_MAGIC = 0x5A4D; constexpr std::uint32_t NT_MAGIC = 0x00004550; constexpr std::uint16_t NUM_DIR_ENTRIES = 16; @@ -338,6 +342,20 @@ struct nt_header_32 { std::uint16_t OptionalMagic; }; +struct rich_entry { + std::uint16_t ProductId; + std::uint16_t BuildNumber; + std::uint32_t Count; +}; + +struct rich_header { + std::uint32_t StartSignature; + std::vector Entries; + std::uint32_t EndSignature; + std::uint32_t DecryptionKey; + bool isPresent; +}; + /* * This structure is only used to know how far to move the offset * when parsing resources. The data is stored in a resource_dir_entry diff --git a/pe-parser-library/include/parser-library/parse.h b/pe-parser-library/include/parser-library/parse.h index ac7d372..55f45b8 100644 --- a/pe-parser-library/include/parser-library/parse.h +++ b/pe-parser-library/include/parser-library/parse.h @@ -25,6 +25,7 @@ THE SOFTWARE. #pragma once #include +#include #include #include "nt-headers.h" @@ -153,6 +154,7 @@ uint64_t bufLen(bounded_buffer *b); struct parsed_pe_internal; typedef struct _pe_header { + rich_header rich; nt_header_32 nt; } pe_header; @@ -162,6 +164,11 @@ typedef struct _parsed_pe { pe_header peHeader; } parsed_pe; +// Resolve a Rich header product id / build number pair to a known +// product name +typedef std::pair ProductKey; +const std::string& GetRichProductName(std::uint16_t prodId, std::uint16_t buildNum); + // get parser error status as integer std::uint32_t GetPEErr(); @@ -177,6 +184,10 @@ parsed_pe *ParsePEFromFile(const char *filePath); // destruct a PE context void DestructParsedPE(parsed_pe *p); +// iterate over Rich header entries +typedef int (*iterRich)(void *, rich_entry); +void IterRich(parsed_pe *pe, iterRich cb, void *cbd); + // iterate over the resources typedef int (*iterRsrc)(void *, resource); void IterRsrc(parsed_pe *pe, iterRsrc cb, void *cbd); diff --git a/pe-parser-library/src/parse.cpp b/pe-parser-library/src/parse.cpp index 1e49187..125de84 100644 --- a/pe-parser-library/src/parse.cpp +++ b/pe-parser-library/src/parse.cpp @@ -122,6 +122,25 @@ struct parsed_pe_internal { std::vector symbols; }; +// The mapping of Rich header product id / build number pairs +// to strings +static const std::map ProductMap = { + {std::make_pair(1, 0), "Imported Functions"} +}; + +static const std::string kUnknownProduct = ""; + +// Resolve a Rich header product id / build number pair to a known +// product name +const std::string& GetRichProductName(std::uint16_t prodId, std::uint16_t buildNum) { + auto it = ProductMap.find(std::make_pair(prodId, buildNum)); + if (it != ProductMap.end()) { + return it->second; + } else { + return kUnknownProduct; + } +} + std::uint32_t err = 0; std::string err_loc; @@ -243,6 +262,14 @@ bool getSecForVA(const std::vector
&secs, VA v, section &sec) { return false; } +void IterRich(parsed_pe *pe, iterRich cb, void *cbd) { + for (rich_entry r : pe->peHeader.rich.Entries) { + if (cb(cbd, r) != 0) { + break; + } + } +} + void IterRsrc(parsed_pe *pe, iterRsrc cb, void *cbd) { parsed_pe_internal *pint = pe->internal; @@ -798,6 +825,83 @@ bool readNtHeader(bounded_buffer *b, nt_header_32 &header) { return true; } +bool readRichHeader(bounded_buffer *rich_buf, std::uint32_t key, rich_header &rich_hdr) { + if (rich_buf == nullptr) { + return false; + } + + std::uint32_t encrypted_dword; + std::uint32_t decrypted_dword; + + // Confirm DanS signature exists first. + // The first decrypted DWORD value of the rich header + // at offset 0 should be 0x536e6144 aka the "DanS" signature + if (!readDword(rich_buf, 0, encrypted_dword)) { + PE_ERR(PEERR_READ); + return false; + } + + decrypted_dword = encrypted_dword ^ key; + + if (decrypted_dword == RICH_MAGIC_START) { + // DanS magic found + rich_hdr.isPresent = true; + rich_hdr.StartSignature = decrypted_dword; + } else { + // DanS magic not found + rich_hdr.isPresent = false; + return false; + } + + // Iterate over the remaining entries. + // Start from buffer offset 16 because after "DanS" there + // are three DWORDs of zero padding that can be skipped over. + // a DWORD is 4 bytes. Loop is incrementing 8 bytes, however + // we are reading two DWORDS at a time, which is the size + // of one rich header entry. + for (std::uint32_t i = 16; i < rich_buf->bufLen-8; i += 8) { + rich_entry entry; + // Read first DWORD of entry and decrypt it + if (!readDword(rich_buf, i, encrypted_dword)) { + PE_ERR(PEERR_READ); + return false; + } + decrypted_dword = encrypted_dword ^ key; + // The high WORD of the first DWORD is the Product ID + entry.ProductId = (decrypted_dword & 0xFFFF0000) >> 16; + // The low WORD of the first DWORD is the Build Number + entry.BuildNumber = (decrypted_dword & 0xFFFF); + + // The second DWORD represents the use count + if (!readDword(rich_buf, i+4, encrypted_dword)) { + PE_ERR(PEERR_READ); + return false; + } + decrypted_dword = encrypted_dword ^ key; + // The full 32-bit DWORD is the count + entry.Count = decrypted_dword; + + // Preserve the individual entry + rich_hdr.Entries.push_back(entry); + + } + + // Preserve the end signature aka "Rich" magic + if (!readDword(rich_buf, rich_buf->bufLen-4, rich_hdr.EndSignature)) { + PE_ERR(PEERR_READ); + return false; + }; + if (rich_hdr.EndSignature != RICH_MAGIC_END) { + PE_ERR(PEERR_MAGIC); + return false; + } + + // Preserve the decryption key + rich_hdr.DecryptionKey = key; + + return true; +} + bool getHeader(bounded_buffer *file, pe_header &p, bounded_buffer *&rem) { if (file == nullptr) { return false; @@ -823,6 +927,53 @@ bool getHeader(bounded_buffer *file, pe_header &p, bounded_buffer *&rem) { } curOffset += offset; + // read rich header + std::uint32_t dword; + std::uint32_t rich_end_signature_offset; + std::uint32_t xor_key; + bool found_rich = false; + + // Start reading from RICH_OFFSET (0x80), a known Rich header offset. + // Note: 0x80 is based on anecdotal evidence. + // + // Iterate over the DWORDs, hence why i increments 4 bytes at a time. + for (std::uint32_t i = RICH_OFFSET; i < offset; i += 4) { + if (!readDword(file, i, dword)) { + PE_ERR(PEERR_READ); + return false; + } + + // Found the trailing Rich signature + if (dword == RICH_MAGIC_END) { + found_rich = true; + rich_end_signature_offset = i; + break; + } + } + + if (found_rich) { + // Get the XOR decryption key. It is the DWORD immediately + // after the Rich signature. + if (!readDword(file, rich_end_signature_offset + 4, xor_key)) { + PE_ERR(PEERR_READ); + return false; + } + + // Split the Rich header out into its own buffer + bounded_buffer *richBuf = splitBuffer(file, 0x80, rich_end_signature_offset + 4); + if (richBuf == nullptr) { + return false; + } + + readRichHeader(richBuf, xor_key, p.rich); + if (richBuf != nullptr) { + deleteBuffer(richBuf); + } + + } else { + p.rich.isPresent = false; + } + // now, we can read out the fields of the NT headers bounded_buffer *ntBuf = splitBuffer(file, curOffset, file->bufLen);