use std::path::Path;
use object::pe::{ImageExportDirectory, IMAGE_FILE_DLL};
use object::read::pe::{ExportTarget, ImageNtHeaders, ImageOptionalHeader};
use object::LittleEndian as LE;
use crate::error::{Error, Result};
use crate::types::{Architecture, ExportDirectory, ExportInfo, PeFile};
pub fn parse_pe_file(path: impl AsRef<Path>) -> Result<PeFile> {
let path = path.as_ref();
let data = std::fs::read(path).map_err(|e| Error::FileOpen {
path: path.to_path_buf(),
source: e,
})?;
let mut pe = parse_pe_bytes(&data)?;
pe.path = path.to_path_buf();
Ok(pe)
}
pub fn parse_pe_bytes(data: &[u8]) -> Result<PeFile> {
if let Ok(pe) = object::read::pe::PeFile64::parse(data) {
return build_pe_file(&pe, data);
}
if let Ok(pe) = object::read::pe::PeFile32::parse(data) {
return build_pe_file(&pe, data);
}
Err(Error::InvalidFileFormat)
}
fn build_pe_file<'data, Pe>(
pe: &object::read::pe::PeFile<'data, Pe, &'data [u8]>,
_data: &'data [u8],
) -> Result<PeFile>
where
Pe: ImageNtHeaders,
{
let nt = pe.nt_headers();
let fh = nt.file_header();
let machine = fh.machine.get(LE);
let architecture = Architecture::from_machine_type(machine);
let characteristics = fh.characteristics.get(LE);
let is_dll = (characteristics & IMAGE_FILE_DLL) != 0;
let opt = nt.optional_header();
let image_base = opt.image_base();
let entry_point_rva = opt.address_of_entry_point();
let (export_directory, exports) = match pe.export_table() {
Ok(Some(et)) => parse_exports(&et)?,
Ok(None) => (None, Vec::new()),
Err(e) => return Err(Error::PeParse(format!("failed to read export table: {e}"))),
};
Ok(PeFile {
path: std::path::PathBuf::new(),
architecture,
is_dll,
image_base,
entry_point_rva,
export_directory,
exports,
})
}
fn parse_exports(
et: &object::read::pe::ExportTable<'_>,
) -> Result<(Option<ExportDirectory>, Vec<ExportInfo>)> {
let dir: &ImageExportDirectory = et.directory();
let export_directory = ExportDirectory {
characteristics: dir.characteristics.get(LE),
timestamp: dir.time_date_stamp.get(LE),
major_version: dir.major_version.get(LE),
minor_version: dir.minor_version.get(LE),
name_rva: dir.name.get(LE),
ordinal_base: dir.base.get(LE),
address_table_entries: dir.number_of_functions.get(LE),
number_of_name_pointers: dir.number_of_names.get(LE),
address_table_rva: dir.address_of_functions.get(LE),
name_pointer_rva: dir.address_of_names.get(LE),
ordinal_table_rva: dir.address_of_name_ordinals.get(LE),
};
let raw_exports = et
.exports()
.map_err(|e| Error::PeParse(format!("failed to iterate exports: {e}")))?;
let mut exports = Vec::with_capacity(raw_exports.len());
for exp in &raw_exports {
let name = exp
.name
.map(|n| {
String::from_utf8(n.to_vec())
.map_err(|e| Error::PeParse(format!("invalid UTF-8 in export name: {e}")))
})
.transpose()?;
let ordinal = exp.ordinal;
let (is_forwarded, forward_to, relative_address) = match exp.target {
ExportTarget::Address(rva) => (false, None, Some(rva)),
ExportTarget::ForwardByName(dll, fname) => {
let dll_s = String::from_utf8_lossy(dll);
let fname_s = String::from_utf8_lossy(fname);
(true, Some(format!("{dll_s}.{fname_s}")), None)
}
ExportTarget::ForwardByOrdinal(dll, ord) => {
let dll_s = String::from_utf8_lossy(dll);
(true, Some(format!("{dll_s}.#{ord}")), None)
}
};
exports.push(ExportInfo {
name,
ordinal,
is_forwarded,
forward_to,
relative_address,
});
}
Ok((Some(export_directory), exports))
}
impl PeFile {
pub fn parse(path: impl AsRef<Path>) -> Result<Self> {
parse_pe_file(path.as_ref())
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
parse_pe_bytes(data)
}
pub fn export_directory(&self) -> Option<&ExportDirectory> {
self.export_directory.as_ref()
}
pub fn exports(&self) -> &[ExportInfo] {
&self.exports
}
pub fn architecture(&self) -> Architecture {
self.architecture
}
pub fn is_dll(&self) -> bool {
self.is_dll
}
pub fn find_export(&self, name: &str) -> Option<&ExportInfo> {
self.exports.iter().find(|e| e.name.as_deref() == Some(name))
}
pub fn find_export_by_ordinal(&self, ordinal: u32) -> Option<&ExportInfo> {
self.exports.iter().find(|e| e.ordinal == ordinal)
}
pub fn export_names(&self) -> Vec<String> {
let mut names: Vec<_> = self
.exports
.iter()
.filter_map(|e| e.name.clone())
.collect();
names.sort();
names
}
}