Skip to content

Commit

Permalink
read: Add PE resource navigation
Browse files Browse the repository at this point in the history
Add helpers for navigating a PE file resource directory
  • Loading branch information
Guiguiprim committed Feb 11, 2022
1 parent 927b511 commit fda29ca
Show file tree
Hide file tree
Showing 6 changed files with 714 additions and 2 deletions.
4 changes: 4 additions & 0 deletions crates/examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ required-features = ["object/read_core", "object/write_core", "object/pe", "obje
[[bin]]
name = "readobj"
required-features = ["read"]

[[bin]]
name = "perscdump"
required-features = ["object/read"]
157 changes: 157 additions & 0 deletions crates/examples/src/bin/perscdump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//! A example binary that print the resource hierarchy inside a PE file.
use object::{
endian::LittleEndian as LE,
pe,
read::pe::{
language_code_to_bcp47code, resource_type_name, ImageNtHeaders, ResourceDirectoryEntry,
ResourceDirectoryTable, ResourceNameOrId,
},
};
use std::{env, fs, process};

fn main() {
let mut args = env::args();
if args.len() != 2 {
eprintln!("Usage: {} <pe_file>", args.next().unwrap());
process::exit(1);
}

args.next();
let file_path = args.next().unwrap();

let file = match fs::File::open(&file_path) {
Ok(file) => file,
Err(err) => {
eprintln!("Failed to open file '{}': {}", file_path, err,);
process::exit(1);
}
};
let data = match unsafe { memmap2::Mmap::map(&file) } {
Ok(mmap) => mmap,
Err(err) => {
eprintln!("Failed to map file '{}': {}", file_path, err,);
process::exit(1);
}
};
let data = &*data;

let kind = match object::FileKind::parse(data) {
Ok(file) => file,
Err(err) => {
eprintln!("Failed to parse file: {}", err);
process::exit(1);
}
};
let res = match kind {
object::FileKind::Pe32 => dump_resource::<pe::ImageNtHeaders32>(data),
object::FileKind::Pe64 => dump_resource::<pe::ImageNtHeaders64>(data),
_ => {
eprintln!("Not a PE file");
process::exit(1);
}
};

if let Err(err) = res {
eprintln!("{}", err);
process::exit(1);
}
}

fn dump_resource<Pe: ImageNtHeaders>(data: &[u8]) -> Result<(), String> {
let pe_file = object::read::pe::PeFile::<Pe>::parse(data).map_err(|e| e.to_string())?;
match pe_file.resource_directory_table() {
Ok(table) => {
println!("- Root directory:");
dump_table(&table, 0);
}
Err(err) => return Err(format!("Couldn't found resource: {}", err)),
}
Ok(())
}

fn dump_table(dir: &ResourceDirectoryTable, level: usize) {
let indent = level * 2 + 2;
for entry in dir.iter() {
match entry {
ResourceDirectoryEntry::Directory(dir) => {
println!("{:>width$}- Directory", "", width = indent);
let indent = indent + 2;
print_name(dir.name(), level);

match dir.table() {
Ok(table) => {
dump_table(&table, level + 1);
}
Err(err) => {
println!(
"{:>width$}Could not read dir table: {}",
"",
err,
width = indent
);
}
}
}
ResourceDirectoryEntry::Entry(rsc) => {
println!("{:>width$}- Entry", "", width = indent);
let indent = indent + 2;
print_name(rsc.name(), level);
match rsc.data_entry() {
Ok(data) => {
println!(
"{:>width$}Offset: {}",
"",
data.offset_to_data.get(LE),
width = indent
);
println!("{:>width$}Size: {}", "", data.size.get(LE), width = indent);
}
Err(err) => {
println!(
"{:>width$}Could not read data entry: {}",
"",
err,
width = indent
);
}
}
}
}
}
}

fn print_name(name: ResourceNameOrId, level: usize) {
let indent = level * 2 + 4;
match name {
ResourceNameOrId::Name(name) => match name.to_string_lossy() {
Ok(name) => println!("{:>width$}Name: {}", "", name, width = indent),
_ => println!(
"{:>width$}Name buffer: {:?}",
"",
name.data(),
width = indent
),
},
ResourceNameOrId::Id(id) => {
let mut done = false;
if level == 0 {
if let Some(name) = resource_type_name(id) {
println!("{:>width$}Name from id: {}", "", name, width = indent);
done = true;
}
}

if level == 2 {
if let Some(name) = language_code_to_bcp47code(id) {
println!("{:>width$}Language: {}", "", name, width = indent);
done = true;
}
}

if !done {
println!("{:>width$}Name id: {}", "", id, width = indent);
}
}
}
}
35 changes: 34 additions & 1 deletion src/pe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2001,11 +2001,13 @@ pub struct ImageResourceDirectory {
pub minor_version: U16<LE>,
pub number_of_named_entries: U16<LE>,
pub number_of_id_entries: U16<LE>,
// DirectoryEntries[ImageResourceDirectoryEntry];
}

pub const IMAGE_RESOURCE_NAME_IS_STRING: u32 = 0x8000_0000;
pub const IMAGE_RESOURCE_DATA_IS_DIRECTORY: u32 = 0x8000_0000;
pub const IMAGE_RESOURCE_NAME_OFFSET_MASK: u32 = 0x7FFF_FFFF;
pub const IMAGE_RESOURCE_NAME_ID_MASK: u32 = 0x0000_FFFF;
pub const IMAGE_RESOURCE_DATA_OFFSET_MASK: u32 = 0x7FFF_FFFF;
//
// Each directory contains the 32-bit Name of the entry and an offset,
// relative to the beginning of the resource directory of the data associated
Expand All @@ -2028,6 +2030,37 @@ pub struct ImageResourceDirectoryEntry {
pub offset_to_data_or_directory: U32<LE>,
}

impl ImageResourceDirectoryEntry {
/// Returns true if the entry is a sub-directory
pub fn is_directory(&self) -> bool {
(self.offset_to_data_or_directory.get(LE) & IMAGE_RESOURCE_DATA_IS_DIRECTORY) > 0
}

/// Returns the offset to the associated directory or data struct
pub fn data_offset(&self) -> u32 {
self.offset_to_data_or_directory.get(LE) & IMAGE_RESOURCE_DATA_OFFSET_MASK
}

/// Returns true if the name is an custom string
pub fn has_string_name(&self) -> bool {
(self.name_or_id.get(LE) & IMAGE_RESOURCE_NAME_IS_STRING) > 0
}

/// Returns the name string offset
///
/// Valide if `has_string_name()` returns true
pub fn name_offset(&self) -> u32 {
self.name_or_id.get(LE) & IMAGE_RESOURCE_NAME_OFFSET_MASK
}

/// Returns the name id
///
/// Valide if `has_string_name()` returns false
pub fn name_id(&self) -> u16 {
(self.name_or_id.get(LE) & IMAGE_RESOURCE_NAME_ID_MASK) as u16
}
}

//
// For resource directory entries that have actual string names, the Name
// field of the directory entry points to an object of the following type.
Expand Down
7 changes: 6 additions & 1 deletion src/read/pe/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{pe, ByteString, Bytes, CodeView, LittleEndian as LE, Pod, U32};

use super::{
DataDirectories, ExportTable, ImageThunkData, ImportTable, PeSection, PeSectionIterator,
PeSegment, PeSegmentIterator, RichHeaderInfo, SectionTable,
PeSegment, PeSegmentIterator, RichHeaderInfo, SectionTable, ResourceDirectoryTable,
};

/// A PE32 (32-bit) image file.
Expand Down Expand Up @@ -118,6 +118,11 @@ where
pub(super) fn section_alignment(&self) -> u64 {
u64::from(self.nt_headers.optional_header().section_alignment())
}

/// Returns the root resource directory table
pub fn resource_directory_table(&self) -> Result<ResourceDirectoryTable<'data>> {
ResourceDirectoryTable::parse(self)
}
}

impl<'data, Pe, R> read::private::Sealed for PeFile<'data, Pe, R>
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 @@ -25,6 +25,9 @@ pub use import::*;
mod relocation;
pub use relocation::*;

mod resource;
pub use resource::*;

mod rich;
pub use rich::*;

Expand Down
Loading

0 comments on commit fda29ca

Please sign in to comment.