From 34076c07351bd88c6728b340e179908463178b32 Mon Sep 17 00:00:00 2001 From: Guiguiprim Date: Wed, 16 Feb 2022 01:43:18 +0100 Subject: [PATCH] read: Add PE resource navigation (#425) Add helpers for navigating a PE file resource directory --- crates/examples/src/readobj/pe.rs | 52 ++++ .../testfiles/pe/base-gnu.exe.readobj | 26 ++ src/pe.rs | 1 - src/read/pe/data_directory.rs | 20 +- src/read/pe/mod.rs | 3 + src/read/pe/resource.rs | 289 ++++++++++++++++++ 6 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 src/read/pe/resource.rs diff --git a/crates/examples/src/readobj/pe.rs b/crates/examples/src/readobj/pe.rs index 131cf048..94b2389f 100644 --- a/crates/examples/src/readobj/pe.rs +++ b/crates/examples/src/readobj/pe.rs @@ -92,6 +92,7 @@ fn print_pe(p: &mut Printer<'_>, data: &[u8]) { print_export_dir(p, data, §ions, &data_directories); print_import_dir::(p, data, §ions, &data_directories); print_reloc_dir(p, data, machine, §ions, &data_directories); + print_resource_dir(p, data, §ions, &data_directories); } } } @@ -308,6 +309,57 @@ fn print_import_dir( Some(()) } +fn print_resource_dir( + p: &mut Printer<'_>, + data: &[u8], + sections: &SectionTable, + data_directories: &DataDirectories, +) -> Option<()> { + let rsc_table = data_directories + .resource_directory_table(data, sections) + .print_err(p)??; + p.group("ResourceDirectory", |p| print_resource_table(p, &rsc_table)); + Some(()) +} + +fn print_resource_table(p: &mut Printer<'_>, table: &ResourceDirectoryTable<'_>) { + p.group("Directory", |p| { + p.field( + "Number of named entries", + table.table.number_of_named_entries.get(LE), + ); + p.field( + "Number of ID entries", + table.table.number_of_id_entries.get(LE), + ); + p.group("Entries", |p| { + for e in table.iter() { + match e.name() { + ResourceNameOrId::Name(name) => match name.to_string_lossy() { + Ok(name) => p.field("Name", name), + Err(_) => p.field("Name", "Invalid"), + }, + ResourceNameOrId::Id(id) => { + p.field("Name ID", id); + } + } + + match e.data() { + Ok(ResourceDirectoryEntryData::Directory(table)) => { + print_resource_table(p, &table) + } + Ok(ResourceDirectoryEntryData::Entry(rsc)) => { + p.field_hex("VirtualAddress", rsc.offset_to_data.get(LE)); + p.field("Size", rsc.size.get(LE)); + p.field("Code page", rsc.code_page.get(LE)); + } + _ => p.field("Data", "Invalid"), + } + } + }); + }) +} + fn print_sections( p: &mut Printer<'_>, data: &[u8], diff --git a/crates/examples/testfiles/pe/base-gnu.exe.readobj b/crates/examples/testfiles/pe/base-gnu.exe.readobj index c5ee365d..51af9385 100644 --- a/crates/examples/testfiles/pe/base-gnu.exe.readobj +++ b/crates/examples/testfiles/pe/base-gnu.exe.readobj @@ -15687,3 +15687,29 @@ ImageBaseRelocation { Type: IMAGE_REL_BASED_DIR64 (0xA) Addend: 0x140001690 } +ResourceDirectory { + Directory { + Number of named entries: 0 + Number of ID entries: 1 + Entries { + Name ID: 24 + Directory { + Number of named entries: 0 + Number of ID entries: 1 + Entries { + Name ID: 1 + Directory { + Number of named entries: 0 + Number of ID entries: 1 + Entries { + Name ID: 0 + VirtualAddress: 0x10058 + Size: 1167 + Code page: 0 + } + } + } + } + } + } +} diff --git a/src/pe.rs b/src/pe.rs index 83a01d46..3ccbfd34 100644 --- a/src/pe.rs +++ b/src/pe.rs @@ -2001,7 +2001,6 @@ pub struct ImageResourceDirectory { pub minor_version: U16, pub number_of_named_entries: U16, pub number_of_id_entries: U16, - // DirectoryEntries[ImageResourceDirectoryEntry]; } pub const IMAGE_RESOURCE_NAME_IS_STRING: u32 = 0x8000_0000; diff --git a/src/read/pe/data_directory.rs b/src/read/pe/data_directory.rs index 7c6fad22..27ef7ddd 100644 --- a/src/read/pe/data_directory.rs +++ b/src/read/pe/data_directory.rs @@ -3,7 +3,9 @@ use core::slice; use crate::read::{Error, ReadError, ReadRef, Result}; use crate::{pe, LittleEndian as LE}; -use super::{ExportTable, ImportTable, RelocationBlockIterator, SectionTable}; +use super::{ + ExportTable, ImportTable, RelocationBlockIterator, ResourceDirectoryTable, SectionTable, +}; /// The table of data directories in a PE file. #[derive(Debug, Clone, Copy)] @@ -120,6 +122,22 @@ impl<'data> DataDirectories<'data> { let reloc_data = data_dir.data(data, sections)?; Ok(Some(RelocationBlockIterator::new(reloc_data))) } + + /// Returns the root resource directory table. + /// + /// `data` must be the entire file data. + pub fn resource_directory_table>( + &self, + data: R, + sections: &SectionTable<'data>, + ) -> Result>> { + let data_dir = match self.get(pe::IMAGE_DIRECTORY_ENTRY_RESOURCE) { + Some(data_dir) => data_dir, + None => return Ok(None), + }; + let rsc_data = data_dir.data(data, sections)?; + ResourceDirectoryTable::parse(rsc_data).map(Some) + } } impl pe::ImageDataDirectory { diff --git a/src/read/pe/mod.rs b/src/read/pe/mod.rs index 36718492..2b7cc5d7 100644 --- a/src/read/pe/mod.rs +++ b/src/read/pe/mod.rs @@ -25,6 +25,9 @@ pub use import::*; mod relocation; pub use relocation::*; +mod resource; +pub use resource::*; + mod rich; pub use rich::*; diff --git a/src/read/pe/resource.rs b/src/read/pe/resource.rs new file mode 100644 index 00000000..997ee7d2 --- /dev/null +++ b/src/read/pe/resource.rs @@ -0,0 +1,289 @@ +use crate::endian::{LittleEndian as LE, U16}; +use crate::pe::{ImageResourceDataEntry, ImageResourceDirectory, ImageResourceDirectoryEntry}; +use crate::read::{ReadError, ReadRef, Result}; + +/// A resource directory +#[derive(Clone, Copy)] +pub struct ResourceDirectoryTable<'data> { + directory_data: &'data [u8], + /// the resource directory table + pub table: &'data ImageResourceDirectory, + /// the resource directory entries + pub entries: &'data [ImageResourceDirectoryEntry], +} + +impl<'data> core::fmt::Debug for ResourceDirectoryTable<'data> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Do not print self.directory_data + f.debug_struct("ResourceDirectoryTable") + .field("table", &self.table) + .field("entries", &self.entries) + .finish() + } +} + +impl<'data> ResourceDirectoryTable<'data> { + /// Parses the root resource directory. + /// + /// `data` must be the entire resource section + pub fn parse(data: &'data [u8]) -> Result { + Self::read(data, 0) + } + + fn read(data: &'data [u8], mut offset: u64) -> Result { + let table = data + .read::(&mut offset) + .read_error("Invalid resource directory table")?; + let entries_count = table.number_of_id_entries.get(LE) as usize + + table.number_of_named_entries.get(LE) as usize; + let entries = data + .read_slice_at::(offset, entries_count) + .read_error("Invalid resource directory entries")?; + Ok(Self { + directory_data: data, + table, + entries, + }) + } + + /// Returns an iterator over the directory entries + pub fn iter(&self) -> ResourceDirectoryIter<'data> { + ResourceDirectoryIter { + directory_data: self.directory_data, + inner: self.entries.iter(), + } + } +} + +/// An iterator over a resource directory entries +#[allow(missing_debug_implementations)] +pub struct ResourceDirectoryIter<'data> { + directory_data: &'data [u8], + inner: core::slice::Iter<'data, ImageResourceDirectoryEntry>, +} + +impl<'data> Iterator for ResourceDirectoryIter<'data> { + type Item = ResourceDirectoryEntry<'data>; + fn next(&mut self) -> Option { + self.inner.next().map(|entry| ResourceDirectoryEntry { + directory_data: self.directory_data, + entry, + }) + } +} + +/// A resource directory entry +#[derive(Clone, Copy)] +pub struct ResourceDirectoryEntry<'data> { + directory_data: &'data [u8], + entry: &'data ImageResourceDirectoryEntry, +} + +impl<'data> core::fmt::Debug for ResourceDirectoryEntry<'data> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Do not print self.directory_data + f.debug_struct("ResourceDirectoryEntry") + .field("entry", &self.entry) + .finish() + } +} + +impl<'data> ResourceDirectoryEntry<'data> { + /// Returns true if the entry is a sub-directory + pub fn is_directory(&self) -> bool { + (self.entry.offset_to_data_or_directory.get(LE) + & crate::pe::IMAGE_RESOURCE_DATA_IS_DIRECTORY) + > 0 + } + + /// Returns the offset to the associated directory or data struct + pub fn data_offset(&self) -> u32 { + self.entry.offset_to_data_or_directory.get(LE) & 0x7FFF_FFFF + } + + /// Returns true if the name is an custom string + pub fn has_string_name(&self) -> bool { + (self.entry.name_or_id.get(LE) & crate::pe::IMAGE_RESOURCE_NAME_IS_STRING) > 0 + } + + /// Returns the name string offset + /// + /// Valid if `has_string_name()` returns true + fn name_offset(&self) -> u32 { + self.entry.name_or_id.get(LE) & 0x7FFF_FFFF + } + + /// Returns the name id + /// + /// Valid if `has_string_name()` returns false + fn name_id(&self) -> u16 { + (self.entry.name_or_id.get(LE) & 0x0000_FFFF) as u16 + } + + /// Returns the entry name + pub fn name(&self) -> ResourceNameOrId<'data> { + if self.has_string_name() { + ResourceNameOrId::Name(ResourceName { + directory_data: self.directory_data, + offset: self.name_offset(), + }) + } else { + ResourceNameOrId::Id(self.name_id()) + } + } + + /// Returns the entry language code + /// + /// This is only valid a the level 2 of a standard resource directory structure. + /// + /// In a standard resource directory structure: + /// - level 0: entry.name_or_id is the resource type + /// - level 1: entry.name_or_id is the resource name + /// - level 2: entry.name_or_id is the language code + pub fn language_code(&self) -> Option { + if !self.has_string_name() { + Some(self.name_id()) + } else { + None + } + } + + /// Returns the data associated to this directory entry + pub fn data(&self) -> Result> { + if self.is_directory() { + ResourceDirectoryTable::read(self.directory_data, self.data_offset() as _) + .map(|t| ResourceDirectoryEntryData::Directory(t)) + } else { + self.directory_data + .read_at::(self.data_offset() as _) + .read_error("Invalid resource entry") + .map(|d| ResourceDirectoryEntryData::Entry(d)) + } + } +} + +/// A resource directory entry data +#[derive(Debug, Clone, Copy)] +pub enum ResourceDirectoryEntryData<'data> { + /// A sub directory entry + Directory(ResourceDirectoryTable<'data>), + /// A resource entry + Entry(&'data ImageResourceDataEntry), +} + +impl<'data> ResourceDirectoryEntryData<'data> { + /// Converts to an option of directory + /// + /// Helper for iterator filtering + pub fn directory(self) -> Option> { + match self { + Self::Directory(dir) => Some(dir), + _ => None, + } + } + + /// Converts to an option of entry + /// + /// Helper for iterator filtering + pub fn resource(self) -> Option<&'data ImageResourceDataEntry> { + match self { + Self::Entry(rsc) => Some(rsc), + _ => None, + } + } +} + +/// A resource name +pub struct ResourceName<'data> { + directory_data: &'data [u8], + offset: u32, +} + +impl<'data> ResourceName<'data> { + /// Converts to a `String` + pub fn to_string_lossy(&self) -> Result { + let d = self.data()?; + Ok(alloc::string::String::from_utf16_lossy(d)) + } + + /// Returns the string unicode buffer + pub fn data(&self) -> Result<&'data [u16]> { + let len = self + .directory_data + .read_at::>(self.offset as _) + .read_error("Invalid name length")?; + let offset = self + .offset + .checked_add(2) + .read_error("Invalid name offset")?; + self.directory_data + .read_slice_at::(offset as _, len.get(LE) as _) + .read_error("Invalid name buffer") + } +} + +impl<'data> core::fmt::Debug for ResourceName<'data> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Do not print self.directory_data + f.debug_struct("ResourceName") + .field("offset", &self.offset) + .field("data", &self.data()) + .finish() + } +} + +/// A resource name +/// +/// Can be either a string or an id +#[derive(Debug)] +pub enum ResourceNameOrId<'data> { + /// A resource string name + Name(ResourceName<'data>), + /// A resource name id + Id(u16), +} + +// Resource type: https://docs.microsoft.com/en-us/windows/win32/menurc/resource-types + +/// ID for: Hardware-dependent cursor resource. +pub const RESOURCE_TYPE_ID_RT_CURSOR: u16 = 1; +/// ID for: Bitmap resource. +pub const RESOURCE_TYPE_ID_RT_BITMAP: u16 = 2; +/// ID for: Hardware-dependent icon resource. +pub const RESOURCE_TYPE_ID_RT_ICON: u16 = 3; +/// ID for: Menu resource. +pub const RESOURCE_TYPE_ID_RT_MENU: u16 = 4; +/// ID for: Dialog box. +pub const RESOURCE_TYPE_ID_RT_DIALOG: u16 = 5; +/// ID for: String-table entry. +pub const RESOURCE_TYPE_ID_RT_STRING: u16 = 6; +/// ID for: Font directory resource. +pub const RESOURCE_TYPE_ID_RT_FONTDIR: u16 = 7; +/// ID for: Font resource. +pub const RESOURCE_TYPE_ID_RT_FONT: u16 = 8; +/// ID for: Accelerator table. +pub const RESOURCE_TYPE_ID_RT_ACCELERATOR: u16 = 9; +/// ID for: Application-defined resource (raw data). +pub const RESOURCE_TYPE_ID_RT_RCDATA: u16 = 10; +/// ID for: Message-table entry. +pub const RESOURCE_TYPE_ID_RT_MESSAGETABLE: u16 = 11; +/// ID for: Hardware-independent cursor resource. +pub const RESOURCE_TYPE_ID_RT_GROUP_CURSOR: u16 = 12; +/// ID for: Hardware-independent icon resource. +pub const RESOURCE_TYPE_ID_RT_GROUP_ICON: u16 = 14; +/// ID for: Version resource. +pub const RESOURCE_TYPE_ID_RT_VERSION: u16 = 16; +/// ID for: Allows a resource editing tool to associate a string with an .rc file. +pub const RESOURCE_TYPE_ID_RT_DLGINCLUDE: u16 = 17; +/// ID for: Plug and Play resource. +pub const RESOURCE_TYPE_ID_RT_PLUGPLAY: u16 = 19; +/// ID for: VXD. +pub const RESOURCE_TYPE_ID_RT_VXD: u16 = 20; +/// ID for: Animated cursor. +pub const RESOURCE_TYPE_ID_RT_ANICURSOR: u16 = 21; +/// ID for: Animated icon. +pub const RESOURCE_TYPE_ID_RT_ANIICON: u16 = 22; +/// ID for: HTML resource. +pub const RESOURCE_TYPE_ID_RT_HTML: u16 = 23; +/// ID for: Side-by-Side Assembly Manifest. +pub const RESOURCE_TYPE_ID_RT_MANIFEST: u16 = 24;