Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pe rich header #375

Merged
merged 12 commits into from
Sep 20, 2021
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>,
daladim marked this conversation as resolved.
Show resolved Hide resolved
}

//
// 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,
daladim marked this conversation as resolved.
Show resolved Hide resolved
/// 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))
}