mirror of
https://github.com/QuasarApp/pe-parse.git
synced 2025-04-26 04:14:32 +00:00
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:
parent
6ee67f63e1
commit
1544c61c38
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user