use alloc::vec::Vec; use scroll::{Pread, Pwrite}; use log::debug; use crate::error; use crate::pe::data_directories; use crate::pe::options; use crate::pe::section_table; use crate::pe::utils; #[repr(C)] #[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite)] pub struct ExportDirectoryTable { pub export_flags: u32, pub time_date_stamp: u32, pub major_version: u16, pub minor_version: u16, pub name_rva: u32, pub ordinal_base: u32, pub address_table_entries: u32, pub number_of_name_pointers: u32, pub export_address_table_rva: u32, pub name_pointer_rva: u32, pub ordinal_table_rva: u32, } pub const SIZEOF_EXPORT_DIRECTORY_TABLE: usize = 40; impl ExportDirectoryTable { pub fn parse(bytes: &[u8], offset: usize) -> error::Result { let res = bytes.pread_with(offset, scroll::LE)?; Ok(res) } } #[derive(Debug)] pub enum ExportAddressTableEntry { ExportRVA(u32), ForwarderRVA(u32), } pub const SIZEOF_EXPORT_ADDRESS_TABLE_ENTRY: usize = 4; pub type ExportAddressTable = Vec; /// Array of rvas into the export name table /// /// Export name is defined iff pointer table has pointer to the name pub type ExportNamePointerTable = Vec; /// Array of indexes into the export address table. /// /// Should obey the formula `idx = ordinal - ordinalbase` pub type ExportOrdinalTable = Vec; #[derive(Debug, Default)] /// Export data contains the `dll` name which other libraries can import symbols by (two-level namespace), as well as other important indexing data allowing symbol lookups pub struct ExportData<'a> { pub name: Option<&'a str>, pub export_directory_table: ExportDirectoryTable, pub export_name_pointer_table: ExportNamePointerTable, pub export_ordinal_table: ExportOrdinalTable, pub export_address_table: ExportAddressTable, } impl<'a> ExportData<'a> { pub fn parse( bytes: &'a [u8], dd: data_directories::DataDirectory, sections: &[section_table::SectionTable], file_alignment: u32, ) -> error::Result> { Self::parse_with_opts( bytes, dd, sections, file_alignment, &options::ParseOptions::default(), ) } pub fn parse_with_opts( bytes: &'a [u8], dd: data_directories::DataDirectory, sections: &[section_table::SectionTable], file_alignment: u32, opts: &options::ParseOptions, ) -> error::Result> { let export_rva = dd.virtual_address as usize; let size = dd.size as usize; debug!("export_rva {:#x} size {:#}", export_rva, size); let export_offset = utils::find_offset_or( export_rva, sections, file_alignment, opts, &format!("cannot map export_rva ({:#x}) into offset", export_rva), )?; let export_directory_table = ExportDirectoryTable::parse(bytes, export_offset).map_err(|_| { error::Error::Malformed(format!( "cannot parse export_directory_table (offset {:#x})", export_offset )) })?; let number_of_name_pointers = export_directory_table.number_of_name_pointers as usize; let address_table_entries = export_directory_table.address_table_entries as usize; if number_of_name_pointers > bytes.len() { return Err(error::Error::BufferTooShort( number_of_name_pointers, "name pointers", )); } if address_table_entries > bytes.len() { return Err(error::Error::BufferTooShort( address_table_entries, "address table entries", )); } let export_name_pointer_table = utils::find_offset( export_directory_table.name_pointer_rva as usize, sections, file_alignment, opts, ) .map_or(vec![], |table_offset| { let mut offset = table_offset; let mut table: ExportNamePointerTable = Vec::with_capacity(number_of_name_pointers); for _ in 0..number_of_name_pointers { if let Ok(name_rva) = bytes.gread_with(&mut offset, scroll::LE) { table.push(name_rva); } else { break; } } table }); let export_ordinal_table = utils::find_offset( export_directory_table.ordinal_table_rva as usize, sections, file_alignment, opts, ) .map_or(vec![], |table_offset| { let mut offset = table_offset; let mut table: ExportOrdinalTable = Vec::with_capacity(number_of_name_pointers); for _ in 0..number_of_name_pointers { if let Ok(name_ordinal) = bytes.gread_with(&mut offset, scroll::LE) { table.push(name_ordinal); } else { break; } } table }); let export_address_table = utils::find_offset( export_directory_table.export_address_table_rva as usize, sections, file_alignment, opts, ) .map_or(vec![], |table_offset| { let mut offset = table_offset; let mut table: ExportAddressTable = Vec::with_capacity(address_table_entries); let export_end = export_rva + size; for _ in 0..address_table_entries { if let Ok(func_rva) = bytes.gread_with::(&mut offset, scroll::LE) { if utils::is_in_range(func_rva as usize, export_rva, export_end) { table.push(ExportAddressTableEntry::ForwarderRVA(func_rva)); } else { table.push(ExportAddressTableEntry::ExportRVA(func_rva)); } } else { break; } } table }); let name = utils::find_offset( export_directory_table.name_rva as usize, sections, file_alignment, opts, ) .and_then(|offset| bytes.pread(offset).ok()); Ok(ExportData { name, export_directory_table, export_name_pointer_table, export_ordinal_table, export_address_table, }) } } #[derive(Debug)] /// PE binaries have two kinds of reexports, either specifying the dll's name, or the ordinal value of the dll pub enum Reexport<'a> { DLLName { export: &'a str, lib: &'a str }, DLLOrdinal { ordinal: usize, lib: &'a str }, } impl<'a> scroll::ctx::TryFromCtx<'a, scroll::Endian> for Reexport<'a> { type Error = crate::error::Error; #[inline] fn try_from_ctx(bytes: &'a [u8], _ctx: scroll::Endian) -> Result<(Self, usize), Self::Error> { let reexport = bytes.pread::<&str>(0)?; let reexport_len = reexport.len(); debug!("reexport: {}", &reexport); for o in 0..reexport_len { let c: u8 = bytes.pread(o)?; debug!("reexport offset: {:#x} char: {:#x}", o, c); if c == b'.' { let dll: &'a str = bytes.pread_with(0, scroll::ctx::StrCtx::Length(o))?; debug!("dll: {:?}", &dll); if o + 1 == reexport_len { break; } let len = reexport_len - o - 1; let rest: &'a [u8] = bytes.pread_with(o + 1, len)?; debug!("rest: {:?}", &rest); if rest[0] == b'#' { let ordinal = rest.pread_with::<&str>(1, scroll::ctx::StrCtx::Length(len - 1))?; let ordinal = ordinal.parse::().map_err(|_e| { error::Error::Malformed(format!( "Cannot parse reexport ordinal from {} bytes", bytes.len() )) })?; return Ok(( Reexport::DLLOrdinal { ordinal: ordinal as usize, lib: dll, }, reexport_len + 1, )); } else { let export = rest.pread_with::<&str>(0, scroll::ctx::StrCtx::Length(len))?; return Ok((Reexport::DLLName { export, lib: dll }, reexport_len + 1)); } } } Err(error::Error::Malformed(format!( "Reexport {:#} is malformed", reexport ))) } } impl<'a> Reexport<'a> { pub fn parse(bytes: &'a [u8], offset: usize) -> crate::error::Result> { bytes.pread(offset) } } #[derive(Debug, Default)] /// An exported symbol in this binary, contains synthetic data (name offset, etc., are computed) pub struct Export<'a> { pub name: Option<&'a str>, pub offset: Option, pub rva: usize, pub size: usize, pub reexport: Option>, } #[derive(Debug, Copy, Clone)] struct ExportCtx<'a> { pub ptr: u32, pub idx: usize, pub sections: &'a [section_table::SectionTable], pub file_alignment: u32, pub addresses: &'a ExportAddressTable, pub ordinals: &'a ExportOrdinalTable, pub opts: options::ParseOptions, } impl<'a, 'b> scroll::ctx::TryFromCtx<'a, ExportCtx<'b>> for Export<'a> { type Error = error::Error; #[inline] fn try_from_ctx( bytes: &'a [u8], ExportCtx { ptr, idx, sections, file_alignment, addresses, ordinals, opts, }: ExportCtx<'b>, ) -> Result<(Self, usize), Self::Error> { use self::ExportAddressTableEntry::*; let name = utils::find_offset(ptr as usize, sections, file_alignment, &opts) .and_then(|offset| bytes.pread::<&str>(offset).ok()); if let Some(ordinal) = ordinals.get(idx) { if let Some(rva) = addresses.get(*ordinal as usize) { match *rva { ExportRVA(rva) => { let rva = rva as usize; let offset = utils::find_offset(rva, sections, file_alignment, &opts); Ok(( Export { name, offset, rva, reexport: None, size: 0, }, 0, )) } ForwarderRVA(rva) => { let rva = rva as usize; let offset = utils::find_offset_or( rva, sections, file_alignment, &opts, &format!( "cannot map RVA ({:#x}) of export ordinal {} into offset", rva, ordinal ), )?; let reexport = Reexport::parse(bytes, offset)?; Ok(( Export { name, offset: Some(offset), rva, reexport: Some(reexport), size: 0, }, 0, )) } } } else { Err(error::Error::Malformed(format!( "cannot get RVA of export ordinal {}", ordinal ))) } } else { Err(error::Error::Malformed(format!( "cannot get ordinal of export name entry {}", idx ))) } } } impl<'a> Export<'a> { pub fn parse( bytes: &'a [u8], export_data: &ExportData, sections: &[section_table::SectionTable], file_alignment: u32, ) -> error::Result>> { Self::parse_with_opts( bytes, export_data, sections, file_alignment, &options::ParseOptions::default(), ) } pub fn parse_with_opts( bytes: &'a [u8], export_data: &ExportData, sections: &[section_table::SectionTable], file_alignment: u32, opts: &options::ParseOptions, ) -> error::Result>> { let pointers = &export_data.export_name_pointer_table; let addresses = &export_data.export_address_table; let ordinals = &export_data.export_ordinal_table; let mut exports = Vec::with_capacity(pointers.len()); for (idx, &ptr) in pointers.iter().enumerate() { if let Ok(export) = bytes.pread_with( 0, ExportCtx { ptr, idx, sections, file_alignment, addresses, ordinals, opts: *opts, }, ) { exports.push(export); } } // TODO: sort + compute size Ok(exports) } } #[cfg(test)] mod tests { use self::data_directories::*; use super::*; static CORKAMI_POCS_PE_EXPORTSDATA_EXE: [u8; 0x400] = [ 0x4d, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4c, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x02, 0x01, 0x0b, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xb0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x20, 0x2a, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x66, 0x61, 0x6b, 0x65, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x10, 0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x66, 0x00, 0x6d, 0x73, 0x76, 0x63, 0x72, 0x74, 0x2e, 0x64, 0x6c, 0x6c, 0x00, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x65, 0x78, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x14, 0x10, 0xf0, 0x10, 0xff, 0x15, 0x30, 0x10, 0x00, 0x10, 0x73, 0xc4, 0x04, 0xc3, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[test] fn size_export_directory_table() { assert_eq!( ::std::mem::size_of::(), SIZEOF_EXPORT_DIRECTORY_TABLE ); } #[test] fn parse_export_table() { let data_dirs = DataDirectories::parse(&CORKAMI_POCS_PE_EXPORTSDATA_EXE[..], 16, &mut 0xb8).unwrap(); let export_table = data_dirs.get_export_table().unwrap(); assert_eq!(export_table.virtual_address, 0x10b0); assert_eq!(export_table.size, 0x0); } #[test] fn parse_export_directory() { let data_dir = ExportDirectoryTable::parse(&CORKAMI_POCS_PE_EXPORTSDATA_EXE[..], 0x2b0); assert!(data_dir.is_ok()); let data_dir = data_dir.unwrap(); assert_eq!(data_dir.export_flags, 0x0); assert_eq!(data_dir.time_date_stamp, 0x0); assert_eq!(data_dir.major_version, 0x0); assert_eq!(data_dir.minor_version, 0x0); assert_eq!(data_dir.name_rva, 0x0); assert_eq!(data_dir.ordinal_base, 0x0); assert_eq!(data_dir.address_table_entries, 0x4); assert_eq!(data_dir.number_of_name_pointers, 0x0); assert_eq!(data_dir.export_address_table_rva, 0x10e0); assert_eq!(data_dir.name_pointer_rva, 0x0); assert_eq!(data_dir.ordinal_table_rva, 0x1100); } }