use alloc::borrow::Cow; use alloc::vec::Vec; use core::fmt::{Debug, LowerHex}; use crate::error; use scroll::ctx::TryFromCtx; use scroll::{Pread, Pwrite, SizeWith}; use crate::pe::data_directories; use crate::pe::options; use crate::pe::section_table; use crate::pe::utils; use log::{debug, warn}; pub const IMPORT_BY_ORDINAL_32: u32 = 0x8000_0000; pub const IMPORT_BY_ORDINAL_64: u64 = 0x8000_0000_0000_0000; pub const IMPORT_RVA_MASK_32: u32 = 0x7fff_ffff; pub const IMPORT_RVA_MASK_64: u64 = 0x0000_0000_7fff_ffff; pub trait Bitfield<'a>: Into + PartialEq + Eq + LowerHex + Debug + TryFromCtx<'a, scroll::Endian, Error = scroll::Error> { fn is_ordinal(&self) -> bool; fn to_ordinal(&self) -> u16; fn to_rva(&self) -> u32; fn size_of() -> usize; fn is_zero(&self) -> bool; } impl<'a> Bitfield<'a> for u64 { fn is_ordinal(&self) -> bool { self & IMPORT_BY_ORDINAL_64 == IMPORT_BY_ORDINAL_64 } fn to_ordinal(&self) -> u16 { (0xffff & self) as u16 } fn to_rva(&self) -> u32 { (self & IMPORT_RVA_MASK_64) as u32 } fn size_of() -> usize { 8 } fn is_zero(&self) -> bool { *self == 0 } } impl<'a> Bitfield<'a> for u32 { fn is_ordinal(&self) -> bool { self & IMPORT_BY_ORDINAL_32 == IMPORT_BY_ORDINAL_32 } fn to_ordinal(&self) -> u16 { (0xffff & self) as u16 } fn to_rva(&self) -> u32 { (self & IMPORT_RVA_MASK_32) as u32 } fn size_of() -> usize { 4 } fn is_zero(&self) -> bool { *self == 0 } } #[derive(Debug, Clone)] pub struct HintNameTableEntry<'a> { pub hint: u16, pub name: &'a str, } impl<'a> HintNameTableEntry<'a> { fn parse(bytes: &'a [u8], mut offset: usize) -> error::Result { let offset = &mut offset; let hint = bytes.gread_with(offset, scroll::LE)?; let name = bytes.pread::<&'a str>(*offset)?; Ok(HintNameTableEntry { hint, name }) } } #[derive(Debug, Clone)] pub enum SyntheticImportLookupTableEntry<'a> { OrdinalNumber(u16), HintNameTableRVA((u32, HintNameTableEntry<'a>)), // [u8; 31] bitfield :/ } pub type ImportLookupTable<'a> = Vec>; impl<'a> SyntheticImportLookupTableEntry<'a> { pub fn parse>( bytes: &'a [u8], offset: usize, sections: &[section_table::SectionTable], file_alignment: u32, ) -> error::Result> { Self::parse_with_opts::( bytes, offset, sections, file_alignment, &options::ParseOptions::default(), ) } pub fn parse_with_opts>( bytes: &'a [u8], mut offset: usize, sections: &[section_table::SectionTable], file_alignment: u32, opts: &options::ParseOptions, ) -> error::Result> { let le = scroll::LE; let offset = &mut offset; let mut table = Vec::new(); loop { let bitfield: T = bytes.gread_with(offset, le)?; if bitfield.is_zero() { debug!("imports done"); break; } else { let entry = { debug!("bitfield {:#x}", bitfield); use self::SyntheticImportLookupTableEntry::*; if bitfield.is_ordinal() { let ordinal = bitfield.to_ordinal(); debug!("importing by ordinal {:#x}", ordinal); OrdinalNumber(ordinal) } else { let rva = bitfield.to_rva(); let hentry = { debug!("searching for RVA {:#x}", rva); if let Some(offset) = utils::find_offset(rva as usize, sections, file_alignment, opts) { debug!("offset {:#x}", offset); HintNameTableEntry::parse(bytes, offset)? } else { warn!("Entry {} has bad RVA: {:#x}", table.len(), rva); continue; } }; HintNameTableRVA((rva, hentry)) } }; table.push(entry); } } Ok(table) } } // get until entry is 0 pub type ImportAddressTable = Vec; #[repr(C)] #[derive(Debug, Pread, Pwrite, SizeWith)] pub struct ImportDirectoryEntry { pub import_lookup_table_rva: u32, pub time_date_stamp: u32, pub forwarder_chain: u32, pub name_rva: u32, pub import_address_table_rva: u32, } pub const SIZEOF_IMPORT_DIRECTORY_ENTRY: usize = 20; impl ImportDirectoryEntry { pub fn is_null(&self) -> bool { (self.import_lookup_table_rva == 0) && (self.time_date_stamp == 0) && (self.forwarder_chain == 0) && (self.name_rva == 0) && (self.import_address_table_rva == 0) } } #[derive(Debug)] pub struct SyntheticImportDirectoryEntry<'a> { pub import_directory_entry: ImportDirectoryEntry, /// Computed pub name: &'a str, /// The import lookup table is a vector of either ordinals, or RVAs + import names pub import_lookup_table: Option>, /// Computed pub import_address_table: ImportAddressTable, } impl<'a> SyntheticImportDirectoryEntry<'a> { pub fn parse>( bytes: &'a [u8], import_directory_entry: ImportDirectoryEntry, sections: &[section_table::SectionTable], file_alignment: u32, ) -> error::Result> { Self::parse_with_opts::( bytes, import_directory_entry, sections, file_alignment, &options::ParseOptions::default(), ) } pub fn parse_with_opts>( bytes: &'a [u8], import_directory_entry: ImportDirectoryEntry, sections: &[section_table::SectionTable], file_alignment: u32, opts: &options::ParseOptions, ) -> error::Result> { const LE: scroll::Endian = scroll::LE; let name_rva = import_directory_entry.name_rva; let name = utils::try_name(bytes, name_rva as usize, sections, file_alignment, opts)?; let import_lookup_table = { let import_lookup_table_rva = import_directory_entry.import_lookup_table_rva; let import_address_table_rva = import_directory_entry.import_address_table_rva; if let Some(import_lookup_table_offset) = utils::find_offset( import_lookup_table_rva as usize, sections, file_alignment, opts, ) { debug!("Synthesizing lookup table imports for {} lib, with import lookup table rva: {:#x}", name, import_lookup_table_rva); let import_lookup_table = SyntheticImportLookupTableEntry::parse_with_opts::( bytes, import_lookup_table_offset, sections, file_alignment, opts, )?; debug!( "Successfully synthesized import lookup table entry from lookup table: {:#?}", import_lookup_table ); Some(import_lookup_table) } else if let Some(import_address_table_offset) = utils::find_offset( import_address_table_rva as usize, sections, file_alignment, opts, ) { debug!("Synthesizing lookup table imports for {} lib, with import address table rva: {:#x}", name, import_lookup_table_rva); let import_address_table = SyntheticImportLookupTableEntry::parse_with_opts::( bytes, import_address_table_offset, sections, file_alignment, opts, )?; debug!( "Successfully synthesized import lookup table entry from IAT: {:#?}", import_address_table ); Some(import_address_table) } else { None } }; let import_address_table_offset = &mut utils::find_offset( import_directory_entry.import_address_table_rva as usize, sections, file_alignment, opts, ) .ok_or_else(|| { error::Error::Malformed(format!( "Cannot map import_address_table_rva {:#x} into offset for {}", import_directory_entry.import_address_table_rva, name )) })?; let mut import_address_table = Vec::new(); loop { let import_address = bytes .gread_with::(import_address_table_offset, LE)? .into(); if import_address == 0 { break; } else { import_address_table.push(import_address); } } Ok(SyntheticImportDirectoryEntry { import_directory_entry, name, import_lookup_table, import_address_table, }) } } #[derive(Debug)] /// Contains a list of synthesized import data for this binary, e.g., which symbols from which libraries it is importing from pub struct ImportData<'a> { pub import_data: Vec>, } impl<'a> ImportData<'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 import_directory_table_rva = dd.virtual_address as usize; debug!( "import_directory_table_rva {:#x}", import_directory_table_rva ); let offset = &mut utils::find_offset(import_directory_table_rva, sections, file_alignment, opts) .ok_or_else(|| { error::Error::Malformed(format!( "Cannot create ImportData; cannot map import_directory_table_rva {:#x} into offset", import_directory_table_rva )) })?; debug!("import data offset {:#x}", offset); let mut import_data = Vec::new(); loop { let import_directory_entry: ImportDirectoryEntry = bytes.gread_with(offset, scroll::LE)?; debug!("{:#?}", import_directory_entry); if import_directory_entry.is_null() { break; } else { let entry = SyntheticImportDirectoryEntry::parse_with_opts::( bytes, import_directory_entry, sections, file_alignment, opts, )?; debug!("entry {:#?}", entry); import_data.push(entry); } } debug!("finished ImportData"); Ok(ImportData { import_data }) } } #[derive(Debug)] /// A synthesized symbol import, the name is pre-indexed, and the binary offset is computed, as well as which dll it belongs to pub struct Import<'a> { pub name: Cow<'a, str>, pub dll: &'a str, pub ordinal: u16, pub offset: usize, pub rva: usize, pub size: usize, } impl<'a> Import<'a> { pub fn parse>( _bytes: &'a [u8], import_data: &ImportData<'a>, _sections: &[section_table::SectionTable], ) -> error::Result>> { let mut imports = Vec::new(); for data in &import_data.import_data { if let Some(ref import_lookup_table) = data.import_lookup_table { let dll = data.name; let import_base = data.import_directory_entry.import_address_table_rva as usize; debug!("Getting imports from {}", &dll); for (i, entry) in import_lookup_table.iter().enumerate() { let offset = import_base + (i * T::size_of()); use self::SyntheticImportLookupTableEntry::*; let (rva, name, ordinal) = match *entry { HintNameTableRVA((rva, ref hint_entry)) => { // if hint_entry.name = "" && hint_entry.hint = 0 { // println!(" warning hint/name table rva from {} without hint {:#x}", dll, rva); // } (rva, Cow::Borrowed(hint_entry.name), hint_entry.hint) } OrdinalNumber(ordinal) => { let name = format!("ORDINAL {}", ordinal); (0x0, Cow::Owned(name), ordinal) } }; let import = Import { name, ordinal, dll, size: T::size_of(), offset, rva: rva as usize, }; imports.push(import); } } } Ok(imports) } }