Initial impl. of Rich header parser. (#89)

* Initial impl. of Rich header parser. Closes trailofbits/pe-parse#83

* Fix const correctness per @woodruffw PR review
This commit is contained in:
Stefan Siegfried 2019-10-14 15:57:54 -04:00 committed by William Woodruff
parent 6ee67f63e1
commit 1544c61c38
4 changed files with 198 additions and 0 deletions

View File

@ -227,6 +227,17 @@ int printSymbols(void *N,
return 0;
}
int printRich(void *N, rich_entry r) {
static_cast<void>(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<void>(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);

View File

@ -26,6 +26,7 @@ THE SOFTWARE.
#include <cstdint>
#include <string>
#include <vector>
#define _offset(t, f) \
static_cast<std::uint32_t>( \
@ -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<rich_entry> 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

View File

@ -25,6 +25,7 @@ THE SOFTWARE.
#pragma once
#include <cstdint>
#include <map>
#include <string>
#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<std::uint16_t, std::uint16_t> 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);

View File

@ -122,6 +122,25 @@ struct parsed_pe_internal {
std::vector<symbol> symbols;
};
// The mapping of Rich header product id / build number pairs
// to strings
static const std::map<ProductKey, const std::string> ProductMap = {
{std::make_pair(1, 0), "Imported Functions"}
};
static const std::string kUnknownProduct = "<unknown>";
// 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<section> &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);