From 1b0ea3a80884dbcf6cd79f25beefedb1af35760d 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 | 37 +++++++++++++++++++++++++---- src/pe/exception.rs | 40 +++++++++++++++++++++++++++---- src/pe/export.rs | 33 +++++++++++++++++++++++++- src/pe/import.rs | 45 ++++++++++++++++++++++++++++++----- src/pe/mod.rs | 22 ++++++++++++----- src/pe/options.rs | 15 ++++++++++++ src/pe/utils.rs | 57 +++++++++++++++++++++++++++++++-------------- 7 files changed, 210 insertions(+), 39 deletions(-) create mode 100644 src/pe/options.rs diff --git a/src/pe/debug.rs b/src/pe/debug.rs index f5c78555..fa9c5c31 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -4,6 +4,7 @@ use scroll::{Pread, Pwrite, SizeWith}; use crate::pe::data_directories; use crate::pe::section_table; use crate::pe::utils; +use crate::pe::options; #[derive(Debug, PartialEq, Copy, Clone, Default)] pub struct DebugData<'a> { @@ -17,11 +18,21 @@ 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 +70,25 @@ 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 +116,10 @@ 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 +127,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..ce0b69e3 100644 --- a/src/pe/exception.rs +++ b/src/pe/exception.rs @@ -50,6 +50,7 @@ use crate::error; use crate::pe::data_directories; use crate::pe::section_table; +use crate::pe::options; use crate::pe::utils; /// The function has an exception handler that should be called when looking for functions that need @@ -662,10 +663,21 @@ pub struct ExceptionData<'a> { impl<'a> ExceptionData<'a> { /// Parses exception data from the image at the given offset. pub fn parse( + bytes: &'a [u8], + 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 +689,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,29 +773,49 @@ 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(|| { + 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 { + 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).ok_or_else(|| { + 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)) })?; diff --git a/src/pe/export.rs b/src/pe/export.rs index 5bbe802d..9f360951 100644 --- a/src/pe/export.rs +++ b/src/pe/export.rs @@ -5,6 +5,7 @@ use log::debug; use crate::error; +use crate::pe::options; use crate::pe::data_directories; use crate::pe::section_table; use crate::pe::utils; @@ -70,6 +71,16 @@ 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 +89,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 +106,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 +127,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 +148,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 +174,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 +267,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 +282,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 +299,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 +323,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 +363,16 @@ 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 +389,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..b16da9e7 100644 --- a/src/pe/import.rs +++ b/src/pe/import.rs @@ -8,6 +8,7 @@ use scroll::{Pread, Pwrite, SizeWith}; use crate::pe::data_directories; use crate::pe::section_table; +use crate::pe::options; use crate::pe::utils; use log::{debug, warn}; @@ -93,10 +94,20 @@ 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 +130,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,15 +191,25 @@ 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) + 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::( @@ -203,7 +224,7 @@ impl<'a> SyntheticImportDirectoryEntry<'a> { ); Some(import_lookup_table) } else if let Some(import_address_table_offset) = - utils::find_offset(import_address_table_rva as usize, sections, file_alignment) + 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::( @@ -226,6 +247,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 +287,23 @@ 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 +313,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..243e8f5f 100644 --- a/src/pe/mod.rs +++ b/src/pe/mod.rs @@ -5,6 +5,7 @@ use alloc::vec::Vec; +pub mod options; pub mod characteristic; pub mod data_directories; pub mod debug; @@ -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 @@ -92,10 +98,10 @@ 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) + 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 +110,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 +143,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 +158,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..064e068e --- /dev/null +++ b/src/pe/options.rs @@ -0,0 +1,15 @@ +/// 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, + } + } +} \ No newline at end of file diff --git a/src/pe/utils.rs b/src/pe/utils.rs index 730d74d0..3a614f58 100644 --- a/src/pe/utils.rs +++ b/src/pe/utils.rs @@ -3,6 +3,7 @@ use alloc::string::ToString; use scroll::Pread; use super::section_table; +use super::options; use crate::pe::data_directories::DataDirectory; use core::cmp; @@ -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,24 @@ 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)