diff --git a/examples/readobj.rs b/examples/readobj.rs index 710985da..550df0bb 100644 --- a/examples/readobj.rs +++ b/examples/readobj.rs @@ -4579,24 +4579,28 @@ mod pe { p.field_enum("Index", index, FLAGS_IMAGE_DIRECTORY_ENTRY); p.field_hex("VirtualAddress", dir.virtual_address.get(LE)); p.field_hex("Size", dir.size.get(LE)); - if let Some(dir_data) = sections - .as_ref() - .and_then(|sections| dir.data(data, sections).ok()) - { - match index { - IMAGE_DIRECTORY_ENTRY_EXPORT => print_export_dir(p, dir, dir_data), - // TODO - _ => {} - } - } }); } if let Some(ref sections) = sections { - print_sections(p, data, header.machine.get(LE), symbols.as_ref(), §ions); + print_sections(p, data, header.machine.get(LE), symbols.as_ref(), sections); } if let Some(ref symbols) = symbols { print_symbols(p, sections.as_ref(), &symbols); } + if let Some(ref sections) = sections { + for (index, dir) in data_directories.iter().enumerate() { + match index { + IMAGE_DIRECTORY_ENTRY_EXPORT => { + print_export_dir(p, data, §ions, dir); + } + IMAGE_DIRECTORY_ENTRY_IMPORT => { + print_import_dir::(p, data, §ions, dir); + } + // TODO + _ => {} + } + } + } } } } @@ -4667,70 +4671,127 @@ mod pe { }); } - fn print_export_dir(p: &mut Printer, dir: &ImageDataDirectory, dir_data: &[u8]) { - if let Ok((export_dir, _)) = object::from_bytes::(dir_data) { - p.group("ImageExportDirectory", |p| { - p.field_hex("Characteristics", export_dir.characteristics.get(LE)); - p.field_hex("TimeDateStamp", export_dir.time_date_stamp.get(LE)); - p.field("MajorVersion", export_dir.major_version.get(LE)); - p.field("MinorVersion", export_dir.minor_version.get(LE)); - p.field_hex("Name", export_dir.name.get(LE)); - p.field("Base", export_dir.base.get(LE)); - p.field("NumberOfFunctions", export_dir.number_of_functions.get(LE)); - p.field("NumberOfNames", export_dir.number_of_names.get(LE)); - p.field_hex( - "AddressOfFunctions", - export_dir.address_of_functions.get(LE), - ); - p.field_hex("AddressOfNames", export_dir.address_of_names.get(LE)); - p.field_hex( - "AddressOfNameOrdinals", - export_dir.address_of_name_ordinals.get(LE), - ); - if let Ok(export_table) = ExportTable::parse(dir_data, dir.virtual_address.get(LE)) - { - // TODO: the order of the name pointers might be interesting? - let mut names = vec![None; export_table.addresses().len()]; - for (name_pointer, ordinal) in export_table.name_iter() { - if let Some(name) = names.get_mut(ordinal as usize) { - *name = Some(name_pointer); - } + fn print_export_dir( + p: &mut Printer, + data: &[u8], + sections: &SectionTable, + dir: &ImageDataDirectory, + ) -> Option<()> { + let dir_data = dir.data(data, sections).ok()?; + let export_dir = object::from_bytes::(dir_data) + .ok()? + .0; + p.group("ImageExportDirectory", |p| { + p.field_hex("Characteristics", export_dir.characteristics.get(LE)); + p.field_hex("TimeDateStamp", export_dir.time_date_stamp.get(LE)); + p.field("MajorVersion", export_dir.major_version.get(LE)); + p.field("MinorVersion", export_dir.minor_version.get(LE)); + p.field_hex("Name", export_dir.name.get(LE)); + p.field("Base", export_dir.base.get(LE)); + p.field("NumberOfFunctions", export_dir.number_of_functions.get(LE)); + p.field("NumberOfNames", export_dir.number_of_names.get(LE)); + p.field_hex( + "AddressOfFunctions", + export_dir.address_of_functions.get(LE), + ); + p.field_hex("AddressOfNames", export_dir.address_of_names.get(LE)); + p.field_hex( + "AddressOfNameOrdinals", + export_dir.address_of_name_ordinals.get(LE), + ); + if let Ok(export_table) = ExportTable::parse(dir_data, dir.virtual_address.get(LE)) { + // TODO: the order of the name pointers might be interesting? + let mut names = vec![None; export_table.addresses().len()]; + for (name_pointer, ordinal) in export_table.name_iter() { + if let Some(name) = names.get_mut(ordinal as usize) { + *name = Some(name_pointer); } + } - let ordinal_base = export_table.ordinal_base(); - for (ordinal, address) in export_table.addresses().iter().enumerate() { - p.group("Export", |p| { - p.field("Ordinal", ordinal_base.wrapping_add(ordinal as u32)); - if let Some(name_pointer) = names[ordinal] { - p.field_string( - "Name", - name_pointer, - export_table.name_from_pointer(name_pointer).ok(), - ); - } - p.field_hex("Address", address.get(LE)); - if let Ok(target) = export_table.target_from_address(address.get(LE)) { - match target { - ExportTarget::Address(_) => {} - ExportTarget::ForwardByOrdinal(library, ordinal) => { - p.field_inline_string("ForwardLibrary", library); - p.field("ForwardOrdinal", ordinal); - } - ExportTarget::ForwardByName(library, name) => { - p.field_inline_string("ForwardLibrary", library); - p.field_inline_string("ForwardName", name); - } + let ordinal_base = export_table.ordinal_base(); + for (ordinal, address) in export_table.addresses().iter().enumerate() { + p.group("Export", |p| { + p.field("Ordinal", ordinal_base.wrapping_add(ordinal as u32)); + if let Some(name_pointer) = names[ordinal] { + p.field_string( + "Name", + name_pointer, + export_table.name_from_pointer(name_pointer).ok(), + ); + } + p.field_hex("Address", address.get(LE)); + if let Ok(target) = export_table.target_from_address(address.get(LE)) { + match target { + ExportTarget::Address(_) => {} + ExportTarget::ForwardByOrdinal(library, ordinal) => { + p.field_inline_string("ForwardLibrary", library); + p.field("ForwardOrdinal", ordinal); + } + ExportTarget::ForwardByName(library, name) => { + p.field_inline_string("ForwardLibrary", library); + p.field_inline_string("ForwardName", name); } - } else if let Ok(Some(forward)) = - export_table.forward_string(address.get(LE)) - { - p.field_inline_string("Forward", forward); } - }); - } + } else if let Ok(Some(forward)) = + export_table.forward_string(address.get(LE)) + { + p.field_inline_string("Forward", forward); + } + }); } - }); - } + } + }); + Some(()) + } + + fn print_import_dir( + p: &mut Printer, + data: &[u8], + sections: &SectionTable, + dir: &ImageDataDirectory, + ) -> Option<()> { + let import_address = dir.virtual_address.get(LE); + let (section_data, section_address) = sections.pe_data_containing(data, import_address)?; + let import_table = ImportTable::new(section_data, section_address, import_address); + let mut import_descs = import_table.descriptors().ok()?; + p.group("ImageImportDirectory", |p| { + while let Ok(Some(import_desc)) = import_descs.next() { + p.group("ImageImportDescriptor", |p| { + p.field_hex("LookupTable", import_desc.original_first_thunk.get(LE)); + p.field_hex("TimeDataStamp", import_desc.time_date_stamp.get(LE)); + p.field_hex("ForwarderChain", import_desc.forwarder_chain.get(LE)); + let name = import_desc.name.get(LE); + p.field_string("Name", name, import_table.name(name).ok()); + p.field_hex("AddressTable", import_desc.first_thunk.get(LE)); + let mut address_thunks = + import_table.thunks(import_desc.first_thunk.get(LE)).ok(); + if let Ok(mut lookup_thunks) = + import_table.thunks(import_desc.original_first_thunk.get(LE)) + { + while let Ok(Some(thunk)) = lookup_thunks.next::() { + p.group("Thunk", |p| { + p.field_hex("Lookup", thunk.raw()); + if let Some(Some(thunk)) = address_thunks + .as_mut() + .and_then(|thunks| thunks.next::().ok()) + { + p.field_hex("Address", thunk.raw()); + } + if thunk.is_ordinal() { + p.field("Ordinal", thunk.ordinal()); + } else if let Ok((hint, name)) = + import_table.hint_name(thunk.address()) + { + p.field("Hint", hint); + p.field_inline_string("Name", name); + } + }); + } + } + }); + } + }); + Some(()) } fn print_sections( diff --git a/src/elf.rs b/src/elf.rs index 8b324431..a1a00d4a 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -2576,7 +2576,7 @@ pub const OHW_R4KEOP: u32 = 0x1; pub const OHW_R8KPFETCH: u32 = 0x2; /// R5000 end-of-page patch. pub const OHW_R5KEOP: u32 = 0x4; -/// R5000 cvt.[ds].l bug. clean=1. +/// R5000 cvt.\[ds\].l bug. clean=1. pub const OHW_R5KCVTL: u32 = 0x8; #[allow(missing_docs)] diff --git a/src/pe.rs b/src/pe.rs index 46daf533..f42f83b4 100644 --- a/src/pe.rs +++ b/src/pe.rs @@ -1813,12 +1813,10 @@ pub struct ImageImportByName { //pub name: [i8; 1], } -/* -// TODO? unions - #[derive(Debug, Clone, Copy)] #[repr(C)] -pub struct ImageThunkData64 { +pub struct ImageThunkData64(pub U64); +/* union { /// PBYTE pub forwarder_string: U64, @@ -1828,11 +1826,12 @@ pub struct ImageThunkData64 { /// PIMAGE_IMPORT_BY_NAME pub address_of_data: U64, } u1; -} +*/ #[derive(Debug, Clone, Copy)] #[repr(C)] -pub struct ImageThunkData32 { +pub struct ImageThunkData32(pub U32); +/* union { /// PBYTE pub forwarder_string: U32, @@ -2905,8 +2904,8 @@ unsafe_impl_pod!( ImageArchiveMemberHeader, ImageExportDirectory, ImageImportByName, - //ImageThunkData64, - //ImageThunkData32, + ImageThunkData64, + ImageThunkData32, ImageTlsDirectory64, ImageTlsDirectory32, ImageImportDescriptor, diff --git a/src/read/mod.rs b/src/read/mod.rs index b47780b4..41d34411 100644 --- a/src/read/mod.rs +++ b/src/read/mod.rs @@ -454,9 +454,9 @@ impl<'data> SymbolMapEntry for ObjectMapEntry<'data> { /// An imported symbol. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Import<'data> { + library: ByteString<'data>, // TODO: or ordinal name: ByteString<'data>, - library: ByteString<'data>, } impl<'data> Import<'data> { diff --git a/src/read/pe/data_directory.rs b/src/read/pe/data_directory.rs new file mode 100644 index 00000000..a236eb44 --- /dev/null +++ b/src/read/pe/data_directory.rs @@ -0,0 +1,102 @@ +use core::slice; + +use crate::read::{ReadError, ReadRef, Result}; +use crate::{pe, LittleEndian as LE}; + +use super::{ExportTable, ImportTable, SectionTable}; + +/// The table of data directories in a PE file. +#[derive(Debug, Clone, Copy)] +pub struct DataDirectories<'data> { + entries: &'data [pe::ImageDataDirectory], +} + +impl<'data> DataDirectories<'data> { + /// Parse the data directory table. + /// + /// `data` must be the remaining optional data following the + /// [optional header](pe::ImageOptionalHeader64). `number` must be from the + /// [`number_of_rva_and_sizes`](pe::ImageOptionalHeader64::number_of_rva_and_sizes) + /// field of the optional header. + pub fn parse(data: &'data [u8], number: u32) -> Result { + let entries = data + .read_slice_at(0, number as usize) + .read_error("Invalid PE number of RVA and sizes")?; + Ok(DataDirectories { entries }) + } + + /// Iterator over the data directories. + pub fn iter(&self) -> slice::Iter<'data, pe::ImageDataDirectory> { + self.entries.iter() + } + + /// Returns the data directory at the given index. + /// + /// Index should be one of the `IMAGE_DIRECTORY_ENTRY_*` constants. + /// + /// Returns `None` if the index is larger than the table size, + /// or if the entry at the index has a zero virtual address. + pub fn get(&self, index: usize) -> Option<&'data pe::ImageDataDirectory> { + self.entries + .get(index) + .filter(|d| d.virtual_address.get(LE) != 0) + } + + /// Returns the partially parsed export directory. + /// + /// `data` must be the entire file data. + pub fn export_table>( + &self, + data: R, + sections: &SectionTable<'data>, + ) -> Result>> { + let data_dir = match self.get(pe::IMAGE_DIRECTORY_ENTRY_EXPORT) { + Some(data_dir) => data_dir, + None => return Ok(None), + }; + let export_va = data_dir.virtual_address.get(LE); + let export_data = data_dir.data(data, sections)?; + ExportTable::parse(export_data, export_va).map(Some) + } + + /// Returns the partially parsed import directory. + /// + /// `data` must be the entire file data. + pub fn import_table>( + &self, + data: R, + sections: &SectionTable<'data>, + ) -> Result>> { + let data_dir = match self.get(pe::IMAGE_DIRECTORY_ENTRY_IMPORT) { + Some(data_dir) => data_dir, + None => return Ok(None), + }; + let import_va = data_dir.virtual_address.get(LE); + let (section_data, section_va) = sections + .pe_data_containing(data, import_va) + .read_error("Invalid import data dir virtual address")?; + Ok(Some(ImportTable::new(section_data, section_va, import_va))) + } +} + +impl pe::ImageDataDirectory { + /// Get the data referenced by this directory entry. + /// + /// This function has some limitations: + /// - It requires that the data is contained in a single section. + /// - It uses the size field of the directory entry, which is + /// not desirable for all data directories. + /// - It uses the `virtual_address` of the directory entry as an address, + /// which is not valid for `IMAGE_DIRECTORY_ENTRY_SECURITY`. + pub fn data<'data, R: ReadRef<'data>>( + &self, + data: R, + sections: &SectionTable<'data>, + ) -> Result<&'data [u8]> { + sections + .pe_data_at(data, self.virtual_address.get(LE)) + .read_error("Invalid data dir virtual address")? + .get(..self.size.get(LE) as usize) + .read_error("Invalid data dir size") + } +} diff --git a/src/read/pe/file.rs b/src/read/pe/file.rs index eeae1d5f..15f7d819 100644 --- a/src/read/pe/file.rs +++ b/src/read/pe/file.rs @@ -9,10 +9,11 @@ use crate::read::{ self, Architecture, ComdatKind, Error, Export, FileFlags, Import, NoDynamicRelocationIterator, Object, ObjectComdat, ObjectKind, ReadError, ReadRef, Result, SectionIndex, SymbolIndex, }; -use crate::{pe, ByteString, Bytes, CodeView, LittleEndian as LE, Pod, U32, U64}; +use crate::{pe, ByteString, Bytes, CodeView, LittleEndian as LE, Pod, U32}; use super::{ - ExportTable, PeSection, PeSectionIterator, PeSegment, PeSegmentIterator, SectionTable, + DataDirectories, ExportTable, ImageThunkData, ImportTable, PeSection, PeSectionIterator, + PeSegment, PeSegmentIterator, SectionTable, }; /// A PE32 (32-bit) image file. @@ -29,7 +30,7 @@ where { pub(super) dos_header: &'data pe::ImageDosHeader, pub(super) nt_headers: &'data Pe, - pub(super) data_directories: &'data [pe::ImageDataDirectory], + pub(super) data_directories: DataDirectories<'data>, pub(super) common: CoffCommon<'data, R>, pub(super) data: R, } @@ -81,32 +82,34 @@ where self.common.sections } + /// Returns the data directories of this file. + pub fn data_directories(&self) -> DataDirectories<'data> { + self.data_directories + } + /// Returns the data directory at the given index. pub fn data_directory(&self, id: usize) -> Option<&'data pe::ImageDataDirectory> { - self.data_directories - .get(id) - .filter(|d| d.virtual_address.get(LE) != 0) + self.data_directories.get(id) } /// Returns the export table of this file. /// /// The export table is located using the data directory. pub fn export_table(&self) -> Result>> { - let data_dir = match self.data_directory(pe::IMAGE_DIRECTORY_ENTRY_EXPORT) { - Some(data_dir) => data_dir, - None => return Ok(None), - }; - let export_data = data_dir.data(self.data, &self.common.sections)?; - let export_va = data_dir.virtual_address.get(LE); - ExportTable::parse(export_data, export_va).map(Some) + self.data_directories + .export_table(self.data, &self.common.sections) } - pub(super) fn section_alignment(&self) -> u64 { - u64::from(self.nt_headers.optional_header().section_alignment()) + /// Returns the import table of this file. + /// + /// The import table is located using the data directory. + pub fn import_table(&self) -> Result>> { + self.data_directories + .import_table(self.data, &self.common.sections) } - fn data_at(&self, va: u32) -> Option> { - self.common.sections.pe_data_at(self.data, va).map(Bytes) + pub(super) fn section_alignment(&self) -> u64 { + u64::from(self.nt_headers.optional_header().section_alignment()) } } @@ -247,77 +250,21 @@ where } fn imports(&self) -> Result>> { - let data_dir = match self.data_directory(pe::IMAGE_DIRECTORY_ENTRY_IMPORT) { - Some(data_dir) => data_dir, - None => return Ok(Vec::new()), - }; - - // The size declared in the IMAGE_DIRECTORY_ENTRY_IMPORT is ignored by the Windows loader - // Hence, we'll parse the list until a null entry, without restricting the read to this declared size - // (i.e. we're not using `data_dir.data()`) - let mut import_descriptors = self - .common - .sections - .pe_data_at(self.data, data_dir.virtual_address.get(LE)) - .map(Bytes) - .ok_or(read::Error("Unable to read PE import descriptors"))?; let mut imports = Vec::new(); - loop { - let import_desc = import_descriptors - .read::() - .read_error("Missing PE null import descriptor")?; - if import_desc.original_first_thunk.get(LE) == 0 { - break; - } - - let library = self - .data_at(import_desc.name.get(LE)) - .read_error("Invalid PE import descriptor name")? - .read_string() - .read_error("Invalid PE import descriptor name")?; - - let thunk_va = import_desc.original_first_thunk.get(LE); - let mut thunk_data = self - .data_at(thunk_va) - .read_error("Invalid PE import thunk address")?; - loop { - let hint_name = if self.is_64() { - let thunk = thunk_data - .read::>() - .read_error("Missing PE null import thunk")? - .get(LE); - if thunk == 0 { - break; - } - if thunk & pe::IMAGE_ORDINAL_FLAG64 != 0 { - // TODO: handle import by ordinal - continue; - } - thunk as u32 - } else { - let thunk = thunk_data - .read::>() - .read_error("Missing PE null import thunk")? - .get(LE); - if thunk == 0 { - break; + if let Some(import_table) = self.import_table()? { + let mut import_descs = import_table.descriptors()?; + while let Some(import_desc) = import_descs.next()? { + let library = import_table.name(import_desc.name.get(LE))?; + let mut thunks = import_table.thunks(import_desc.original_first_thunk.get(LE))?; + while let Some(thunk) = thunks.next::()? { + if !thunk.is_ordinal() { + let (_hint, name) = import_table.hint_name(thunk.address())?; + imports.push(Import { + library: ByteString(library), + name: ByteString(name), + }); } - if thunk & pe::IMAGE_ORDINAL_FLAG32 != 0 { - // TODO: handle import by ordinal - continue; - } - thunk - }; - let name = self - .data_at(hint_name) - .read_error("Invalid PE import thunk name")? - .read_string_at(2) - .read_error("Invalid PE import thunk name")?; - - imports.push(Import { - name: ByteString(name), - library: ByteString(library), - }); + } } } Ok(imports) @@ -572,6 +519,7 @@ pub fn optional_header_magic<'data, R: ReadRef<'data>>(data: R) -> Result { #[allow(missing_docs)] pub trait ImageNtHeaders: Debug + Pod { type ImageOptionalHeader: ImageOptionalHeader; + type ImageThunkData: ImageThunkData; /// Return true if this type is a 64-bit header. /// @@ -603,7 +551,7 @@ pub trait ImageNtHeaders: Debug + Pod { fn parse<'data, R: ReadRef<'data>>( data: R, offset: &mut u64, - ) -> read::Result<(&'data Self, &'data [pe::ImageDataDirectory])> { + ) -> read::Result<(&'data Self, DataDirectories<'data>)> { // Note that this does not include the data directories in the optional header. let nt_headers = data .read::(offset) @@ -620,13 +568,13 @@ pub trait ImageNtHeaders: Debug + Pod { u64::from(nt_headers.file_header().size_of_optional_header.get(LE)) .checked_sub(mem::size_of::() as u64) .read_error("PE optional header size is too small")?; - let mut optional_data = data + let optional_data = data .read_bytes(offset, optional_data_size) - .read_error("Invalid PE optional header size") - .map(Bytes)?; - let data_directories = optional_data - .read_slice(nt_headers.optional_header().number_of_rva_and_sizes() as usize) - .read_error("Invalid PE number of RVA and sizes")?; + .read_error("Invalid PE optional header size")?; + let data_directories = DataDirectories::parse( + optional_data, + nt_headers.optional_header().number_of_rva_and_sizes(), + )?; Ok((nt_headers, data_directories)) } @@ -692,6 +640,7 @@ pub trait ImageOptionalHeader: Debug + Pod { impl ImageNtHeaders for pe::ImageNtHeaders32 { type ImageOptionalHeader = pe::ImageOptionalHeader32; + type ImageThunkData = pe::ImageThunkData32; #[inline] fn is_type_64(&self) -> bool { @@ -868,6 +817,7 @@ impl ImageOptionalHeader for pe::ImageOptionalHeader32 { impl ImageNtHeaders for pe::ImageNtHeaders64 { type ImageOptionalHeader = pe::ImageOptionalHeader64; + type ImageThunkData = pe::ImageThunkData64; #[inline] fn is_type_64(&self) -> bool { @@ -1041,18 +991,3 @@ impl ImageOptionalHeader for pe::ImageOptionalHeader64 { self.number_of_rva_and_sizes.get(LE) } } - -impl pe::ImageDataDirectory { - /// Get the data referenced by this directory entry. - pub fn data<'data, R: ReadRef<'data>>( - &self, - data: R, - sections: &SectionTable<'data>, - ) -> Result<&'data [u8]> { - sections - .pe_data_at(data, self.virtual_address.get(LE)) - .read_error("Invalid data dir virtual address or size")? - .get(..self.size.get(LE) as usize) - .read_error("Invalid data dir size") - } -} diff --git a/src/read/pe/import.rs b/src/read/pe/import.rs new file mode 100644 index 00000000..af8bc9c0 --- /dev/null +++ b/src/read/pe/import.rs @@ -0,0 +1,218 @@ +use core::fmt::Debug; +use core::mem; + +use crate::read::{Bytes, ReadError, Result}; +use crate::{pe, LittleEndian as LE, Pod, U16Bytes}; + +use super::ImageNtHeaders; + +/// Information for parsing a PE import table. +#[derive(Debug, Clone)] +pub struct ImportTable<'data> { + section_data: Bytes<'data>, + section_address: u32, + import_address: u32, +} + +impl<'data> ImportTable<'data> { + /// Create a new import table parser. + /// + /// The import descriptors start at `import_address`. + /// The size declared in the `IMAGE_DIRECTORY_ENTRY_IMPORT` data directory is + /// ignored by the Windows loader, and so descriptors will be parsed until a null entry. + /// + /// `section_data` should be from the section containing `import_address`, and + /// `section_address` should be the address of that section. Pointers within the + /// descriptors and thunks may point to anywhere within the section data. + pub fn new(section_data: &'data [u8], section_address: u32, import_address: u32) -> Self { + ImportTable { + section_data: Bytes(section_data), + section_address, + import_address, + } + } + + /// Return an iterator for the import descriptors. + pub fn descriptors(&self) -> Result> { + let offset = self.import_address.wrapping_sub(self.section_address); + let mut data = self.section_data; + data.skip(offset as usize) + .read_error("Invalid PE import descriptor address")?; + Ok(ImportDescriptorIterator { data }) + } + + /// Return a library name given its address. + /// + /// This address may be from [`pe::ImageImportDescriptor::name`]. + pub fn name(&self, address: u32) -> Result<&'data [u8]> { + self.section_data + .read_string_at(address.wrapping_sub(self.section_address) as usize) + .read_error("Invalid PE import descriptor name") + } + + /// Return a list of thunks given its address. + /// + /// This address may be from [`pe::ImageImportDescriptor::original_first_thunk`] + /// or [`pe::ImageImportDescriptor::first_thunk`]. + pub fn thunks(&self, address: u32) -> Result> { + let offset = address.wrapping_sub(self.section_address); + let mut data = self.section_data; + data.skip(offset as usize) + .read_error("Invalid PE import thunk table address")?; + Ok(ImportThunkList { data }) + } + + /// Parse a thunk. + pub fn import(&self, thunk: Pe::ImageThunkData) -> Result> { + if thunk.is_ordinal() { + Ok(Import::Ordinal(thunk.ordinal())) + } else { + let (hint, name) = self.hint_name(thunk.address())?; + Ok(Import::Name(hint, name)) + } + } + + /// Return the hint and name at the given address. + /// + /// This address may be from [`pe::ImageThunkData32`] or [`pe::ImageThunkData64`]. + /// + /// The hint is an index into the export name pointer table in the target library. + pub fn hint_name(&self, address: u32) -> Result<(u16, &'data [u8])> { + let offset = address.wrapping_sub(self.section_address); + let mut data = self.section_data; + data.skip(offset as usize) + .read_error("Invalid PE import thunk address")?; + let hint = data + .read::>() + .read_error("Missing PE import thunk hint")? + .get(LE); + let name = data + .read_string() + .read_error("Missing PE import thunk name")?; + Ok((hint, name)) + } +} + +/// A fallible iterator for the descriptors in the import data directory. +#[derive(Debug, Clone)] +pub struct ImportDescriptorIterator<'data> { + data: Bytes<'data>, +} + +impl<'data> ImportDescriptorIterator<'data> { + /// Return the next descriptor. + /// + /// Returns `Ok(None)` when a null descriptor is found. + pub fn next(&mut self) -> Result> { + let import_desc = self + .data + .read::() + .read_error("Missing PE null import descriptor")?; + if import_desc.original_first_thunk.get(LE) == 0 { + Ok(None) + } else { + Ok(Some(import_desc)) + } + } +} + +/// A list of import thunks. +/// +/// These may be in the import lookup table, or the import address table. +#[derive(Debug, Clone)] +pub struct ImportThunkList<'data> { + data: Bytes<'data>, +} + +impl<'data> ImportThunkList<'data> { + /// Get the thunk at the given index. + pub fn get(&self, index: usize) -> Result { + let thunk = self + .data + .read_at(index * mem::size_of::()) + .read_error("Invalid PE import thunk index")?; + Ok(*thunk) + } + + /// Return the first thunk in the list, and update `self` to point after it. + /// + /// Returns `Ok(None)` when a null thunk is found. + pub fn next(&mut self) -> Result> { + let thunk = self + .data + .read::() + .read_error("Missing PE null import thunk")?; + if thunk.address() == 0 { + Ok(None) + } else { + Ok(Some(*thunk)) + } + } +} + +/// A parsed import thunk. +#[derive(Debug, Clone, Copy)] +pub enum Import<'data> { + /// Import by ordinal. + Ordinal(u16), + /// Import by name. + /// + /// Includes a hint for the index into the export name pointer table in the target library. + Name(u16, &'data [u8]), +} + +/// A trait for generic access to [`pe::ImageThunkData32`] and [`pe::ImageThunkData64`]. +#[allow(missing_docs)] +pub trait ImageThunkData: Debug + Pod { + /// Return the raw thunk value. + fn raw(self) -> u64; + + /// Returns true if the ordinal flag is set. + fn is_ordinal(self) -> bool; + + /// Return the ordinal portion of the thunk. + /// + /// Does not check the ordinal flag. + fn ordinal(self) -> u16; + + /// Return the RVA portion of the thunk. + /// + /// Does not check the ordinal flag. + fn address(self) -> u32; +} + +impl ImageThunkData for pe::ImageThunkData64 { + fn raw(self) -> u64 { + self.0.get(LE) + } + + fn is_ordinal(self) -> bool { + self.0.get(LE) & pe::IMAGE_ORDINAL_FLAG64 != 0 + } + + fn ordinal(self) -> u16 { + self.0.get(LE) as u16 + } + + fn address(self) -> u32 { + self.0.get(LE) as u32 & 0x7fff_ffff + } +} + +impl ImageThunkData for pe::ImageThunkData32 { + fn raw(self) -> u64 { + self.0.get(LE).into() + } + + fn is_ordinal(self) -> bool { + self.0.get(LE) & pe::IMAGE_ORDINAL_FLAG32 != 0 + } + + fn ordinal(self) -> u16 { + self.0.get(LE) as u16 + } + + fn address(self) -> u32 { + self.0.get(LE) & 0x7fff_ffff + } +} diff --git a/src/read/pe/mod.rs b/src/read/pe/mod.rs index a023e659..6873665c 100644 --- a/src/read/pe/mod.rs +++ b/src/read/pe/mod.rs @@ -13,7 +13,13 @@ pub use file::*; mod section; pub use section::*; +mod data_directory; +pub use data_directory::*; + mod export; pub use export::*; +mod import; +pub use import::*; + pub use super::coff::{SectionTable, SymbolTable}; diff --git a/src/read/pe/section.rs b/src/read/pe/section.rs index edcf85df..c3d9b5a6 100644 --- a/src/read/pe/section.rs +++ b/src/read/pe/section.rs @@ -312,10 +312,24 @@ where impl<'data> SectionTable<'data> { /// Return the data at the given virtual address in a PE file. + /// + /// Ignores sections with invalid data. pub fn pe_data_at>(&self, data: R, va: u32) -> Option<&'data [u8]> { + self.iter().find_map(|section| section.pe_data_at(data, va)) + } + + /// Return the section data at the given virtual address in a PE file. + /// + /// Also returns the virtual address of the data. + /// + /// Ignores sections with invalid data. + pub fn pe_data_containing>( + &self, + data: R, + va: u32, + ) -> Option<(&'data [u8], u32)> { self.iter() - .filter_map(|section| section.pe_data_at(data, va)) - .next() + .find_map(|section| section.pe_data_containing(data, va)) } } @@ -337,14 +351,43 @@ impl pe::ImageSectionHeader { } /// Return the data at the given virtual address if this section contains it. + /// + /// Ignores sections with invalid data. pub fn pe_data_at<'data, R: ReadRef<'data>>(&self, data: R, va: u32) -> Option<&'data [u8]> { let section_va = self.virtual_address.get(LE); let offset = va.checked_sub(section_va)?; - let section_data = self.pe_data(data).ok()?; - if (offset as usize) < section_data.len() { + let (section_offset, section_size) = self.pe_file_range(); + // Address must be within section (and not at its end). + if offset < section_size { + let section_data = data + .read_bytes_at(section_offset.into(), section_size.into()) + .ok()?; section_data.get(offset as usize..) } else { - // We're calling `.get(i..)` with a range. In case i == section_data.len(), this will return Some([]), not None + None + } + } + + /// Return the section data if it contains the given virtual address. + /// + /// Also returns the virtual address of the data. + /// + /// Ignores sections with invalid data. + pub fn pe_data_containing<'data, R: ReadRef<'data>>( + &self, + data: R, + va: u32, + ) -> Option<(&'data [u8], u32)> { + let section_va = self.virtual_address.get(LE); + let offset = va.checked_sub(section_va)?; + let (section_offset, section_size) = self.pe_file_range(); + // Address must be within section (and not at its end). + if offset < section_size { + let section_data = data + .read_bytes_at(section_offset.into(), section_size.into()) + .ok()?; + Some((section_data, section_va)) + } else { None } }