Skip to content

Commit

Permalink
read/pe: rich header (gimli-rs#375)
Browse files Browse the repository at this point in the history
  • Loading branch information
daladim committed Sep 20, 2021
1 parent 5f5bf71 commit 1d8366a
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/pe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,20 @@ pub struct ImageVxdHeader {
pub e32_ddkver: U16<LE>,
}

/// A PE rich header entry
///
/// Rich headers have no official documentation, but have been heavily reversed-engineered and documented in the wild, e.g.:
/// * `http://www.ntcore.com/files/richsign.htm`
/// * `https://www.researchgate.net/figure/Structure-of-the-Rich-Header_fig1_318145388`
///
/// These data are "masked", i.e. XORed with a rich header mask, that is file-specific.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct MaskedRichHeaderEntry {
pub masked_comp_id: U32<LE>,
pub masked_count: U32<LE>,
}

//
// File header format.
//
Expand Down Expand Up @@ -2954,4 +2968,5 @@ unsafe_impl_pod!(
ImageArchitectureEntry,
ImportObjectHeader,
ImageCor20Header,
MaskedRichHeaderEntry,
);
6 changes: 6 additions & 0 deletions src/read/pe/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use core::{mem, str};
use core::convert::TryInto;

use crate::read::coff::{CoffCommon, CoffSymbol, CoffSymbolIterator, CoffSymbolTable, SymbolTable};
use crate::read::pe::RichHeaderInfos;
use crate::read::{
self, Architecture, ComdatKind, Error, Export, FileFlags, Import, NoDynamicRelocationIterator,
Object, ObjectComdat, ObjectKind, ReadError, ReadRef, Result, SectionIndex, SymbolIndex,
Expand Down Expand Up @@ -77,6 +78,11 @@ where
self.nt_headers
}

/// Returns infos about the rich header of this file (if any)
pub fn rich_header_infos(&self) -> Option<RichHeaderInfos> {
RichHeaderInfos::parse(self.data, self.dos_header.nt_headers_offset().into())
}

/// Returns the section table of this binary.
pub fn section_table(&self) -> SectionTable<'data> {
self.common.sections
Expand Down
3 changes: 3 additions & 0 deletions src/read/pe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ pub use export::*;
mod import;
pub use import::*;

mod rich;
pub use rich::*;

pub use super::coff::{SectionTable, SymbolTable};
102 changes: 102 additions & 0 deletions src/read/pe/rich.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! PE rich header handling

use alloc::vec::Vec;

use crate::{pe, LittleEndian as LE, ReadRef, U32};

/// Extracted infos about a possible Rich Header
#[derive(Debug, Clone, Copy)]
pub struct RichHeaderInfos<'data> {
/// The offset at which the rich header starts
pub start: usize,
/// The length (in bytes) of the rich header.
/// This includes the payload, but also the 16-byte start sequence and the 8-byte final "Rich" and XOR key
pub length: usize,
/// The data used to mask the rich header.
/// Unless the file has been tampered with, it should be equal to a checksum of the file header
pub mask: u32,
masked_entries: &'data [pe::MaskedRichHeaderEntry],
}

/// A PE rich header entry after it has been unmasked.
///
/// See [`crate::pe::MaskedRichHeaderEntry`]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct RichHeaderEntry {
/// ID of the component
pub comp_id: u32,
/// Number of times this component has been used when building this PE
pub count: u32,
}

impl<'data> RichHeaderInfos<'data> {
/// Try to detect a rich header in the current PE file, and locate its [`crate::pe::MaskedRichHeaderEntry`]s
pub fn parse<R: ReadRef<'data>>(data: R, nt_header_offset: u64) -> Option<Self> {
const END_MARKER: &[u8] = &[0x52, 0x69, 0x63, 0x68]; // "Rich"
const CLEARTEXT_START_MARKER: u32 = 0x536e6144; // little-endian "DanS"

// Locate the rich header, if any
// It ends with the ASCII 'Rich' string, before the NT header
// It starts at the start marker (a masked ASCII 'DanS' string)
let all_headers = data.read_bytes_at(0, nt_header_offset).ok()?;

let dos_and_rich_header = read_bytes_until_u32_sequence(all_headers, END_MARKER).ok()?;

let xor_key = data
.read_at::<U32<LE>>(dos_and_rich_header.len() as u64 + 4)
.ok()?;

let marker = U32::new(LE, CLEARTEXT_START_MARKER ^ xor_key.get(LE));
let mut start_sequence: Vec<u8> = Vec::with_capacity(16);
start_sequence.extend_from_slice(crate::pod::bytes_of(&marker));
start_sequence.extend_from_slice(crate::pod::bytes_of(xor_key));
start_sequence.extend_from_slice(crate::pod::bytes_of(xor_key));
start_sequence.extend_from_slice(crate::pod::bytes_of(xor_key));

let rich_header_start =
match read_bytes_until_u32_sequence(dos_and_rich_header, &start_sequence) {
Err(()) => return None,
Ok(slice) => slice.len(),
};
let rh_len = dos_and_rich_header.len() - rich_header_start + 8/* for the "Rich" marker and the XOR key */;

// Extract the contents of the rich header
let items_start = rich_header_start + start_sequence.len();
let items_len = rh_len - start_sequence.len() - 8;
let item_count = items_len / core::mem::size_of::<pe::MaskedRichHeaderEntry>();
let items =
match data.read_slice_at::<pe::MaskedRichHeaderEntry>(items_start as u64, item_count) {
Err(()) => return None,
Ok(items) => items,
};
Some(RichHeaderInfos {
start: rich_header_start,
length: rh_len,
mask: xor_key.get(LE),
masked_entries: items,
})
}

/// Creates a new vector of unmasked entries
pub fn unmasked_entries(&self) -> Vec<RichHeaderEntry> {
self.masked_entries
.iter()
.map(|entry| RichHeaderEntry {
comp_id: entry.masked_comp_id.get(LE) ^ self.mask,
count: entry.masked_count.get(LE) ^ self.mask,
})
.collect()
}
}

/// Read bytes until a sequence of u32-aligned values
fn read_bytes_until_u32_sequence<'a>(data: &'a [u8], needle: &[u8]) -> Result<&'a [u8], ()> {
const U32_SIZE: usize = core::mem::size_of::<u32>();

data.windows(needle.len())
.step_by(U32_SIZE)
.position(|window| window == needle)
.ok_or(())
.and_then(|n_steps| data.read_bytes_at(0, (n_steps * U32_SIZE) as u64))
}

0 comments on commit 1d8366a

Please sign in to comment.