diff --git a/src/read/mod.rs b/src/read/mod.rs index 75814eef..83dff5d5 100644 --- a/src/read/mod.rs +++ b/src/read/mod.rs @@ -460,7 +460,7 @@ impl<'data> Import<'data> { /// An exported symbol. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Export<'data> { - // TODO: and ordinal? + // TODO: and ordinal name: ByteString<'data>, address: u64, } diff --git a/src/read/pe/export.rs b/src/read/pe/export.rs new file mode 100644 index 00000000..fba1f765 --- /dev/null +++ b/src/read/pe/export.rs @@ -0,0 +1,152 @@ +use alloc::vec::Vec; +use core::convert::TryInto; +use core::fmt::Debug; + +use crate::read::ReadError; +use crate::read::Result; +use crate::ByteString; +use crate::{pe, Bytes, LittleEndian as LE, U16Bytes, U32Bytes}; + +/// Where an export is pointing to +#[derive(Clone)] +pub enum ExportTarget<'data> { + /// The export points at a RVA in the file + Local(u64), + /// The export is "forwarded" to another DLL + /// + /// for example, "MYDLL.expfunc" or "MYDLL.#27" + Forwarded(&'data [u8]), +} + +/// An export from a PE file +/// +/// There are multiple kinds of PE exports (with or without a name, and local or exported) +#[derive(Clone)] +pub struct Export<'data> { + /// The ordinal of the export + pub ordinal: u32, + /// The name of the export, if ever the PE file has named it + pub name: Option<&'data [u8]>, + /// The target of this export + pub target: ExportTarget<'data>, +} + +impl<'a> Debug for Export<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> { + f.debug_struct("Export") + .field("ordinal", &self.ordinal) + .field("name", &self.name.map(ByteString)) + .field("target", &self.target) + .finish() + } +} + +impl<'a> Debug for ExportTarget<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> { + match self { + ExportTarget::Local(addr) => f.write_fmt(format_args!("Local({:#x})", addr)), + ExportTarget::Forwarded(forward) => { + f.write_fmt(format_args!("Forwarded({:?})", ByteString(forward))) + } + } + } +} + +impl<'data, Pe, R> super::PeFile<'data, Pe, R> +where + Pe: super::ImageNtHeaders, + R: crate::ReadRef<'data>, +{ + /// Returns the exports of this PE file + /// + /// See also the [`PeFile::exports`] function, which only returns a subset of these exports. + 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(Vec::new()), + }; + let export_va = data_dir.virtual_address.get(LE); + let export_size = data_dir.size.get(LE); + let export_data = data_dir.data(self.data, &self.common.sections).map(Bytes)?; + let export_dir = export_data + .read_at::(0) + .read_error("Invalid PE export dir size")?; + let addresses = export_data + .read_slice_at::>( + export_dir + .address_of_functions + .get(LE) + .wrapping_sub(export_va) as usize, + export_dir.number_of_functions.get(LE) as usize, + ) + .read_error("Invalid PE export address table")?; + let number = export_dir.number_of_names.get(LE) as usize; + let names = export_data + .read_slice_at::>( + export_dir.address_of_names.get(LE).wrapping_sub(export_va) as usize, + number, + ) + .read_error("Invalid PE export name table")?; + let base_ordinal = export_dir.base.get(LE); + let ordinals = export_data + .read_slice_at::>( + export_dir + .address_of_name_ordinals + .get(LE) + .wrapping_sub(export_va) as usize, + number, + ) + .read_error("Invalid PE export ordinal table")?; + + // First, let's list all exports... + let mut exports = Vec::new(); + for (i, address) in addresses.iter().enumerate() { + // Convert from an array index to an ordinal + // The MSDN documentation is wrong here, see https://stackoverflow.com/a/40001778/721832 + let ordinal: u32 = match i.try_into() { + Err(_err) => continue, + Ok(index) => index, + }; + let ordinal = ordinal + base_ordinal; + let address = address.get(LE); + + // is it a regular or forwarded export? + if address < export_va || (address - export_va) >= export_size { + exports.push(Export { + ordinal: ordinal, + target: ExportTarget::Local( + self.common.image_base.wrapping_add(address as u64), + ), + name: None, // might be populated later + }); + } else { + let forwarded_to = export_data + .read_string_at(address.wrapping_sub(export_va) as usize) + .read_error("Invalid target for PE forwarded export")?; + exports.push(Export { + ordinal: ordinal, + target: ExportTarget::Forwarded(forwarded_to), + name: None, // might be populated later + }); + } + } + + // Now, check whether some (or all) of them have an associated name + for (name_ptr, ordinal_index) in names.iter().zip(ordinals.iter()) { + // Items in the ordinal array are biased. + // The MSDN documentation is wrong regarding this bias, see https://stackoverflow.com/a/40001778/721832 + let ordinal_index = ordinal_index.get(LE) as u32; + + let name = export_data + .read_string_at(name_ptr.get(LE).wrapping_sub(export_va) as usize) + .read_error("Invalid PE export name entry")?; + + exports + .get_mut(ordinal_index as usize) + .ok_or_else(|| crate::read::Error("Invalid PE export ordinal"))? + .name = Some(name); + } + + Ok(exports) + } +} diff --git a/src/read/pe/file.rs b/src/read/pe/file.rs index a154d656..8ad48652 100644 --- a/src/read/pe/file.rs +++ b/src/read/pe/file.rs @@ -9,9 +9,7 @@ use crate::read::{ self, Architecture, ComdatKind, Error, Export, FileFlags, Import, NoDynamicRelocationIterator, Object, ObjectComdat, ReadError, ReadRef, Result, SectionIndex, SymbolIndex, }; -use crate::{ - pe, ByteString, Bytes, CodeView, LittleEndian as LE, Pod, U16Bytes, U32Bytes, U32, U64, -}; +use crate::{pe, ByteString, Bytes, CodeView, LittleEndian as LE, Pod, U32, U64}; use super::{PeSection, PeSectionIterator, PeSegment, PeSegmentIterator, SectionTable}; @@ -297,60 +295,21 @@ where } fn exports(&self) -> Result>> { - let data_dir = match self.data_directory(pe::IMAGE_DIRECTORY_ENTRY_EXPORT) { - Some(data_dir) => data_dir, - None => return Ok(Vec::new()), - }; - let export_va = data_dir.virtual_address.get(LE); - let export_size = data_dir.size.get(LE); - let export_data = data_dir.data(self.data, &self.common.sections).map(Bytes)?; - let export_dir = export_data - .read_at::(0) - .read_error("Invalid PE export dir size")?; - let addresses = export_data - .read_slice_at::>( - export_dir - .address_of_functions - .get(LE) - .wrapping_sub(export_va) as usize, - export_dir.number_of_functions.get(LE) as usize, - ) - .read_error("Invalid PE export address table")?; - let number = export_dir.number_of_names.get(LE) as usize; - let names = export_data - .read_slice_at::>( - export_dir.address_of_names.get(LE).wrapping_sub(export_va) as usize, - number, - ) - .read_error("Invalid PE export name table")?; - let ordinals = export_data - .read_slice_at::>( - export_dir - .address_of_name_ordinals - .get(LE) - .wrapping_sub(export_va) as usize, - number, - ) - .read_error("Invalid PE export ordinal table")?; - - let mut exports = Vec::new(); - for (name, ordinal) in names.iter().zip(ordinals.iter()) { - let name = export_data - .read_string_at(name.get(LE).wrapping_sub(export_va) as usize) - .read_error("Invalid PE export name entry")?; - let address = addresses - .get(ordinal.get(LE) as usize) - .read_error("Invalid PE export ordinal entry")? - .get(LE); - // Check for export address (vs forwarder address). - if address < export_va || (address - export_va) >= export_size { - exports.push(Export { - name: ByteString(name), - address: self.common.image_base.wrapping_add(address.into()), - }) + self.export_table().map(|pe_exports| { + let mut exports = Vec::new(); + for pe_export in pe_exports { + match (pe_export.name, pe_export.target) { + (Some(name), super::export::ExportTarget::Local(address)) => { + exports.push(Export { + name: ByteString(name), + address, + }) + } + _ => continue, + } } - } - Ok(exports) + exports + }) } fn pdb_info(&self) -> Result> { diff --git a/src/read/pe/mod.rs b/src/read/pe/mod.rs index 2a0b6812..a023e659 100644 --- a/src/read/pe/mod.rs +++ b/src/read/pe/mod.rs @@ -13,4 +13,7 @@ pub use file::*; mod section; pub use section::*; +mod export; +pub use export::*; + pub use super::coff::{SectionTable, SymbolTable}; diff --git a/src/read/traits.rs b/src/read/traits.rs index 4cc497b9..051d9de8 100644 --- a/src/read/traits.rs +++ b/src/read/traits.rs @@ -166,7 +166,9 @@ pub trait Object<'data: 'file, 'file>: read::private::Sealed { /// Get the imported symbols. fn imports(&self) -> Result>>; - /// Get the exported symbols. + /// Get the exported symbols that expose both a name and an address. + /// + /// Some file formats may provide other kinds of symbols, that can be retrieved using the lower-level API fn exports(&self) -> Result>>; /// Return true if the file contains debug information sections, false if not.