From 3727868878ec917475d224fdd860e10a4b5020ed Mon Sep 17 00:00:00 2001 From: ko1N Date: Sun, 6 Oct 2019 20:54:43 +0200 Subject: [PATCH] Added a feature to disable pe rva resolve for already mapped modules (e.g. from a memory dump) --- src/pe/debug.rs | 53 +++++++++++++++++++++++++--- src/pe/exception.rs | 59 ++++++++++++++++++++++++++----- src/pe/export.rs | 45 +++++++++++++++++++++++- src/pe/import.rs | 84 +++++++++++++++++++++++++++++++++++++++------ src/pe/mod.rs | 36 ++++++++++++++----- src/pe/options.rs | 13 +++++++ src/pe/utils.rs | 63 ++++++++++++++++++++++++---------- 7 files changed, 304 insertions(+), 49 deletions(-) create mode 100644 src/pe/options.rs diff --git a/src/pe/debug.rs b/src/pe/debug.rs index f5c78555..a2b55e92 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -2,6 +2,7 @@ use crate::error; use scroll::{Pread, Pwrite, SizeWith}; use crate::pe::data_directories; +use crate::pe::options; use crate::pe::section_table; use crate::pe::utils; @@ -17,11 +18,27 @@ impl<'a> DebugData<'a> { 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 image_debug_directory = - ImageDebugDirectory::parse(bytes, dd, sections, file_alignment)?; + ImageDebugDirectory::parse_with_opts(bytes, dd, sections, file_alignment, opts)?; let codeview_pdb70_debug_info = - CodeviewPDB70DebugInfo::parse(bytes, &image_debug_directory)?; + CodeviewPDB70DebugInfo::parse_with_opts(bytes, &image_debug_directory, opts)?; Ok(DebugData { image_debug_directory, @@ -59,14 +76,31 @@ pub const IMAGE_DEBUG_TYPE_FIXUP: u32 = 6; pub const IMAGE_DEBUG_TYPE_BORLAND: u32 = 9; impl ImageDebugDirectory { + #[allow(unused)] fn parse( bytes: &[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(), + ) + } + + fn parse_with_opts( + bytes: &[u8], + dd: data_directories::DataDirectory, + sections: &[section_table::SectionTable], + file_alignment: u32, + opts: &options::ParseOptions, ) -> error::Result { let rva = dd.virtual_address as usize; - let offset = utils::find_offset(rva, sections, file_alignment).ok_or_else(|| { + let offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| { error::Error::Malformed(format!( "Cannot map ImageDebugDirectory rva {:#x} into offset", rva @@ -94,6 +128,14 @@ pub struct CodeviewPDB70DebugInfo<'a> { impl<'a> CodeviewPDB70DebugInfo<'a> { pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result> { + Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) + } + + pub fn parse_with_opts( + bytes: &'a [u8], + idd: &ImageDebugDirectory, + opts: &options::ParseOptions, + ) -> error::Result> { if idd.data_type != IMAGE_DEBUG_TYPE_CODEVIEW { // not a codeview debug directory // that's not an error, but it's not a CodeviewPDB70DebugInfo either @@ -101,7 +143,10 @@ impl<'a> CodeviewPDB70DebugInfo<'a> { } // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly - let mut offset: usize = idd.pointer_to_raw_data as usize; + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; // calculate how long the eventual filename will be, which doubles as a check of the record size let filename_length = idd.size_of_data as isize - 24; diff --git a/src/pe/exception.rs b/src/pe/exception.rs index 8f853a24..d26854d3 100644 --- a/src/pe/exception.rs +++ b/src/pe/exception.rs @@ -49,6 +49,7 @@ use scroll::{self, Pread, Pwrite}; use crate::error; use crate::pe::data_directories; +use crate::pe::options; use crate::pe::section_table; use crate::pe::utils; @@ -666,6 +667,23 @@ impl<'a> ExceptionData<'a> { directory: data_directories::DataDirectory, sections: &[section_table::SectionTable], file_alignment: u32, + ) -> error::Result { + Self::parse_with_opts( + bytes, + directory, + sections, + file_alignment, + &options::ParseOptions::default(), + ) + } + + /// Parses exception data from the image at the given offset. + pub fn parse_with_opts( + bytes: &'a [u8], + directory: data_directories::DataDirectory, + sections: &[section_table::SectionTable], + file_alignment: u32, + opts: &options::ParseOptions, ) -> error::Result { let size = directory.size as usize; @@ -677,7 +695,7 @@ impl<'a> ExceptionData<'a> { } let rva = directory.virtual_address as usize; - let offset = utils::find_offset(rva, sections, file_alignment).ok_or_else(|| { + let offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| { error::Error::Malformed(format!("cannot map exception_rva ({:#x}) into offset", rva)) })?; @@ -761,31 +779,56 @@ impl<'a> ExceptionData<'a> { /// Resolves unwind information for the given function entry. pub fn get_unwind_info( + &self, + function: RuntimeFunction, + sections: &[section_table::SectionTable], + ) -> error::Result> { + self.get_unwind_info_with_opts(function, sections, &options::ParseOptions::default()) + } + + /// Resolves unwind information for the given function entry. + pub fn get_unwind_info_with_opts( &self, mut function: RuntimeFunction, sections: &[section_table::SectionTable], + opts: &options::ParseOptions, ) -> error::Result> { while function.unwind_info_address % 2 != 0 { let rva = (function.unwind_info_address & !1) as usize; - function = self.get_function_by_rva(rva, sections)?; + function = self.get_function_by_rva_with_opts(rva, sections, opts)?; } let rva = function.unwind_info_address as usize; - let offset = utils::find_offset(rva, sections, self.file_alignment).ok_or_else(|| { - error::Error::Malformed(format!("cannot map unwind rva ({:#x}) into offset", rva)) - })?; + let offset = + utils::find_offset(rva, sections, self.file_alignment, opts).ok_or_else(|| { + error::Error::Malformed(format!("cannot map unwind rva ({:#x}) into offset", rva)) + })?; UnwindInfo::parse(self.bytes, offset) } + #[allow(dead_code)] fn get_function_by_rva( &self, rva: usize, sections: &[section_table::SectionTable], ) -> error::Result { - let offset = utils::find_offset(rva, sections, self.file_alignment).ok_or_else(|| { - error::Error::Malformed(format!("cannot map exception rva ({:#x}) into offset", rva)) - })?; + self.get_function_by_rva_with_opts(rva, sections, &options::ParseOptions::default()) + } + + fn get_function_by_rva_with_opts( + &self, + rva: usize, + sections: &[section_table::SectionTable], + opts: &options::ParseOptions, + ) -> error::Result { + let offset = + utils::find_offset(rva, sections, self.file_alignment, opts).ok_or_else(|| { + error::Error::Malformed(format!( + "cannot map exception rva ({:#x}) into offset", + rva + )) + })?; self.get_function_by_offset(offset) } diff --git a/src/pe/export.rs b/src/pe/export.rs index 5bbe802d..b4705617 100644 --- a/src/pe/export.rs +++ b/src/pe/export.rs @@ -6,6 +6,7 @@ use log::debug; use crate::error; use crate::pe::data_directories; +use crate::pe::options; use crate::pe::section_table; use crate::pe::utils; @@ -70,6 +71,22 @@ impl<'a> ExportData<'a> { 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; @@ -78,6 +95,7 @@ impl<'a> ExportData<'a> { export_rva, sections, file_alignment, + opts, &format!("cannot map export_rva ({:#x}) into offset", export_rva), )?; let export_directory_table = @@ -94,6 +112,7 @@ impl<'a> ExportData<'a> { export_directory_table.name_pointer_rva as usize, sections, file_alignment, + opts, ) .map_or(vec![], |table_offset| { let mut offset = table_offset; @@ -114,6 +133,7 @@ impl<'a> ExportData<'a> { export_directory_table.ordinal_table_rva as usize, sections, file_alignment, + opts, ) .map_or(vec![], |table_offset| { let mut offset = table_offset; @@ -134,6 +154,7 @@ impl<'a> ExportData<'a> { export_directory_table.export_address_table_rva as usize, sections, file_alignment, + opts, ) .map_or(vec![], |table_offset| { let mut offset = table_offset; @@ -159,6 +180,7 @@ impl<'a> ExportData<'a> { export_directory_table.name_rva as usize, sections, file_alignment, + opts, ) .and_then(|offset| bytes.pread(offset).ok()); @@ -251,6 +273,7 @@ struct ExportCtx<'a> { 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> { @@ -265,11 +288,12 @@ impl<'a, 'b> scroll::ctx::TryFromCtx<'a, ExportCtx<'b>> for Export<'a> { 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) + 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) { @@ -281,6 +305,7 @@ impl<'a, 'b> scroll::ctx::TryFromCtx<'a, ExportCtx<'b>> for Export<'a> { rva, sections, file_alignment, + &opts, &format!( "cannot map RVA ({:#x}) of export ordinal {} into offset", rva, ordinal @@ -304,6 +329,7 @@ impl<'a, 'b> scroll::ctx::TryFromCtx<'a, ExportCtx<'b>> for Export<'a> { rva, sections, file_alignment, + &opts, &format!( "cannot map RVA ({:#x}) of export ordinal {} into offset", rva, ordinal @@ -343,6 +369,22 @@ impl<'a> Export<'a> { 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; @@ -359,6 +401,7 @@ impl<'a> Export<'a> { file_alignment, addresses, ordinals, + opts: *opts, }, ) { exports.push(export); diff --git a/src/pe/import.rs b/src/pe/import.rs index 714f4357..b6d45e26 100644 --- a/src/pe/import.rs +++ b/src/pe/import.rs @@ -7,6 +7,7 @@ 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; @@ -93,10 +94,26 @@ 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; @@ -119,7 +136,7 @@ impl<'a> SyntheticImportLookupTableEntry<'a> { let hentry = { debug!("searching for RVA {:#x}", rva); if let Some(offset) = - utils::find_offset(rva as usize, sections, file_alignment) + utils::find_offset(rva as usize, sections, file_alignment, opts) { debug!("offset {:#x}", offset); HintNameTableEntry::parse(bytes, offset)? @@ -180,16 +197,35 @@ impl<'a> SyntheticImportDirectoryEntry<'a> { 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)?; + 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) - { + 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::( bytes, @@ -202,9 +238,12 @@ impl<'a> SyntheticImportDirectoryEntry<'a> { 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) - { + } 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::( bytes, @@ -226,6 +265,7 @@ impl<'a> SyntheticImportDirectoryEntry<'a> { import_directory_entry.import_address_table_rva as usize, sections, file_alignment, + opts, ) .ok_or_else(|| { error::Error::Malformed(format!( @@ -265,13 +305,36 @@ impl<'a> ImportData<'a> { 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).ok_or_else(|| error::Error::Malformed(format!("Cannot create ImportData; cannot map import_directory_table_rva {:#x} into offset", 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 { @@ -281,11 +344,12 @@ impl<'a> ImportData<'a> { if import_directory_entry.is_null() { break; } else { - let entry = SyntheticImportDirectoryEntry::parse::( + let entry = SyntheticImportDirectoryEntry::parse_with_opts::( bytes, import_directory_entry, sections, file_alignment, + opts, )?; debug!("entry {:#?}", entry); import_data.push(entry); diff --git a/src/pe/mod.rs b/src/pe/mod.rs index 76337893..53af6278 100644 --- a/src/pe/mod.rs +++ b/src/pe/mod.rs @@ -13,6 +13,7 @@ pub mod export; pub mod header; pub mod import; pub mod optional_header; +pub mod options; pub mod relocation; pub mod section_table; pub mod symbol; @@ -62,6 +63,11 @@ pub struct PE<'a> { impl<'a> PE<'a> { /// Reads a PE binary from the underlying `bytes` pub fn parse(bytes: &'a [u8]) -> error::Result { + Self::parse_with_opts(bytes, &options::ParseOptions::default()) + } + + /// Reads a PE binary from the underlying `bytes` + pub fn parse_with_opts(bytes: &'a [u8], opts: &options::ParseOptions) -> error::Result { let header = header::Header::parse(bytes)?; debug!("{:#?}", header); let offset = &mut (header.dos_header.pe_pointer as usize @@ -91,11 +97,21 @@ impl<'a> PE<'a> { ); let file_alignment = optional_header.windows_fields.file_alignment; if let Some(export_table) = *optional_header.data_directories.get_export_table() { - if let Ok(ed) = - export::ExportData::parse(bytes, export_table, §ions, file_alignment) - { + if let Ok(ed) = export::ExportData::parse_with_opts( + bytes, + export_table, + §ions, + file_alignment, + opts, + ) { debug!("export data {:#?}", ed); - exports = export::Export::parse(bytes, &ed, §ions, file_alignment)?; + exports = export::Export::parse_with_opts( + bytes, + &ed, + §ions, + file_alignment, + opts, + )?; name = ed.name; debug!("name: {:#?}", name); export_data = Some(ed); @@ -104,18 +120,20 @@ impl<'a> PE<'a> { debug!("exports: {:#?}", exports); if let Some(import_table) = *optional_header.data_directories.get_import_table() { let id = if is_64 { - import::ImportData::parse::( + import::ImportData::parse_with_opts::( bytes, import_table, §ions, file_alignment, + opts, )? } else { - import::ImportData::parse::( + import::ImportData::parse_with_opts::( bytes, import_table, §ions, file_alignment, + opts, )? }; debug!("import data {:#?}", id); @@ -135,11 +153,12 @@ impl<'a> PE<'a> { } debug!("imports: {:#?}", imports); if let Some(debug_table) = *optional_header.data_directories.get_debug_table() { - debug_data = Some(debug::DebugData::parse( + debug_data = Some(debug::DebugData::parse_with_opts( bytes, debug_table, §ions, file_alignment, + opts, )?); } @@ -149,11 +168,12 @@ impl<'a> PE<'a> { if let Some(exception_table) = *optional_header.data_directories.get_exception_table() { - exception_data = Some(exception::ExceptionData::parse( + exception_data = Some(exception::ExceptionData::parse_with_opts( bytes, exception_table, §ions, file_alignment, + opts, )?); } } diff --git a/src/pe/options.rs b/src/pe/options.rs new file mode 100644 index 00000000..5fea632f --- /dev/null +++ b/src/pe/options.rs @@ -0,0 +1,13 @@ +/// Parsing Options structure for the PE parser +#[derive(Debug, Copy, Clone)] +pub struct ParseOptions { + /// Wether the parser should resolve rvas or not. Default: true + pub resolve_rva: bool, +} + +impl ParseOptions { + /// Returns a parse options structure with default values + pub fn default() -> Self { + ParseOptions { resolve_rva: true } + } +} diff --git a/src/pe/utils.rs b/src/pe/utils.rs index 730d74d0..b9cdec12 100644 --- a/src/pe/utils.rs +++ b/src/pe/utils.rs @@ -2,6 +2,7 @@ use crate::error; use alloc::string::ToString; use scroll::Pread; +use super::options; use super::section_table; use crate::pe::data_directories::DataDirectory; @@ -62,36 +63,42 @@ pub fn find_offset( rva: usize, sections: &[section_table::SectionTable], file_alignment: u32, + opts: &options::ParseOptions, ) -> Option { - for (i, section) in sections.iter().enumerate() { - debug!( - "Checking {} for {:#x} ∈ {:#x}..{:#x}", - section.name().unwrap_or(""), - rva, - section.virtual_address, - section.virtual_address + section.virtual_size - ); - if is_in_section(rva, §ion, file_alignment) { - let offset = rva2offset(rva, §ion); + if opts.resolve_rva { + for (i, section) in sections.iter().enumerate() { debug!( - "Found in section {}({}), remapped into offset {:#x}", + "Checking {} for {:#x} ∈ {:#x}..{:#x}", section.name().unwrap_or(""), - i, - offset + rva, + section.virtual_address, + section.virtual_address + section.virtual_size ); - return Some(offset); + if is_in_section(rva, §ion, file_alignment) { + let offset = rva2offset(rva, §ion); + debug!( + "Found in section {}({}), remapped into offset {:#x}", + section.name().unwrap_or(""), + i, + offset + ); + return Some(offset); + } } + None + } else { + Some(rva) } - None } pub fn find_offset_or( rva: usize, sections: &[section_table::SectionTable], file_alignment: u32, + opts: &options::ParseOptions, msg: &str, ) -> error::Result { - find_offset(rva, sections, file_alignment) + find_offset(rva, sections, file_alignment, opts) .ok_or_else(|| error::Error::Malformed(msg.to_string())) } @@ -100,8 +107,9 @@ pub fn try_name<'a>( rva: usize, sections: &[section_table::SectionTable], file_alignment: u32, + opts: &options::ParseOptions, ) -> error::Result<&'a str> { - match find_offset(rva, sections, file_alignment) { + match find_offset(rva, sections, file_alignment, opts) { Some(offset) => Ok(bytes.pread::<&str>(offset)?), None => Err(error::Error::Malformed(format!( "Cannot find name from rva {:#x} in sections: {:?}", @@ -116,11 +124,30 @@ pub fn get_data<'a, T>( directory: DataDirectory, file_alignment: u32, ) -> error::Result +where + T: scroll::ctx::TryFromCtx<'a, scroll::Endian, Error = scroll::Error>, +{ + get_data_with_opts( + bytes, + sections, + directory, + file_alignment, + &options::ParseOptions::default(), + ) +} + +pub fn get_data_with_opts<'a, T>( + bytes: &'a [u8], + sections: &[section_table::SectionTable], + directory: DataDirectory, + file_alignment: u32, + opts: &options::ParseOptions, +) -> error::Result where T: scroll::ctx::TryFromCtx<'a, scroll::Endian, Error = scroll::Error>, { let rva = directory.virtual_address as usize; - let offset = find_offset(rva, sections, file_alignment) + let offset = find_offset(rva, sections, file_alignment, opts) .ok_or_else(|| error::Error::Malformed(directory.virtual_address.to_string()))?; let result: T = bytes.pread_with(offset, scroll::LE)?; Ok(result)