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 exports by ordinal #349

Merged
merged 15 commits into from
Aug 13, 2021
1 change: 0 additions & 1 deletion src/read/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,6 @@ impl<'data> Import<'data> {
/// An exported symbol.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Export<'data> {
// TODO: and ordinal?
daladim marked this conversation as resolved.
Show resolved Hide resolved
name: ByteString<'data>,
address: u64,
}
Expand Down
182 changes: 128 additions & 54 deletions 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, U16Bytes, U32Bytes, U32, U64,
};

use super::{PeSection, PeSectionIterator, PeSegment, PeSegmentIterator, SectionTable};
use super::{PeExport, PeSection, PeSectionIterator, PeSegment, PeSegmentIterator, SectionTable};

/// A PE32 (32-bit) image file.
pub type PeFile32<'data, R = &'data [u8]> = PeFile<'data, pe::ImageNtHeaders32, R>;
Expand Down Expand Up @@ -95,6 +95,120 @@ where
pub fn data(&self) -> R {
self.data
}

/// Returns the exports of this PE file
///
/// See also the [`PeFile::exports`] function, which only returns the regular exports.
pub fn pe_exports(&self) -> Result<Vec<PeExport<'data>>> {
daladim marked this conversation as resolved.
Show resolved Hide resolved
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::<pe::ImageExportDirectory>(0)
.read_error("Invalid PE export dir size")?;
let addresses = export_data
.read_slice_at::<U32Bytes<_>>(
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::<U32Bytes<_>>(
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::<U16Bytes<_>>(
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(PeExport::ByOrdinal {
ordinal: ordinal,
address: self.common.image_base.wrapping_add(address as u64),
});
} 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(PeExport::ForwardedByOrdinal {
ordinal: ordinal,
forwarded_to: forwarded_to,
});
}
}

// 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")?;

let unnamed_equivalent = exports.get(ordinal_index as usize).cloned();
match unnamed_equivalent {
Some(PeExport::ByOrdinal { ordinal, address }) => {
let _ = core::mem::replace(
&mut exports[ordinal_index as usize],
PeExport::Regular {
name,
address,
ordinal,
},
);
}

Some(PeExport::ForwardedByOrdinal {
ordinal,
forwarded_to,
}) => {
let _ = core::mem::replace(
&mut exports[ordinal_index as usize],
PeExport::Forwarded {
name,
ordinal,
forwarded_to,
},
);
}

_ => continue, // unless ordinals are not unique in the ordinals array, this should not happen
}
}

Ok(exports)
}
}

impl<'data, Pe, R> read::private::Sealed for PeFile<'data, Pe, R>
Expand Down Expand Up @@ -296,61 +410,21 @@ where
Ok(imports)
}

/// Note that this only returns "regular" exports.
daladim marked this conversation as resolved.
Show resolved Hide resolved
/// [`PeFile::pe_exports`] may have more results
fn exports(&self) -> Result<Vec<Export<'data>>> {
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::<pe::ImageExportDirectory>(0)
.read_error("Invalid PE export dir size")?;
let addresses = export_data
.read_slice_at::<U32Bytes<_>>(
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::<U32Bytes<_>>(
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::<U16Bytes<_>>(
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.pe_exports().map(|pe_exports| {
let mut exports = Vec::new();
for pe_export in pe_exports {
if let PeExport::Regular { name, address, .. } = pe_export {
exports.push(Export {
name: ByteString(name),
address,
})
}
}
}
Ok(exports)
exports
})
}

fn pdb_info(&self) -> Result<Option<CodeView>> {
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 @@ -13,4 +13,7 @@ pub use file::*;
mod section;
pub use section::*;

mod pe_export;
pub use pe_export::*;

pub use super::coff::{SectionTable, SymbolTable};
117 changes: 117 additions & 0 deletions src/read/pe/pe_export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use core::fmt::Debug;

/// Possible exports from a PE file
#[derive(Clone)]
pub enum PeExport<'data> {
daladim marked this conversation as resolved.
Show resolved Hide resolved
/// A named exported symbol from this PE file
Regular {
/// The ordinal of this export
ordinal: u32,
/// The name of this export
name: &'data [u8],
/// The virtual address pointed to by this export
address: u64,
},

/// An export that only has an ordinal, but no name
ByOrdinal {
/// The ordinal of this export
ordinal: u32,
/// The virtual address pointed to by this export
address: u64,
},

/// A forwarded export (i.e. a symbol that is contained in some other DLL).
/// This concept is [PE-specific](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#export-address-table)
Forwarded {
/// The ordinal of this export
ordinal: u32,
/// The name of this export
name: &'data [u8],
/// The name of the actual symbol, in some other lib.
/// for example, "MYDLL.expfunc" or "MYDLL.#27"
forwarded_to: &'data [u8],
},

/// A forwarded export (i.e. a symbol that is contained in some other DLL) that has no name.
/// This concept is [PE-specific](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#export-address-table)
ForwardedByOrdinal {
/// The ordinal of this export
ordinal: u32,
/// The name of the actual symbol, in some other lib.
/// for example, "MYDLL.expfunc" or "MYDLL.#27"
forwarded_to: &'data [u8],
},
}

impl<'a> PeExport<'a> {
/// Returns the ordinal of this export
pub fn ordinal(&self) -> u32 {
match &self {
&PeExport::Regular { ordinal, .. }
| &PeExport::Forwarded { ordinal, .. }
| &PeExport::ByOrdinal { ordinal, .. }
| &PeExport::ForwardedByOrdinal { ordinal, .. } => *ordinal,
}
}

/// Whether this export has a name
pub fn has_name(&self) -> bool {
match &self {
&PeExport::Regular { .. } | &PeExport::Forwarded { .. } => true,
&PeExport::ByOrdinal { .. } | &PeExport::ForwardedByOrdinal { .. } => false,
}
}
}

impl<'a> Debug for PeExport<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
match &self {
PeExport::Regular {
ordinal,
name,
address,
} => f
.debug_struct("Regular")
.field("ordinal", &ordinal)
.field(
"data",
&core::str::from_utf8(name).unwrap_or("<invalid name>"),
daladim marked this conversation as resolved.
Show resolved Hide resolved
)
.field("address", &address)
.finish(),
PeExport::ByOrdinal { ordinal, address } => f
.debug_struct("ByOrdinal")
.field("ordinal", &ordinal)
.field("address", &address)
.finish(),
PeExport::Forwarded {
ordinal,
name,
forwarded_to,
} => f
.debug_struct("Forwarded")
.field("ordinal", &ordinal)
.field(
"name",
&core::str::from_utf8(name).unwrap_or("<invalid name>"),
)
.field(
"forwarded_to",
&core::str::from_utf8(forwarded_to).unwrap_or("<invalid forward name>"),
)
.finish(),
PeExport::ForwardedByOrdinal {
ordinal,
forwarded_to,
} => f
.debug_struct("ForwardedByOrdinal")
.field("ordinal", &ordinal)
.field(
"forwarded_to",
&core::str::from_utf8(forwarded_to).unwrap_or("<invalid forward name>"),
)
.finish(),
}
}
}