From 4a432f40f36b5a284b2603ab572b130e6e7203f1 Mon Sep 17 00:00:00 2001 From: Kento Oki Date: Fri, 12 Apr 2024 11:22:24 +0900 Subject: [PATCH 01/22] PE: Support multiple debug directories and VCFeature metadata --- src/pe/debug.rs | 176 ++++++++++++++++++++++++++++++++++-------------- src/pe/mod.rs | 42 +++++------- tests/te.rs | 6 +- 3 files changed, 146 insertions(+), 78 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index cae77b4b..a6fcca75 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -6,10 +6,11 @@ use crate::pe::options; use crate::pe::section_table; use crate::pe::utils; -#[derive(Debug, PartialEq, Copy, Clone, Default)] +#[derive(Debug, PartialEq, Clone, Default)] pub struct DebugData<'a> { - pub image_debug_directory: ImageDebugDirectory, + pub image_debug_directories: Vec, pub codeview_pdb70_debug_info: Option>, + pub vcfeature_info: Option, } impl<'a> DebugData<'a> { @@ -35,14 +36,16 @@ impl<'a> DebugData<'a> { file_alignment: u32, opts: &options::ParseOptions, ) -> error::Result { - let image_debug_directory = + let image_debug_directories = ImageDebugDirectory::parse_with_opts(bytes, dd, sections, file_alignment, opts)?; let codeview_pdb70_debug_info = - CodeviewPDB70DebugInfo::parse_with_opts(bytes, &image_debug_directory, opts)?; + CodeviewPDB70DebugInfo::parse_with_opts(bytes, &image_debug_directories, opts)?; + let vcfeature_info = VCFeatureInfo::parse_with_opts(bytes, &image_debug_directories, opts)?; Ok(DebugData { - image_debug_directory, + image_debug_directories, codeview_pdb70_debug_info, + vcfeature_info, }) } @@ -50,6 +53,13 @@ impl<'a> DebugData<'a> { pub fn guid(&self) -> Option<[u8; 16]> { self.codeview_pdb70_debug_info.map(|pdb70| pdb70.signature) } + + /// Find a specific debug type in the debug data. + pub fn find_type(&self, data_type: u32) -> Option<&ImageDebugDirectory> { + self.image_debug_directories + .iter() + .find(|idd| idd.data_type == data_type) + } } // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680307(v=vs.85).aspx @@ -74,6 +84,7 @@ pub const IMAGE_DEBUG_TYPE_MISC: u32 = 4; pub const IMAGE_DEBUG_TYPE_EXCEPTION: u32 = 5; pub const IMAGE_DEBUG_TYPE_FIXUP: u32 = 6; pub const IMAGE_DEBUG_TYPE_BORLAND: u32 = 9; +pub const IMAGE_DEBUG_TYPE_VC_FEATURE: u32 = 12; impl ImageDebugDirectory { #[allow(unused)] @@ -82,7 +93,7 @@ impl ImageDebugDirectory { dd: data_directories::DataDirectory, sections: &[section_table::SectionTable], file_alignment: u32, - ) -> error::Result { + ) -> error::Result> { Self::parse_with_opts( bytes, dd, @@ -98,16 +109,22 @@ impl ImageDebugDirectory { sections: &[section_table::SectionTable], file_alignment: u32, opts: &options::ParseOptions, - ) -> error::Result { + ) -> error::Result> { let rva = dd.virtual_address as usize; + let entries = dd.size as usize / std::mem::size_of::(); 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 )) })?; - let idd: Self = bytes.pread_with(offset, scroll::LE)?; - Ok(idd) + let idd_list = (0..entries) + .map(|i| { + let entry = offset + i * std::mem::size_of::(); + bytes.pread_with(entry, scroll::LE) + }) + .collect::, scroll::Error>>()?; + Ok(idd_list) } } @@ -127,60 +144,119 @@ pub struct CodeviewPDB70DebugInfo<'a> { } impl<'a> CodeviewPDB70DebugInfo<'a> { - pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result> { + pub fn parse(bytes: &'a [u8], idd: &Vec) -> error::Result> { Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) } pub fn parse_with_opts( bytes: &'a [u8], - idd: &ImageDebugDirectory, + idd: &Vec, 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 - return Ok(None); - } + let idd = idd + .iter() + .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_CODEVIEW); - // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly - 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; - if filename_length < 0 { - // the record is too short to be plausible - return Err(error::Error::Malformed(format!( - "ImageDebugDirectory size of data seems wrong: {:?}", - idd.size_of_data - ))); - } - let filename_length = filename_length as usize; + if let Some(idd) = idd { + // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; - // check the codeview signature - let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if codeview_signature != CODEVIEW_PDB70_MAGIC { - return Ok(None); + // 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; + if filename_length < 0 { + // the record is too short to be plausible + return Err(error::Error::Malformed(format!( + "ImageDebugDirectory size of data seems wrong: {:?}", + idd.size_of_data + ))); + } + let filename_length = filename_length as usize; + + // check the codeview signature + let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if codeview_signature != CODEVIEW_PDB70_MAGIC { + return Ok(None); + } + + // read the rest + let mut signature: [u8; 16] = [0; 16]; + signature.copy_from_slice(bytes.gread_with(&mut offset, 16)?); + let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if let Some(filename) = bytes.get(offset..offset + filename_length) { + Ok(Some(CodeviewPDB70DebugInfo { + codeview_signature, + signature, + age, + filename, + })) + } else { + Err(error::Error::Malformed(format!( + "ImageDebugDirectory seems corrupted: {:?}", + idd + ))) + } + } else { + // CodeView debug info not found + Ok(None) } + } +} - // read the rest - let mut signature: [u8; 16] = [0; 16]; - signature.copy_from_slice(bytes.gread_with(&mut offset, 16)?); - let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if let Some(filename) = bytes.get(offset..offset + filename_length) { - Ok(Some(CodeviewPDB70DebugInfo { - codeview_signature, - signature, - age, - filename, +/// Represents the `IMAGE_DEBUG_VC_FEATURE_ENTRY` structure +#[repr(C)] +#[derive(Debug, PartialEq, Copy, Clone, Default)] +pub struct VCFeatureInfo { + /// The count of pre-VC++ + pub pre_vc_plusplus_count: u32, + /// The count of C and C++ + pub c_and_cplusplus_count: u32, + /// The count of guard stack + pub guard_stack_count: u32, + /// The count of SDL + pub sdl_count: u32, + /// The count of guard + pub guard_count: u32, +} + +impl<'a> VCFeatureInfo { + pub fn parse(bytes: &'a [u8], idd: &Vec) -> error::Result> { + Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) + } + + pub fn parse_with_opts( + bytes: &'a [u8], + idd: &Vec, + opts: &options::ParseOptions, + ) -> error::Result> { + let idd = idd + .iter() + .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_VC_FEATURE); + + if let Some(idd) = idd { + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; + + let pre_vc_plusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let c_and_cplusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let guard_stack_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let sdl_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let guard_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + + Ok(Some(VCFeatureInfo { + pre_vc_plusplus_count, + c_and_cplusplus_count, + guard_stack_count, + sdl_count, + guard_count, })) } else { - Err(error::Error::Malformed(format!( - "ImageDebugDirectory seems corrupted: {:?}", - idd - ))) + // VC Feature info not found + return Ok(None); } } } diff --git a/src/pe/mod.rs b/src/pe/mod.rs index 1f2bac7a..1bfa7c11 100644 --- a/src/pe/mod.rs +++ b/src/pe/mod.rs @@ -509,7 +509,7 @@ impl<'a> TE<'a> { // Parse the debug data. Must adjust offsets before parsing the image_debug_directory let mut debug_data = debug::DebugData::default(); - debug_data.image_debug_directory = debug::ImageDebugDirectory::parse_with_opts( + debug_data.image_debug_directories = debug::ImageDebugDirectory::parse_with_opts( bytes, header.debug_dir, §ions, @@ -519,7 +519,7 @@ impl<'a> TE<'a> { TE::fixup_debug_data(&mut debug_data, rva_offset as u32); debug_data.codeview_pdb70_debug_info = debug::CodeviewPDB70DebugInfo::parse_with_opts( bytes, - &debug_data.image_debug_directory, + &debug_data.image_debug_directories, opts, )?; @@ -533,29 +533,21 @@ impl<'a> TE<'a> { /// Adjust all addresses in the TE binary debug data. fn fixup_debug_data(dd: &mut debug::DebugData, rva_offset: u32) { - debug!( - "ImageDebugDirectory address of raw data fixed up from: 0x{:X} to 0x{:X}", - dd.image_debug_directory.address_of_raw_data, - dd.image_debug_directory - .address_of_raw_data - .wrapping_sub(rva_offset), - ); - dd.image_debug_directory.address_of_raw_data = dd - .image_debug_directory - .address_of_raw_data - .wrapping_sub(rva_offset); - - debug!( - "ImageDebugDirectory pointer to raw data fixed up from: 0x{:X} to 0x{:X}", - dd.image_debug_directory.pointer_to_raw_data, - dd.image_debug_directory - .pointer_to_raw_data - .wrapping_sub(rva_offset), - ); - dd.image_debug_directory.pointer_to_raw_data = dd - .image_debug_directory - .pointer_to_raw_data - .wrapping_sub(rva_offset); + dd.image_debug_directories.iter_mut().for_each(|idd| { + debug!( + "ImageDebugDirectory address of raw data fixed up from: 0x{:X} to 0x{:X}", + idd.address_of_raw_data, + idd.address_of_raw_data.wrapping_sub(rva_offset), + ); + idd.address_of_raw_data = idd.address_of_raw_data.wrapping_sub(rva_offset); + + debug!( + "ImageDebugDirectory pointer to raw data fixed up from: 0x{:X} to 0x{:X}", + idd.pointer_to_raw_data, + idd.pointer_to_raw_data.wrapping_sub(rva_offset), + ); + idd.pointer_to_raw_data = idd.pointer_to_raw_data.wrapping_sub(rva_offset); + }); } } diff --git a/tests/te.rs b/tests/te.rs index 347131f2..8e8ad5d0 100644 --- a/tests/te.rs +++ b/tests/te.rs @@ -54,13 +54,13 @@ mod te_tests { assert_eq!(te.sections[4].pointer_to_relocations, 0); // Verify the debug directory is correct - assert_eq!(te.debug_data.image_debug_directory.size_of_data, 0xab); + assert_eq!(te.debug_data.image_debug_directories[0].size_of_data, 0xab); assert_eq!( - te.debug_data.image_debug_directory.address_of_raw_data, + te.debug_data.image_debug_directories[0].address_of_raw_data, 0x3b54 ); assert_eq!( - te.debug_data.image_debug_directory.pointer_to_raw_data, + te.debug_data.image_debug_directories[0].pointer_to_raw_data, 0x3b54 ); let debug_info = te.debug_data.codeview_pdb70_debug_info.unwrap(); From e0b325f62b19ffc40d75dd198fdcf089173aa621 Mon Sep 17 00:00:00 2001 From: Kento Oki Date: Fri, 12 Apr 2024 11:34:59 +0900 Subject: [PATCH 02/22] Import `alloc::vec::Vec;` --- src/pe/debug.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index a6fcca75..362a341d 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -1,4 +1,5 @@ use crate::error; +use alloc::vec::Vec; use scroll::{Pread, Pwrite, SizeWith}; use crate::pe::data_directories; From f817a0f009b6e5ea565846fb35dc6bfe1cae3b1f Mon Sep 17 00:00:00 2001 From: Kento Oki Date: Fri, 12 Apr 2024 11:38:42 +0900 Subject: [PATCH 03/22] Use `core::mem` instead of `std::mem` --- src/pe/debug.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 362a341d..ebf05d19 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -112,7 +112,7 @@ impl ImageDebugDirectory { opts: &options::ParseOptions, ) -> error::Result> { let rva = dd.virtual_address as usize; - let entries = dd.size as usize / std::mem::size_of::(); + let entries = dd.size as usize / core::mem::size_of::(); let offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| { error::Error::Malformed(format!( "Cannot map ImageDebugDirectory rva {:#x} into offset", @@ -121,7 +121,7 @@ impl ImageDebugDirectory { })?; let idd_list = (0..entries) .map(|i| { - let entry = offset + i * std::mem::size_of::(); + let entry = offset + i * core::mem::size_of::(); bytes.pread_with(entry, scroll::LE) }) .collect::, scroll::Error>>()?; From 22e1a3ac6b0197838d55e96c609936e1001cc7fd Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 02:05:18 +0900 Subject: [PATCH 04/22] Refactor `DebugData` with TE compatibility --- src/pe/debug.rs | 310 ++++++++++++++++++++++++++++++++++++++---------- src/pe/mod.rs | 29 +---- tests/te.rs | 18 +-- 3 files changed, 261 insertions(+), 96 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 573f53e7..3bf0a542 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -1,5 +1,8 @@ +use core::iter::FusedIterator; + use crate::error; use alloc::vec::Vec; +use log::debug; use scroll::{Pread, Pwrite, SizeWith}; use crate::pe::data_directories; @@ -7,11 +10,96 @@ use crate::pe::options; use crate::pe::section_table; use crate::pe::utils; +/// Size of [`ImageDebugDirectory`] +pub const IMAGE_DEBUG_DIRECTORY_SIZE: usize = 0x1C; + +/// Iterator over debug directory entries in [`DebugData`]. +#[derive(Debug, Copy, Clone)] +pub struct ImageDebugDirectoryIterator<'a> { + /// Raw data reference that scoped to the next element if appropriate + data: &'a [u8], + /// Fixup RVA offset used for TE fixups + /// + /// - **When zero**: no fixup is performed + /// - **When non-zero**: fixup is performed + rva_offset: u32, +} + +impl Iterator for ImageDebugDirectoryIterator<'_> { + type Item = error::Result; + + fn next(&mut self) -> Option { + if self.data.is_empty() { + return None; + } + + Some( + match self.data.pread_with::(0, scroll::LE) { + Ok(func) => { + self.data = &self.data[IMAGE_DEBUG_DIRECTORY_SIZE..]; + + // Adjust all addresses in the TE binary debug data if fixup is specified + let idd = ImageDebugDirectory { + address_of_raw_data: func.address_of_raw_data.wrapping_sub(self.rva_offset), + pointer_to_raw_data: func.pointer_to_raw_data.wrapping_sub(self.rva_offset), + ..func + }; + + debug!( + "ImageDebugDirectory address of raw data fixed up from: 0x{:X} to 0x{:X}", + idd.address_of_raw_data.wrapping_add(self.rva_offset), + idd.address_of_raw_data, + ); + + debug!( + "ImageDebugDirectory pointer to raw data fixed up from: 0x{:X} to 0x{:X}", + idd.pointer_to_raw_data.wrapping_add(self.rva_offset), + idd.pointer_to_raw_data, + ); + + Ok(idd) + } + Err(error) => { + self.data = &[]; + Err(error.into()) + } + }, + ) + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.data.len() / IMAGE_DEBUG_DIRECTORY_SIZE; + (len, Some(len)) + } +} + +impl FusedIterator for ImageDebugDirectoryIterator<'_> {} +impl ExactSizeIterator for ImageDebugDirectoryIterator<'_> {} + +/// Represents debug data extracted from a PE (Portable Executable) or TE (Terse Executable) file. #[derive(Debug, PartialEq, Clone, Default)] pub struct DebugData<'a> { - pub image_debug_directories: Vec, + /// Raw data covering bytes of entire [`ImageDebugDirectory`] + pub data: &'a [u8], + /// Fixup RVA offset used for TE fixups + /// + /// - **When zero**: no fixup is performed + /// - **When non-zero**: fixup is performed + pub rva_offset: u32, + /// Parsed CodeView PDB 7.0 (RSDS) debug information, if available. + /// + /// CodeView PDB 7.0 contains a GUID, an age value, and the path to the PDB file. + /// This is commonly used in modern PDB files. pub codeview_pdb70_debug_info: Option>, + /// Parsed CodeView PDB 2.0 (NB10) debug information, if available. + /// + /// CodeView PDB 2.0 includes a signature, an age value, and the path to the PDB file. + /// It is used in older PDB formats. pub codeview_pdb20_debug_info: Option>, + /// Visual C++ feature data, if available. + /// + /// This includes information about specific features or optimizations enabled + /// in Visual C++ builds. pub vcfeature_info: Option, } @@ -38,16 +126,38 @@ impl<'a> DebugData<'a> { file_alignment: u32, opts: &options::ParseOptions, ) -> error::Result { - let image_debug_directories = - ImageDebugDirectory::parse_with_opts(bytes, dd, sections, file_alignment, opts)?; + Self::parse_with_opts_and_fixup(bytes, dd, sections, file_alignment, opts, 0) + } + + pub fn parse_with_opts_and_fixup( + bytes: &'a [u8], + dd: data_directories::DataDirectory, + sections: &[section_table::SectionTable], + file_alignment: u32, + opts: &options::ParseOptions, + rva_offset: u32, + ) -> error::Result { + let offset = + utils::find_offset(dd.virtual_address as usize, sections, file_alignment, opts) + .ok_or_else(|| { + error::Error::Malformed(format!( + "Cannot map ImageDebugDirectory rva {:#x} into offset", + dd.virtual_address + )) + })?; + + let data = &bytes[offset..offset + dd.size as usize]; + let iterator = ImageDebugDirectoryIterator { data, rva_offset }; + let codeview_pdb70_debug_info = - CodeviewPDB70DebugInfo::parse_with_opts(bytes, &image_debug_directory, opts)?; + CodeviewPDB70DebugInfo::parse_with_opts(bytes, iterator, opts)?; let codeview_pdb20_debug_info = - CodeviewPDB20DebugInfo::parse_with_opts(bytes, &image_debug_directory, opts)?; - let vcfeature_info = VCFeatureInfo::parse_with_opts(bytes, &image_debug_directories, opts)?; + CodeviewPDB20DebugInfo::parse_with_opts(bytes, iterator, opts)?; + let vcfeature_info = VCFeatureInfo::parse_with_opts(bytes, iterator, opts)?; Ok(DebugData { - image_debug_directories, + data, + rva_offset, codeview_pdb70_debug_info, codeview_pdb20_debug_info, vcfeature_info, @@ -60,36 +170,97 @@ impl<'a> DebugData<'a> { } /// Find a specific debug type in the debug data. - pub fn find_type(&self, data_type: u32) -> Option<&ImageDebugDirectory> { - self.image_debug_directories - .iter() + pub fn find_type(&self, data_type: u32) -> Option { + self.entries() + .filter_map(Result::ok) .find(|idd| idd.data_type == data_type) } + + /// Returns iterator for [`ImageDebugDirectory`] + pub fn entries(&self) -> ImageDebugDirectoryIterator<'a> { + ImageDebugDirectoryIterator { + data: &self.data, + rva_offset: self.rva_offset, + } + } } -// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680307(v=vs.85).aspx +/// Represents the IMAGE_DEBUG_DIRECTORY structure in a Portable Executable (PE) file. +/// +/// This structure holds information about the debug data in a PE file. It is used +/// to locate debug information such as PDB files or other types of debugging data. +/// The fields correspond to the Windows-specific IMAGE_DEBUG_DIRECTORY structure. +/// +/// For more details, see the [Microsoft documentation](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#image-debug_directory). +/// +/// #[repr(C)] #[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)] pub struct ImageDebugDirectory { + /// The characteristics of the debug data, reserved for future use. pub characteristics: u32, + /// The time and date when the debug data was created, represented as a Unix timestamp. pub time_date_stamp: u32, + /// The major version number of the debug data format. pub major_version: u16, + /// The minor version number of the debug data format. pub minor_version: u16, + /// The type of debug data, such as codeview or coff. pub data_type: u32, + /// The size of the debug data in bytes. pub size_of_data: u32, + /// The address of the debug data when loaded into memory. pub address_of_raw_data: u32, + /// The file pointer to the debug data within the PE file. pub pointer_to_raw_data: u32, } +/// Represents an unknown debug data type. pub const IMAGE_DEBUG_TYPE_UNKNOWN: u32 = 0; +/// Represents COFF (Common Object File Format) debug information. pub const IMAGE_DEBUG_TYPE_COFF: u32 = 1; +/// Represents CodeView debug information, often used for PDB (Program Database) files. pub const IMAGE_DEBUG_TYPE_CODEVIEW: u32 = 2; +/// Represents FPO (Frame Pointer Omission) information. pub const IMAGE_DEBUG_TYPE_FPO: u32 = 3; +/// Represents miscellaneous debug information. pub const IMAGE_DEBUG_TYPE_MISC: u32 = 4; +/// Represents exception handling information. pub const IMAGE_DEBUG_TYPE_EXCEPTION: u32 = 5; +/// Represents fixup information, used for relocation. pub const IMAGE_DEBUG_TYPE_FIXUP: u32 = 6; +/// Represents OMAP (Optimized Map) information from source to compiled addresses. +pub const IMAGE_DEBUG_TYPE_OMAP_TO_SRC: u32 = 7; +/// Represents OMAP information from compiled addresses to source. +pub const IMAGE_DEBUG_TYPE_OMAP_FROM_SRC: u32 = 8; +/// Represents Borland-specific debug information. pub const IMAGE_DEBUG_TYPE_BORLAND: u32 = 9; +/// Reserved debug data type (value 10). +pub const IMAGE_DEBUG_TYPE_RESERVED10: u32 = 10; +/// Represents BBT (Basic Block Transfer) information, an alias for reserved type 10. +pub const IMAGE_DEBUG_TYPE_BBT: u32 = IMAGE_DEBUG_TYPE_RESERVED10; +/// Represents a CLSID (Class ID) associated with the debug data. +pub const IMAGE_DEBUG_TYPE_CLSID: u32 = 11; +/// Represents Visual C++ feature data. pub const IMAGE_DEBUG_TYPE_VC_FEATURE: u32 = 12; +/// Represents POGO (Profile Guided Optimization) information. +pub const IMAGE_DEBUG_TYPE_POGO: u32 = 13; +/// Represents ILTCG (Intermediate Language to Code Generation) optimization data. +pub const IMAGE_DEBUG_TYPE_ILTCG: u32 = 14; +/// Represents MPX (Memory Protection Extensions) related debug information. +pub const IMAGE_DEBUG_TYPE_MPX: u32 = 15; +/// Represents repro information, typically used for reproducible builds. +pub const IMAGE_DEBUG_TYPE_REPRO: u32 = 16; +/// Represents an embedded Portable PDB, a .NET-specific debug information format. +pub const IMAGE_DEBUG_TYPE_EMBEDDEDPORTABLEPDB: u32 = 17; +/// Represents SPGO (Static Profile Guided Optimization) information. +pub const IMAGE_DEBUG_TYPE_SPGO: u32 = 18; +/// Represents a checksum for the PDB file. +pub const IMAGE_DEBUG_TYPE_PDBCHECKSUM: u32 = 19; +/// Represents extended DLL characteristics for debugging. +pub const IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS: u32 = 20; +/// Represents a performance map for profiling. +pub const IMAGE_DEBUG_TYPE_PERFMAP: u32 = 21; impl ImageDebugDirectory { #[allow(unused)] @@ -133,9 +304,13 @@ impl ImageDebugDirectory { } } +/// Magic number for CodeView PDB 7.0 signature (`'SDSR'`). pub const CODEVIEW_PDB70_MAGIC: u32 = 0x5344_5352; +/// Magic number for CodeView PDB 2.0 signature (`'01BN'`). pub const CODEVIEW_PDB20_MAGIC: u32 = 0x3031_424e; +/// Magic number for CodeView CV 5.0 signature (`'11BN'`). pub const CODEVIEW_CV50_MAGIC: u32 = 0x3131_424e; +/// Magic number for CodeView CV 4.1 signature (`'90BN'`). pub const CODEVIEW_CV41_MAGIC: u32 = 0x3930_424e; // http://llvm.org/doxygen/CVDebugRecord_8h_source.html @@ -149,15 +324,19 @@ pub struct CodeviewPDB70DebugInfo<'a> { } impl<'a> CodeviewPDB70DebugInfo<'a> { - pub fn parse(bytes: &'a [u8], idd: &Vec) -> error::Result> { + pub fn parse( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + ) -> error::Result> { Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) } pub fn parse_with_opts( bytes: &'a [u8], - idd: &Vec, + idd: ImageDebugDirectoryIterator<'_>, opts: &options::ParseOptions, ) -> error::Result> { + let idd = idd.collect::, _>>()?; let idd = idd .iter() .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_CODEVIEW); @@ -227,15 +406,19 @@ pub struct VCFeatureInfo { } impl<'a> VCFeatureInfo { - pub fn parse(bytes: &'a [u8], idd: &Vec) -> error::Result> { + pub fn parse( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + ) -> error::Result> { Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) } pub fn parse_with_opts( bytes: &'a [u8], - idd: &Vec, + idd: ImageDebugDirectoryIterator<'_>, opts: &options::ParseOptions, ) -> error::Result> { + let idd = idd.collect::, _>>()?; let idd = idd .iter() .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_VC_FEATURE); @@ -278,61 +461,68 @@ pub struct CodeviewPDB20DebugInfo<'a> { } impl<'a> CodeviewPDB20DebugInfo<'a> { - pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result> { + pub fn parse( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + ) -> error::Result> { Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) } pub fn parse_with_opts( bytes: &'a [u8], - idd: &ImageDebugDirectory, + idd: ImageDebugDirectoryIterator<'_>, 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 CodeviewPDB20DebugInfo either - return Ok(None); - } + let idd = idd.collect::, _>>()?; + let idd = idd + .iter() + .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_VC_FEATURE); - // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly - 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 - 16; - if filename_length < 0 { - // the record is too short to be plausible - return Err(error::Error::Malformed(format!( - "ImageDebugDirectory size of data seems wrong: {:?}", - idd.size_of_data - ))); - } - let filename_length = filename_length as usize; + if let Some(idd) = idd { + // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; - // check the codeview signature - let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if codeview_signature != CODEVIEW_PDB20_MAGIC { - return Ok(None); - } - let codeview_offset: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - - // read the rest - let signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if let Some(filename) = bytes.get(offset..offset + filename_length) { - Ok(Some(CodeviewPDB20DebugInfo { - codeview_signature, - codeview_offset, - signature, - age, - filename, - })) + // 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 - 16; + if filename_length < 0 { + // the record is too short to be plausible + return Err(error::Error::Malformed(format!( + "ImageDebugDirectory size of data seems wrong: {:?}", + idd.size_of_data + ))); + } + let filename_length = filename_length as usize; + + // check the codeview signature + let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if codeview_signature != CODEVIEW_PDB20_MAGIC { + return Ok(None); + } + let codeview_offset: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + + // read the rest + let signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if let Some(filename) = bytes.get(offset..offset + filename_length) { + Ok(Some(CodeviewPDB20DebugInfo { + codeview_signature, + codeview_offset, + signature, + age, + filename, + })) + } else { + Err(error::Error::Malformed(format!( + "ImageDebugDirectory seems corrupted: {:?}", + idd + ))) + } } else { - Err(error::Error::Malformed(format!( - "ImageDebugDirectory seems corrupted: {:?}", - idd - ))) + // Codeview20 not found + return Ok(None); } } } diff --git a/src/pe/mod.rs b/src/pe/mod.rs index 377dd57e..d6bd25db 100644 --- a/src/pe/mod.rs +++ b/src/pe/mod.rs @@ -536,19 +536,13 @@ impl<'a> TE<'a> { let sections = header.sections(bytes, &mut offset)?; // Parse the debug data. Must adjust offsets before parsing the image_debug_directory - let mut debug_data = debug::DebugData::default(); - debug_data.image_debug_directories = debug::ImageDebugDirectory::parse_with_opts( + let debug_data = debug::DebugData::parse_with_opts_and_fixup( bytes, header.debug_dir, §ions, 0, opts, - )?; - TE::fixup_debug_data(&mut debug_data, rva_offset as u32); - debug_data.codeview_pdb70_debug_info = debug::CodeviewPDB70DebugInfo::parse_with_opts( - bytes, - &debug_data.image_debug_directories, - opts, + rva_offset as u32, )?; Ok(TE { @@ -558,25 +552,6 @@ impl<'a> TE<'a> { rva_offset, }) } - - /// Adjust all addresses in the TE binary debug data. - fn fixup_debug_data(dd: &mut debug::DebugData, rva_offset: u32) { - dd.image_debug_directories.iter_mut().for_each(|idd| { - debug!( - "ImageDebugDirectory address of raw data fixed up from: 0x{:X} to 0x{:X}", - idd.address_of_raw_data, - idd.address_of_raw_data.wrapping_sub(rva_offset), - ); - idd.address_of_raw_data = idd.address_of_raw_data.wrapping_sub(rva_offset); - - debug!( - "ImageDebugDirectory pointer to raw data fixed up from: 0x{:X} to 0x{:X}", - idd.pointer_to_raw_data, - idd.pointer_to_raw_data.wrapping_sub(rva_offset), - ); - idd.pointer_to_raw_data = idd.pointer_to_raw_data.wrapping_sub(rva_offset); - }); - } } /// An analyzed COFF object diff --git a/tests/te.rs b/tests/te.rs index 8e8ad5d0..5bc397d9 100644 --- a/tests/te.rs +++ b/tests/te.rs @@ -53,16 +53,16 @@ mod te_tests { assert_eq!(te.sections[4].pointer_to_raw_data, 0x5e60); assert_eq!(te.sections[4].pointer_to_relocations, 0); + let debug_directories = te + .debug_data + .entries() + .collect::, _>>() + .unwrap(); + // Verify the debug directory is correct - assert_eq!(te.debug_data.image_debug_directories[0].size_of_data, 0xab); - assert_eq!( - te.debug_data.image_debug_directories[0].address_of_raw_data, - 0x3b54 - ); - assert_eq!( - te.debug_data.image_debug_directories[0].pointer_to_raw_data, - 0x3b54 - ); + assert_eq!(debug_directories[0].size_of_data, 0xab); + assert_eq!(debug_directories[0].address_of_raw_data, 0x3b54); + assert_eq!(debug_directories[0].pointer_to_raw_data, 0x3b54); let debug_info = te.debug_data.codeview_pdb70_debug_info.unwrap(); assert_eq!( debug_info.signature, From 04dad2d6bf10b60dc6b21989852b0a5bfd8be681 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 03:19:14 +0900 Subject: [PATCH 05/22] Add `ReproInfo` and `ExDllCharacteristicsInfo` parsers - Added decent docs for `pe::debug` structs and constants - Added integration tests for `pe::debug::DebugData` --- src/pe/debug.rs | 332 +++++++++++++++++- .../pe/debug_directories-clang_lld.exe.bin | Bin 0 -> 2048 bytes tests/bins/pe/debug_directories-msvc.exe.bin | Bin 0 -> 1536 bytes 3 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 tests/bins/pe/debug_directories-clang_lld.exe.bin create mode 100644 tests/bins/pe/debug_directories-msvc.exe.bin diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 3bf0a542..a1571153 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -90,17 +90,38 @@ pub struct DebugData<'a> { /// /// CodeView PDB 7.0 contains a GUID, an age value, and the path to the PDB file. /// This is commonly used in modern PDB files. + /// + /// [`IMAGE_DEBUG_TYPE_CODEVIEW`] pub codeview_pdb70_debug_info: Option>, /// Parsed CodeView PDB 2.0 (NB10) debug information, if available. /// /// CodeView PDB 2.0 includes a signature, an age value, and the path to the PDB file. /// It is used in older PDB formats. + /// + /// [`IMAGE_DEBUG_TYPE_CODEVIEW`] pub codeview_pdb20_debug_info: Option>, /// Visual C++ feature data, if available. /// /// This includes information about specific features or optimizations enabled /// in Visual C++ builds. + /// + /// [`IMAGE_DEBUG_TYPE_VC_FEATURE`] pub vcfeature_info: Option, + /// Extended DLL characteristics information, if available. + /// + /// This data includes extended properties of the DLL that may affect + /// how the operating system handles the DLL, such as security features. + /// + /// [`IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS`] + pub ex_dll_characteristics_info: Option, + /// Reproducible build (Repro) information, if available. + /// + /// - **MSVC builds**: Contains a 32-byte hash stored directly in the raw data. + /// - **Clang builds**: Uses the `ImageDebugDirectory::time_date_stamp` as a hash, + /// with no dedicated raw data. + /// + /// [`IMAGE_DEBUG_TYPE_REPRO`] + pub repro_info: Option>, } impl<'a> DebugData<'a> { @@ -154,6 +175,9 @@ impl<'a> DebugData<'a> { let codeview_pdb20_debug_info = CodeviewPDB20DebugInfo::parse_with_opts(bytes, iterator, opts)?; let vcfeature_info = VCFeatureInfo::parse_with_opts(bytes, iterator, opts)?; + let ex_dll_characteristics_info = + ExDllCharacteristicsInfo::parse_with_opts(bytes, iterator, opts)?; + let repro_info = ReproInfo::parse_with_opts(bytes, iterator, opts)?; Ok(DebugData { data, @@ -161,6 +185,8 @@ impl<'a> DebugData<'a> { codeview_pdb70_debug_info, codeview_pdb20_debug_info, vcfeature_info, + ex_dll_characteristics_info, + repro_info, }) } @@ -476,7 +502,7 @@ impl<'a> CodeviewPDB20DebugInfo<'a> { let idd = idd.collect::, _>>()?; let idd = idd .iter() - .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_VC_FEATURE); + .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_CODEVIEW); if let Some(idd) = idd { // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly @@ -526,3 +552,307 @@ impl<'a> CodeviewPDB20DebugInfo<'a> { } } } + +/// Represents the reproducible build (Repro) information extracted from a PE (Portable Executable) file. +/// +/// The Repro information differs based on the compiler used to build the executable: +/// - For MSVC (Microsoft Visual C++), the Repro information is written directly into the raw data as a 32-byte hash. +/// - For Clang/(correctly, LLD linker), there is no dedicated raw data for the Repro information. Instead, the `ImageDebugDirectory::time_date_stamp` +/// field functions as a hash, providing a unique identifier for the reproducible build. +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ReproInfo<'a> { + /// Represents a hash stored in the `time_date_stamp` field. + /// + /// This variant is used primarily for executables built with Clang/LLD, where the + /// `time_date_stamp` acts as the Repro hash. + TimeDateStamp(u32), + /// Represents a buffer containing the 32-byte Repro hash. + /// + /// This variant is used for MSVC-built executables, where the Repro hash is directly + /// stored as raw data in the debug directory. + Buffer { + /// The length of the buffer containing the Repro data. For MSVC, this is typically 32 bytes long. + length: u32, + /// A reference to the buffer containing the Repro hash data. + buffer: &'a [u8], + }, +} + +impl<'a> ReproInfo<'a> { + pub fn parse( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + ) -> error::Result> { + Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) + } + + pub fn parse_with_opts( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + opts: &options::ParseOptions, + ) -> error::Result> { + let idd = idd.collect::, _>>()?; + let idd = idd + .iter() + .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_REPRO); + + if let Some(idd) = idd { + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; + + // Clang(LLD) produces no data = uses timestamp field instead + // MSVC(lin.exe) produces 36-byte data + if idd.size_of_data > 0 { + let length: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if let Some(buffer) = bytes.get(offset..offset + length as usize) { + Ok(Some(Self::Buffer { length, buffer })) + } else { + Err(error::Error::Malformed(format!( + "ImageDebugDirectory seems corrupted: {:?}", + idd + ))) + } + } else { + Ok(Some(Self::TimeDateStamp(idd.time_date_stamp))) + } + } else { + return Ok(None); + } + } +} + +/// Represents extended DLL characteristics information. +/// +/// This structure holds additional characteristics of a DLL that may influence +/// how the operating system loads or manages the DLL, especially in terms of +/// security features and optimizations. These characteristics can include +/// settings related to ASLR (Address Space Layout Randomization), DEP (Data Execution Prevention), +/// and other security-relevant attributes. +#[repr(C)] +#[derive(Debug, PartialEq, Copy, Clone, Default)] +pub struct ExDllCharacteristicsInfo { + /// The extended characteristics of the DLL. + /// + /// This field is a bitmask of flags that define various security and performance + /// properties of the DLL. The specific flags are defined by the PE format specification. + /// + /// This field contains one or more bitflags of: + /// + /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT`] + /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE`] + /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE`] + /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY`] + /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_1`] + /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_2`] + pub characteristics_ex: u32, +} + +/// Enables Compatibility Enforcement Technology (CET) for the DLL to enhance +/// security via control-flow integrity. +pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT: u32 = 0x1; +/// Enforces CET in strict mode, increasing security measures against +/// control-flow attacks but may impact compatibility. +pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE: u32 = 0x2; +/// Allows relaxed mode for Context IP Validation under CET, providing a balance +/// between security and performance. +pub const IMAGE_DLLCHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE: u32 = 0x4; +/// Restricts the use of dynamic APIs to processes only, enhancing security by +/// limiting external API calls under CET. +pub const IMAGE_DLLCHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY: u32 = 0x8; +/// Reserved for future use related to CET features. Not currently utilized. +pub const IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_1: u32 = 0x10; +/// Reserved for future use related to CET features. Not currently utilized. +pub const IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_2: u32 = 0x20; + +impl<'a> ExDllCharacteristicsInfo { + pub fn parse( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + ) -> error::Result> { + Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) + } + + pub fn parse_with_opts( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + opts: &options::ParseOptions, + ) -> error::Result> { + let idd = idd.collect::, _>>()?; + let idd = idd + .iter() + .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS); + + if let Some(idd) = idd { + // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; + + let characteristics_ex: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + + Ok(Some(ExDllCharacteristicsInfo { characteristics_ex })) + } else { + return Ok(None); + } + } +} + +#[cfg(test)] +mod tests { + + use super::{ + ExDllCharacteristicsInfo, ImageDebugDirectory, ReproInfo, VCFeatureInfo, + IMAGE_DEBUG_TYPE_CODEVIEW, IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, IMAGE_DEBUG_TYPE_ILTCG, + IMAGE_DEBUG_TYPE_POGO, IMAGE_DEBUG_TYPE_REPRO, IMAGE_DEBUG_TYPE_VC_FEATURE, + IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT, IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE, + }; + + const DEBUG_DIRECTORIES_TEST_MSVC_BIN: &[u8] = + include_bytes!("../../tests/bins/pe/debug_directories-msvc.exe.bin"); + const DEBUG_DIRECTORIES_TEST_CLANG_LLD_BIN: &[u8] = + include_bytes!("../../tests/bins/pe/debug_directories-clang_lld.exe.bin"); + + #[test] + fn parse_debug_entries_iterator() { + let binary = + crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_some(), true); + let debug_data = binary.debug_data.unwrap(); + let entries = debug_data.entries().collect::, _>>(); + assert_eq!(entries.is_ok(), true); + let entries = entries.unwrap(); + let entries_expect = vec![ + ImageDebugDirectory { + characteristics: 0x0, + time_date_stamp: 0x80AC7661, + major_version: 0x0, + minor_version: 0x0, + data_type: IMAGE_DEBUG_TYPE_CODEVIEW, + size_of_data: 0x38, + address_of_raw_data: 0x20c0, + pointer_to_raw_data: 0x4c0, + }, + ImageDebugDirectory { + characteristics: 0x0, + time_date_stamp: 0x80AC7661, + major_version: 0x0, + minor_version: 0x0, + data_type: IMAGE_DEBUG_TYPE_VC_FEATURE, + size_of_data: 0x14, + address_of_raw_data: 0x20f8, + pointer_to_raw_data: 0x4f8, + }, + ImageDebugDirectory { + characteristics: 0x0, + time_date_stamp: 0x80AC7661, + major_version: 0x0, + minor_version: 0x0, + data_type: IMAGE_DEBUG_TYPE_POGO, + size_of_data: 0x58, + address_of_raw_data: 0x210c, + pointer_to_raw_data: 0x50c, + }, + ImageDebugDirectory { + characteristics: 0x0, + time_date_stamp: 0x80AC7661, + major_version: 0x0, + minor_version: 0x0, + data_type: IMAGE_DEBUG_TYPE_ILTCG, + size_of_data: 0x0, + address_of_raw_data: 0x0, + pointer_to_raw_data: 0x0, + }, + ImageDebugDirectory { + characteristics: 0x0, + time_date_stamp: 0x80AC7661, + major_version: 0x0, + minor_version: 0x0, + data_type: IMAGE_DEBUG_TYPE_REPRO, + size_of_data: 0x24, + address_of_raw_data: 0x2164, + pointer_to_raw_data: 0x564, + }, + ImageDebugDirectory { + characteristics: 0x0, + time_date_stamp: 0x80AC7661, + major_version: 0x0, + minor_version: 0x0, + data_type: IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, + size_of_data: 0x4, + address_of_raw_data: 0x2188, + pointer_to_raw_data: 0x588, + }, + ]; + assert_eq!(entries, entries_expect); + } + + #[test] + fn parse_debug_vcfeature() { + let binary = + crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_some(), true); + let debug_data = binary.debug_data.unwrap(); + assert_eq!(debug_data.vcfeature_info.is_some(), true); + let vcfeature_info = debug_data.vcfeature_info.unwrap(); + let vcfeature_info_expect = VCFeatureInfo { + pre_vc_plusplus_count: 0, + c_and_cplusplus_count: 1, + guard_stack_count: 0, + sdl_count: 0, + guard_count: 0, + }; + assert_eq!(vcfeature_info, vcfeature_info_expect); + } + + #[test] + fn parse_debug_repro_msvc() { + let binary = + crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_some(), true); + let debug_data = binary.debug_data.unwrap(); + assert_eq!(debug_data.repro_info.is_some(), true); + let repro_info = debug_data.repro_info.unwrap(); + let repro_info_expect = ReproInfo::Buffer { + length: 32, + buffer: &[ + 0x1F, 0x4F, 0x58, 0x9C, 0x3C, 0xEA, 0x00, 0x83, 0x3F, 0x57, 0x00, 0xCC, 0x36, 0xA7, + 0x84, 0xDF, 0xF7, 0x7C, 0x70, 0xE0, 0xEF, 0x7A, 0xBA, 0x08, 0xD0, 0xA6, 0x8B, 0x7F, + 0x61, 0x76, 0xAC, 0x80, + ], + }; + assert_eq!(repro_info, repro_info_expect); + } + + #[test] + fn parse_debug_repro_clang_lld() { + let binary = crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_CLANG_LLD_BIN) + .expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_some(), true); + let debug_data = binary.debug_data.unwrap(); + assert_eq!(debug_data.repro_info.is_some(), true); + let repro_info = debug_data.repro_info.unwrap(); + let repro_info_expect = ReproInfo::TimeDateStamp(0xDB2F3908); + assert_eq!(repro_info, repro_info_expect); + } + + #[test] + fn parse_debug_exdllcharacteristics() { + let binary = + crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_some(), true); + let debug_data = binary.debug_data.unwrap(); + assert_eq!(debug_data.ex_dll_characteristics_info.is_some(), true); + let ex_dll_characteristics_info = debug_data.ex_dll_characteristics_info.unwrap(); + let ex_dll_characteristics_info_expect = ExDllCharacteristicsInfo { + characteristics_ex: IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT + | IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE, + }; + assert_eq!( + ex_dll_characteristics_info, + ex_dll_characteristics_info_expect + ); + } +} diff --git a/tests/bins/pe/debug_directories-clang_lld.exe.bin b/tests/bins/pe/debug_directories-clang_lld.exe.bin new file mode 100644 index 0000000000000000000000000000000000000000..7ca9332976f32100565ba5eea74e1f53a2f0c0e0 GIT binary patch literal 2048 zcmeZ`s$gJbU|?VYVr1Ze%)!B~0E+X;@8Vo{SF)%Oz zML-w?1fZCK!2#q>Fdrz#24VvNjAk$Za-g1IW=Lp+C;P9DIFmgixlt^G@FsSGOm2DOZKnyKgU~)*sCQtknz=yO&vBqOs}p`a)~ zy(lqPAvrNGFTX?~DOI7UG*2NjPr=1MSRpq*B~_1?mrDg|G^YmVeP7S?9vEgjlh^A5 zwc3!2rsD^O2-rN{FEq7#Kj|L@l6Kl3Gy$Gz=7HV7DUs z9cqaJ14DvdQA%P-A_GGYC=h@GV84PQ83{N5!^rr+VI+}J=12?ySe^nUB?}-vpa8_s zv;vdk0g8(N@eiQf4>UPmpm+oj^C$u_E06&MFnxRwE(!^g17$5xIgkR>mx87bWEaSq z4xn5IP)-8KW@rFHG!PW*5-jf@F~{Z=L$iH2!x^*XE%!lrBgDfqSQm(#JpCMlB6Z#T zgLK{foqRm~bVFQ&LxRDkFrt~|?i}I+%2Ocy;C!Z%n+H~+0LjC!JhcMI2e}8rS1HTS yDalO%@(%#T&mhFBs;W|w(t&)SFax?9zt Date: Tue, 29 Oct 2024 03:28:08 +0900 Subject: [PATCH 06/22] Remove impl `ImageDebugDirectory` --- src/pe/debug.rs | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index a1571153..ad6ef34a 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -288,48 +288,6 @@ pub const IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS: u32 = 20; /// Represents a performance map for profiling. pub const IMAGE_DEBUG_TYPE_PERFMAP: u32 = 21; -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(), - ) - } - - pub(crate) 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 entries = dd.size as usize / core::mem::size_of::(); - 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 - )) - })?; - let idd_list = (0..entries) - .map(|i| { - let entry = offset + i * core::mem::size_of::(); - bytes.pread_with(entry, scroll::LE) - }) - .collect::, scroll::Error>>()?; - Ok(idd_list) - } -} - /// Magic number for CodeView PDB 7.0 signature (`'SDSR'`). pub const CODEVIEW_PDB70_MAGIC: u32 = 0x5344_5352; /// Magic number for CodeView PDB 2.0 signature (`'01BN'`). From 9f6254f256dffe222f91bdea215c354cbc69971d Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 03:34:19 +0900 Subject: [PATCH 07/22] Fix docs --- src/pe/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index ad6ef34a..ca7eec54 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -515,7 +515,7 @@ impl<'a> CodeviewPDB20DebugInfo<'a> { /// /// The Repro information differs based on the compiler used to build the executable: /// - For MSVC (Microsoft Visual C++), the Repro information is written directly into the raw data as a 32-byte hash. -/// - For Clang/(correctly, LLD linker), there is no dedicated raw data for the Repro information. Instead, the `ImageDebugDirectory::time_date_stamp` +/// - For Clang/(correctly, LLD linker), there is no dedicated raw data for the Repro information. Instead, the [`ImageDebugDirectory::time_date_stamp`] /// field functions as a hash, providing a unique identifier for the reproducible build. #[derive(Debug, PartialEq, Copy, Clone)] pub enum ReproInfo<'a> { From 44bb522c9aca2031b60084a4405a1cb4354a633e Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 03:34:19 +0900 Subject: [PATCH 08/22] Fix doc comments --- src/pe/debug.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index ad6ef34a..6e807808 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -515,7 +515,7 @@ impl<'a> CodeviewPDB20DebugInfo<'a> { /// /// The Repro information differs based on the compiler used to build the executable: /// - For MSVC (Microsoft Visual C++), the Repro information is written directly into the raw data as a 32-byte hash. -/// - For Clang/(correctly, LLD linker), there is no dedicated raw data for the Repro information. Instead, the `ImageDebugDirectory::time_date_stamp` +/// - For Clang/(correctly, LLD linker), there is no dedicated raw data for the Repro information. Instead, the [`ImageDebugDirectory::time_date_stamp`] /// field functions as a hash, providing a unique identifier for the reproducible build. #[derive(Debug, PartialEq, Copy, Clone)] pub enum ReproInfo<'a> { @@ -560,8 +560,8 @@ impl<'a> ReproInfo<'a> { false => idd.address_of_raw_data as usize, }; - // Clang(LLD) produces no data = uses timestamp field instead - // MSVC(lin.exe) produces 36-byte data + // Clang(LLD) produces no data, uses timestamp field instead + // MSVC(link.exe) produces 32-byte data if idd.size_of_data > 0 { let length: u32 = bytes.gread_with(&mut offset, scroll::LE)?; if let Some(buffer) = bytes.get(offset..offset + length as usize) { From 7ebb03692b6b0f8a5ed722961ca46535b1ae2471 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 03:42:44 +0900 Subject: [PATCH 09/22] add more `IMAGE_DLLCHARACTERISTICS_EX_` fields --- src/pe/debug.rs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 6e807808..bf96256c 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -586,8 +586,8 @@ impl<'a> ReproInfo<'a> { /// This structure holds additional characteristics of a DLL that may influence /// how the operating system loads or manages the DLL, especially in terms of /// security features and optimizations. These characteristics can include -/// settings related to ASLR (Address Space Layout Randomization), DEP (Data Execution Prevention), -/// and other security-relevant attributes. +/// settings related to Intel CET (Control-flow Enforcement Technology) and other +/// security-relevant attributes. #[repr(C)] #[derive(Debug, PartialEq, Copy, Clone, Default)] pub struct ExDllCharacteristicsInfo { @@ -607,22 +607,35 @@ pub struct ExDllCharacteristicsInfo { pub characteristics_ex: u32, } -/// Enables Compatibility Enforcement Technology (CET) for the DLL to enhance -/// security via control-flow integrity. +/// Indicates that Compatibility Enforcement Technology (CET) is enabled for the DLL, +/// enhancing security via control-flow integrity. pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT: u32 = 0x1; -/// Enforces CET in strict mode, increasing security measures against +/// Indicates that CET is enforced in strict mode, increasing security measures against /// control-flow attacks but may impact compatibility. pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE: u32 = 0x2; -/// Allows relaxed mode for Context IP Validation under CET, providing a balance -/// between security and performance. +/// Indicates that relaxed mode for Context IP Validation under CET is allowed, +/// providing a balance between security and performance. pub const IMAGE_DLLCHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE: u32 = 0x4; -/// Restricts the use of dynamic APIs to processes only, enhancing security by -/// limiting external API calls under CET. +/// Indicates that the use of dynamic APIs is restricted to processes only, +/// enhancing security by limiting external API calls under CET. pub const IMAGE_DLLCHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY: u32 = 0x8; -/// Reserved for future use related to CET features. Not currently utilized. +/// Reserved for future. pub const IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_1: u32 = 0x10; -/// Reserved for future use related to CET features. Not currently utilized. +/// Reserved for future. pub const IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_2: u32 = 0x20; +/// Indicates that the DLL is compatible with Forward Control Flow Integrity (CFI). +/// +/// This flag signifies that the DLL is designed to support forward CFI, a security +/// feature that helps prevent certain types of control flow attacks by ensuring +/// that control flow transfers occur only to valid targets. +pub const IMAGE_DLLCHARACTERISTICS_EX_FORWARD_CFI_COMPAT: u32 = 0x40; +/// Indicates that the DLL is hotpatch-compatible. +/// +/// This flag indicates that the DLL can be modified while in use (hotpatching), +/// allowing updates or fixes to be applied without needing to restart the application +/// or service that is using the DLL. This can be useful for maintaining uptime and +/// applying critical patches in a live environment. +pub const IMAGE_DLLCHARACTERISTICS_EX_HOTPATCH_COMPATIBLE: u32 = 0x80; impl<'a> ExDllCharacteristicsInfo { pub fn parse( From f2af2f6cabab4ed5069ac97ea620e8d60a0de10f Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 03:50:57 +0900 Subject: [PATCH 10/22] Fix doc --- src/pe/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index bf96256c..e3a053fb 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -607,7 +607,7 @@ pub struct ExDllCharacteristicsInfo { pub characteristics_ex: u32, } -/// Indicates that Compatibility Enforcement Technology (CET) is enabled for the DLL, +/// Indicates that Control Flow Enforcement Technology (CET) is enabled for the DLL, /// enhancing security via control-flow integrity. pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT: u32 = 0x1; /// Indicates that CET is enforced in strict mode, increasing security measures against From 10fc0430ae5bc8fb258a2a57698586dca95b28a4 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 03:53:27 +0900 Subject: [PATCH 11/22] Format --- src/pe/debug.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index e3a053fb..21012d8f 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -673,7 +673,6 @@ impl<'a> ExDllCharacteristicsInfo { #[cfg(test)] mod tests { - use super::{ ExDllCharacteristicsInfo, ImageDebugDirectory, ReproInfo, VCFeatureInfo, IMAGE_DEBUG_TYPE_CODEVIEW, IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, IMAGE_DEBUG_TYPE_ILTCG, From b4c26729901c8fb85680bac3bfe5e809da22d8db Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 04:01:00 +0900 Subject: [PATCH 12/22] Update doc comment --- src/pe/debug.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 21012d8f..8f431abc 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -117,7 +117,7 @@ pub struct DebugData<'a> { /// Reproducible build (Repro) information, if available. /// /// - **MSVC builds**: Contains a 32-byte hash stored directly in the raw data. - /// - **Clang builds**: Uses the `ImageDebugDirectory::time_date_stamp` as a hash, + /// - **Clang builds**: Uses the [`ImageDebugDirectory::time_date_stamp`] as a hash, /// with no dedicated raw data. /// /// [`IMAGE_DEBUG_TYPE_REPRO`] @@ -519,10 +519,10 @@ impl<'a> CodeviewPDB20DebugInfo<'a> { /// field functions as a hash, providing a unique identifier for the reproducible build. #[derive(Debug, PartialEq, Copy, Clone)] pub enum ReproInfo<'a> { - /// Represents a hash stored in the `time_date_stamp` field. + /// Represents a hash stored in the [`ImageDebugDirectory::time_date_stamp`] field. /// /// This variant is used primarily for executables built with Clang/LLD, where the - /// `time_date_stamp` acts as the Repro hash. + /// [`ImageDebugDirectory::time_date_stamp`] acts as the Repro hash. TimeDateStamp(u32), /// Represents a buffer containing the 32-byte Repro hash. /// From 96ae98fd3eed3afd8feb7aaf49d27d4761d75798 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 04:31:46 +0900 Subject: [PATCH 13/22] Add validation for scoped slice bytes --- src/pe/debug.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 8f431abc..f74d819a 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -167,6 +167,13 @@ impl<'a> DebugData<'a> { )) })?; + // Ensure that the offset and size do not exceed the length of the bytes slice + if offset + dd.size as usize > bytes.len() { + return Err(error::Error::Malformed(format!( + "ImageDebugDirectory offset {:#x} and size {:#x} exceeds the bounds of the bytes size {:#x}", + offset, dd.size, bytes.len() + ))); + } let data = &bytes[offset..offset + dd.size as usize]; let iterator = ImageDebugDirectoryIterator { data, rva_offset }; From c5ff9c7b223317eefb9c56b9cf61a3c8649b3a47 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 06:01:13 +0900 Subject: [PATCH 14/22] Add tests for codeview/pdb70 --- src/pe/debug.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index f74d819a..f52cf23e 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -682,9 +682,10 @@ impl<'a> ExDllCharacteristicsInfo { mod tests { use super::{ ExDllCharacteristicsInfo, ImageDebugDirectory, ReproInfo, VCFeatureInfo, - IMAGE_DEBUG_TYPE_CODEVIEW, IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, IMAGE_DEBUG_TYPE_ILTCG, - IMAGE_DEBUG_TYPE_POGO, IMAGE_DEBUG_TYPE_REPRO, IMAGE_DEBUG_TYPE_VC_FEATURE, - IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT, IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE, + CODEVIEW_PDB70_MAGIC, IMAGE_DEBUG_TYPE_CODEVIEW, IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, + IMAGE_DEBUG_TYPE_ILTCG, IMAGE_DEBUG_TYPE_POGO, IMAGE_DEBUG_TYPE_REPRO, + IMAGE_DEBUG_TYPE_VC_FEATURE, IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT, + IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE, }; const DEBUG_DIRECTORIES_TEST_MSVC_BIN: &[u8] = @@ -766,6 +767,62 @@ mod tests { assert_eq!(entries, entries_expect); } + #[test] + fn parse_debug_codeview_pdb70_msvc() { + let binary = + crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_some(), true); + let debug_data = binary.debug_data.unwrap(); + assert_eq!(debug_data.codeview_pdb70_debug_info.is_some(), true); + let codeview_pdb70_debug_info = debug_data.codeview_pdb70_debug_info.unwrap(); + let filename = unsafe { + std::ffi::CStr::from_bytes_with_nul_unchecked(codeview_pdb70_debug_info.filename) + } + .to_string_lossy() + .to_string(); + assert_eq!(filename, String::from("THIS-IS-BINARY-FOR-GOBLIN-TESTS")); + assert_eq!(codeview_pdb70_debug_info.age, 3); + assert_eq!( + codeview_pdb70_debug_info.codeview_signature, + CODEVIEW_PDB70_MAGIC + ); + assert_eq!( + codeview_pdb70_debug_info.signature, + [ + 0x1F, 0x4F, 0x58, 0x9C, 0x3C, 0xEA, 0x00, 0x83, 0x3F, 0x57, 0x00, 0xCC, 0x36, 0xA7, + 0x84, 0xDF, + ] + ); + } + + #[test] + fn parse_debug_codeview_pdb70_clang() { + let binary = crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_CLANG_LLD_BIN) + .expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_some(), true); + let debug_data = binary.debug_data.unwrap(); + assert_eq!(debug_data.codeview_pdb70_debug_info.is_some(), true); + let codeview_pdb70_debug_info = debug_data.codeview_pdb70_debug_info.unwrap(); + let filename = unsafe { + std::ffi::CStr::from_bytes_with_nul_unchecked(codeview_pdb70_debug_info.filename) + } + .to_string_lossy() + .to_string(); + assert_eq!(filename, String::from("THIS-IS-BINARY-FOR-GOBLIN-TESTS")); + assert_eq!(codeview_pdb70_debug_info.age, 1); + assert_eq!( + codeview_pdb70_debug_info.codeview_signature, + CODEVIEW_PDB70_MAGIC + ); + assert_eq!( + codeview_pdb70_debug_info.signature, + [ + 0xC8, 0xBA, 0xF6, 0xAB, 0xB2, 0x98, 0xD1, 0x9E, 0x4C, 0x4C, 0x44, 0x20, 0x50, 0x44, + 0x42, 0x2E, + ] + ); + } + #[test] fn parse_debug_vcfeature() { let binary = From 1be882629b228f2474c50003d0d3038abcb036a9 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 06:10:09 +0900 Subject: [PATCH 15/22] Add test case for binaries with no debug directories --- src/pe/debug.rs | 9 +++++++++ tests/bins/pe/no_debug_directories.exe.bin | Bin 0 -> 1024 bytes 2 files changed, 9 insertions(+) create mode 100644 tests/bins/pe/no_debug_directories.exe.bin diff --git a/src/pe/debug.rs b/src/pe/debug.rs index f52cf23e..e1e7e185 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -688,11 +688,20 @@ mod tests { IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE, }; + const NO_DEBUG_DIRECTORIES_BIN: &[u8] = + include_bytes!("../../tests/bins/pe/no_debug_directories.exe.bin"); const DEBUG_DIRECTORIES_TEST_MSVC_BIN: &[u8] = include_bytes!("../../tests/bins/pe/debug_directories-msvc.exe.bin"); const DEBUG_DIRECTORIES_TEST_CLANG_LLD_BIN: &[u8] = include_bytes!("../../tests/bins/pe/debug_directories-clang_lld.exe.bin"); + #[test] + fn parse_no_debug_directories() { + let binary = + crate::pe::PE::parse(NO_DEBUG_DIRECTORIES_BIN).expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_none(), true); + } + #[test] fn parse_debug_entries_iterator() { let binary = diff --git a/tests/bins/pe/no_debug_directories.exe.bin b/tests/bins/pe/no_debug_directories.exe.bin new file mode 100644 index 0000000000000000000000000000000000000000..dca51490ec4515034bcd6822f9a330e39c38747c GIT binary patch literal 1024 zcmeZ`s$gJbU|?VYVr1Ze%)!B~0E+X;@8Vo{SF)%Q} zM1Zsa6f-b5fZPe@1LfI3Y#@Nq3<^LFR39@#LL)>WPzyFqRP`2pGgK&}D Date: Tue, 29 Oct 2024 06:14:07 +0900 Subject: [PATCH 16/22] Fix doc --- src/pe/debug.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index e1e7e185..7914aa82 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -611,6 +611,8 @@ pub struct ExDllCharacteristicsInfo { /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY`] /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_1`] /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_2`] + /// - [`IMAGE_DLLCHARACTERISTICS_EX_FORWARD_CFI_COMPAT`] + /// - [`IMAGE_DLLCHARACTERISTICS_EX_HOTPATCH_COMPATIBLE`] pub characteristics_ex: u32, } From 218657839efc4a3e52a6c98ac31b59bd1787d98c Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 06:16:35 +0900 Subject: [PATCH 17/22] Fix doc --- src/pe/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 7914aa82..a71a6719 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -278,7 +278,7 @@ pub const IMAGE_DEBUG_TYPE_CLSID: u32 = 11; pub const IMAGE_DEBUG_TYPE_VC_FEATURE: u32 = 12; /// Represents POGO (Profile Guided Optimization) information. pub const IMAGE_DEBUG_TYPE_POGO: u32 = 13; -/// Represents ILTCG (Intermediate Language to Code Generation) optimization data. +/// Represents ILTCG (Incremental Link Time Code Generation) optimization data. pub const IMAGE_DEBUG_TYPE_ILTCG: u32 = 14; /// Represents MPX (Memory Protection Extensions) related debug information. pub const IMAGE_DEBUG_TYPE_MPX: u32 = 15; From 24879d67fbb76746b465319e44f21e6cf10e4a4d Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 07:21:15 +0900 Subject: [PATCH 18/22] Add parser for `IMAGE_DEBUG_TYPE_POGO` --- src/pe/debug.rs | 249 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 234 insertions(+), 15 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index a71a6719..b9467513 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -122,6 +122,15 @@ pub struct DebugData<'a> { /// /// [`IMAGE_DEBUG_TYPE_REPRO`] pub repro_info: Option>, + /// Profile-guided optimization (POGO aka PGO) data, if available. + /// + /// This data provides information relevant to Profile-Guided Optimization + /// (POGO) processes, including function and data block optimizations. + /// + /// [`IMAGE_DEBUG_TYPE_POGO`] + /// + /// Reference: + pub pogo_info: Option>, } impl<'a> DebugData<'a> { @@ -185,6 +194,7 @@ impl<'a> DebugData<'a> { let ex_dll_characteristics_info = ExDllCharacteristicsInfo::parse_with_opts(bytes, iterator, opts)?; let repro_info = ReproInfo::parse_with_opts(bytes, iterator, opts)?; + let pogo_info = POGOInfo::parse_with_opts(bytes, iterator, opts)?; Ok(DebugData { data, @@ -194,6 +204,7 @@ impl<'a> DebugData<'a> { vcfeature_info, ex_dll_characteristics_info, repro_info, + pogo_info, }) } @@ -680,14 +691,178 @@ impl<'a> ExDllCharacteristicsInfo { } } +/// Represents the `IMAGE_DEBUG_POGO_ENTRY` structure, which provides information +/// about Profile-Guided Optimization (POGO aka PGO) data within a PE file. +/// +/// PGO is a compiler optimization technique that uses data collected from program +/// execution to optimize code layout and improve runtime performance. This structure +/// contains details such as the relative virtual address (RVA), size, and the associated +/// name of the function or data block for which PGO data is provided. +/// +/// +#[repr(C)] +#[derive(Debug, PartialEq, Copy, Clone, Default)] +pub struct POGOInfo<'a> { + /// The signature of POGO debug directory entry is always first 4-bytes + /// + /// Either one of: + /// + /// - [`IMAGE_DEBUG_POGO_SIGNATURE_LTCG`] + /// - [`IMAGE_DEBUG_POGO_SIGNATURE_PGU`] + pub signature: u32, + /// Raw bytes of POGO debug entry, without first 4-bytes signatures field + pub data: &'a [u8], +} + +#[derive(Debug, PartialEq, Copy, Clone, Default)] +pub struct POGOInfoEntry<'a> { + /// The relative virtual address (RVA) of the PGO data. + pub rva: u32, + /// The size of the PGO data block. + pub size: u32, + /// The name of the function or data block associated with the PGO data as a byte slice. + /// + /// This may contain a null-terminated string that represents the function or block + /// name for which PGO optimization data is provided. + pub name: &'a [u8], +} + +/// Iterator over POGO entries in [`POGOInfo`]. +#[derive(Debug, Copy, Clone)] +pub struct POGOEntryIterator<'a> { + /// The raw data of [`POGOInfo::data`] without the signature field + pub data: &'a [u8], +} + +/// Indicates the PGO signature for Link-Time Code Generation (LTCG) (`u32` hex representation of `'LTCG'`). +/// +/// This constant is used in the `IMAGE_DEBUG_DIRECTORY` to identify sections of +/// PGO data generated specifically for LTCG optimizations. +pub const IMAGE_DEBUG_POGO_SIGNATURE_LTCG: u32 = 0x4C544347; +/// Indicates the PGO signature for Profile-Guided Optimization (PGO) updates (PGU) (`u32` hex representation of `'PGU\0'`). +/// +/// This constant is used to signify sections of PGO data associated with PGO updates, +/// which are incremental optimizations based on profiling data collected over time. +pub const IMAGE_DEBUG_POGO_SIGNATURE_PGU: u32 = 0x50475500; +/// Size of [`IMAGE_DEBUG_POGO_SIGNATURE_LTCG`] or [`IMAGE_DEBUG_POGO_SIGNATURE_PGU`] +pub const POGO_SIGNATURE_SIZE: usize = core::mem::size_of::(); + +impl<'a> POGOInfo<'a> { + pub fn parse( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + ) -> error::Result> { + Self::parse_with_opts(bytes, idd, &options::ParseOptions::default()) + } + + pub fn parse_with_opts( + bytes: &'a [u8], + idd: ImageDebugDirectoryIterator<'_>, + opts: &options::ParseOptions, + ) -> error::Result> { + let idd = idd.collect::, _>>()?; + let idd = idd + .iter() + .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_POGO); + + if let Some(idd) = idd { + // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; + + let signature = bytes.gread_with::(&mut offset, scroll::LE)?; + if signature != IMAGE_DEBUG_POGO_SIGNATURE_LTCG + && signature != IMAGE_DEBUG_POGO_SIGNATURE_PGU + { + // This is not something we support + return Ok(None); + } + + if offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE > bytes.len() { + return Err(error::Error::Malformed(format!( + "ImageDebugDirectory offset {:#x} and size {:#x} exceeds the bounds of the bytes size {:#x}", + offset, idd.size_of_data, bytes.len() + ))); + } + let data = &bytes[offset..offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE]; + Ok(Some(POGOInfo { signature, data })) + } else { + return Ok(None); + } + } + + /// Returns iterator for [`POGOInfoEntry`] + pub fn entries(&self) -> POGOEntryIterator<'a> { + POGOEntryIterator { data: &self.data } + } +} + +impl<'a> Iterator for POGOEntryIterator<'a> { + type Item = error::Result>; + + fn next(&mut self) -> Option { + if self.data.is_empty() { + return None; + } + + let mut offset = 0; + let rva = match self.data.gread_with::(&mut offset, scroll::LE) { + Ok(rva) => rva, + Err(error) => return Some(Err(error.into())), + }; + let size = match self.data.gread_with::(&mut offset, scroll::LE) { + Ok(size) => size, + Err(error) => return Some(Err(error.into())), + }; + + let name_offset_start = offset; + if offset >= self.data.len() { + return Some(Err(error::Error::Malformed(format!( + "Offset {:#x} is too big for containing name field of POGO entry (rva {:#x} and size {:#X})", + offset,rva, size + )))); + } + let name = match self.data[offset..].iter().position(|&b| b == 0) { + Some(pos) => { + if offset + pos as usize >= self.data.len() { + return Some(Err(error::Error::Malformed(format!( + "Null-terminator for POGO entry (rva {:#x} and size {:#X}) found but exceeds iterator buffer", + rva, size + )))); + } + let name = &self.data[name_offset_start..name_offset_start + pos + 1]; + offset = name_offset_start + pos + 1; + // Align to the next u32 boundary + offset = (offset + 3) & !3; // Round up to the nearest multiple of 4 + name + } + None => { + return Some(Err(error::Error::Malformed(format!( + "Cannot find null-terimnator for POGO entry (rva {:#x} and size {:#X})", + rva, size + )) + .into())); + } + }; + + self.data = &self.data[offset..]; + Some(Ok(POGOInfoEntry { rva, size, name })) + } +} + +impl FusedIterator for POGOEntryIterator<'_> {} + #[cfg(test)] mod tests { use super::{ - ExDllCharacteristicsInfo, ImageDebugDirectory, ReproInfo, VCFeatureInfo, - CODEVIEW_PDB70_MAGIC, IMAGE_DEBUG_TYPE_CODEVIEW, IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, - IMAGE_DEBUG_TYPE_ILTCG, IMAGE_DEBUG_TYPE_POGO, IMAGE_DEBUG_TYPE_REPRO, - IMAGE_DEBUG_TYPE_VC_FEATURE, IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT, - IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE, + ExDllCharacteristicsInfo, ImageDebugDirectory, POGOInfoEntry, ReproInfo, VCFeatureInfo, + CODEVIEW_PDB70_MAGIC, IMAGE_DEBUG_POGO_SIGNATURE_LTCG, IMAGE_DEBUG_TYPE_CODEVIEW, + IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, IMAGE_DEBUG_TYPE_ILTCG, IMAGE_DEBUG_TYPE_POGO, + IMAGE_DEBUG_TYPE_REPRO, IMAGE_DEBUG_TYPE_VC_FEATURE, + IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT, IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE, + POGO_SIGNATURE_SIZE, }; const NO_DEBUG_DIRECTORIES_BIN: &[u8] = @@ -697,6 +872,12 @@ mod tests { const DEBUG_DIRECTORIES_TEST_CLANG_LLD_BIN: &[u8] = include_bytes!("../../tests/bins/pe/debug_directories-clang_lld.exe.bin"); + fn ffi_to_string(bytes: &[u8]) -> String { + unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(bytes) } + .to_string_lossy() + .to_string() + } + #[test] fn parse_no_debug_directories() { let binary = @@ -786,11 +967,7 @@ mod tests { let debug_data = binary.debug_data.unwrap(); assert_eq!(debug_data.codeview_pdb70_debug_info.is_some(), true); let codeview_pdb70_debug_info = debug_data.codeview_pdb70_debug_info.unwrap(); - let filename = unsafe { - std::ffi::CStr::from_bytes_with_nul_unchecked(codeview_pdb70_debug_info.filename) - } - .to_string_lossy() - .to_string(); + let filename = ffi_to_string(codeview_pdb70_debug_info.filename); assert_eq!(filename, String::from("THIS-IS-BINARY-FOR-GOBLIN-TESTS")); assert_eq!(codeview_pdb70_debug_info.age, 3); assert_eq!( @@ -814,11 +991,7 @@ mod tests { let debug_data = binary.debug_data.unwrap(); assert_eq!(debug_data.codeview_pdb70_debug_info.is_some(), true); let codeview_pdb70_debug_info = debug_data.codeview_pdb70_debug_info.unwrap(); - let filename = unsafe { - std::ffi::CStr::from_bytes_with_nul_unchecked(codeview_pdb70_debug_info.filename) - } - .to_string_lossy() - .to_string(); + let filename = ffi_to_string(codeview_pdb70_debug_info.filename); assert_eq!(filename, String::from("THIS-IS-BINARY-FOR-GOBLIN-TESTS")); assert_eq!(codeview_pdb70_debug_info.age, 1); assert_eq!( @@ -900,4 +1073,50 @@ mod tests { ex_dll_characteristics_info_expect ); } + + #[test] + fn parse_debug_pogo() { + let binary = + crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary"); + assert_eq!(binary.debug_data.is_some(), true); + let debug_data = binary.debug_data.unwrap(); + assert_eq!(debug_data.pogo_info.is_some(), true); + let pogo_info = debug_data.pogo_info.unwrap(); + assert_eq!(pogo_info.signature, IMAGE_DEBUG_POGO_SIGNATURE_LTCG); + assert_eq!(pogo_info.data.len(), 88 - POGO_SIGNATURE_SIZE); + let entries = pogo_info.entries().collect::, _>>().unwrap(); + let entries_expect = vec![ + POGOInfoEntry { + rva: 0x1000, + size: 0x3, + // .text$mn + name: &[0x2E, 0x74, 0x65, 0x78, 0x74, 0x24, 0x6D, 0x6E, 0x00], + }, + POGOInfoEntry { + rva: 0x2000, + size: 0xA8, + // .rdata + name: &[0x2E, 0x72, 0x64, 0x61, 0x74, 0x61, 0x00], + }, + POGOInfoEntry { + rva: 0x20A8, + size: 0x18, + // .rdata$voltmd + name: &[ + 0x2E, 0x72, 0x64, 0x61, 0x74, 0x61, 0x24, 0x76, 0x6F, 0x6C, 0x74, 0x6D, 0x64, + 0x00, + ], + }, + POGOInfoEntry { + rva: 0x20C0, + size: 0xCC, + // .rdata$zzzdbg + name: &[ + 0x2E, 0x72, 0x64, 0x61, 0x74, 0x61, 0x24, 0x7A, 0x7A, 0x7A, 0x64, 0x62, 0x67, + 0x00, + ], + }, + ]; + assert_eq!(entries, entries_expect); + } } From 60c14301ac430f5143edb2ecc1bd081fd3d23ea1 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 07:24:31 +0900 Subject: [PATCH 19/22] Fix doc --- src/pe/debug.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index b9467513..981e9984 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -691,7 +691,7 @@ impl<'a> ExDllCharacteristicsInfo { } } -/// Represents the `IMAGE_DEBUG_POGO_ENTRY` structure, which provides information +/// Represents the POGO info structure, which provides information /// about Profile-Guided Optimization (POGO aka PGO) data within a PE file. /// /// PGO is a compiler optimization technique that uses data collected from program @@ -714,6 +714,7 @@ pub struct POGOInfo<'a> { pub data: &'a [u8], } +/// Represents the `IMAGE_DEBUG_POGO_ENTRY` structure #[derive(Debug, PartialEq, Copy, Clone, Default)] pub struct POGOInfoEntry<'a> { /// The relative virtual address (RVA) of the PGO data. From ddca65deb62061164fce5f729534b6d84f937f75 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 07:26:11 +0900 Subject: [PATCH 20/22] Format --- src/pe/debug.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 981e9984..14ed1397 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -783,9 +783,9 @@ impl<'a> POGOInfo<'a> { if offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE > bytes.len() { return Err(error::Error::Malformed(format!( - "ImageDebugDirectory offset {:#x} and size {:#x} exceeds the bounds of the bytes size {:#x}", - offset, idd.size_of_data, bytes.len() - ))); + "ImageDebugDirectory offset {:#x} and size {:#x} exceeds the bounds of the bytes size {:#x}", + offset, idd.size_of_data, bytes.len() + ))); } let data = &bytes[offset..offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE]; Ok(Some(POGOInfo { signature, data })) @@ -821,9 +821,9 @@ impl<'a> Iterator for POGOEntryIterator<'a> { let name_offset_start = offset; if offset >= self.data.len() { return Some(Err(error::Error::Malformed(format!( - "Offset {:#x} is too big for containing name field of POGO entry (rva {:#x} and size {:#X})", - offset,rva, size - )))); + "Offset {:#x} is too big for containing name field of POGO entry (rva {:#x} and size {:#X})", + offset,rva, size + )))); } let name = match self.data[offset..].iter().position(|&b| b == 0) { Some(pos) => { From 3651bd7dd7f70a6853ed0b06a61e53e8aac73738 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Tue, 29 Oct 2024 08:08:19 +0900 Subject: [PATCH 21/22] Simplify `POGOEntryIterator::next` --- src/pe/debug.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 14ed1397..3ab51940 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -818,7 +818,6 @@ impl<'a> Iterator for POGOEntryIterator<'a> { Err(error) => return Some(Err(error.into())), }; - let name_offset_start = offset; if offset >= self.data.len() { return Some(Err(error::Error::Malformed(format!( "Offset {:#x} is too big for containing name field of POGO entry (rva {:#x} and size {:#X})", @@ -833,8 +832,8 @@ impl<'a> Iterator for POGOEntryIterator<'a> { rva, size )))); } - let name = &self.data[name_offset_start..name_offset_start + pos + 1]; - offset = name_offset_start + pos + 1; + let name = &self.data[offset..offset + pos + 1]; + offset = offset + pos + 1; // Align to the next u32 boundary offset = (offset + 3) & !3; // Round up to the nearest multiple of 4 name From 3b3d5d45653a90627a5a36318a743bcedc93ef84 Mon Sep 17 00:00:00 2001 From: kkent030315 Date: Fri, 20 Dec 2024 00:45:07 +0900 Subject: [PATCH 22/22] backwards compatible --- src/pe/debug.rs | 413 +++++++++++++++++++++--------------------------- 1 file changed, 179 insertions(+), 234 deletions(-) diff --git a/src/pe/debug.rs b/src/pe/debug.rs index 3ab51940..eb08fc07 100644 --- a/src/pe/debug.rs +++ b/src/pe/debug.rs @@ -1,7 +1,6 @@ use core::iter::FusedIterator; use crate::error; -use alloc::vec::Vec; use log::debug; use scroll::{Pread, Pwrite, SizeWith}; @@ -76,6 +75,14 @@ impl Iterator for ImageDebugDirectoryIterator<'_> { impl FusedIterator for ImageDebugDirectoryIterator<'_> {} impl ExactSizeIterator for ImageDebugDirectoryIterator<'_> {} +impl<'a> ImageDebugDirectoryIterator<'a> { + /// Find a specific debug type in the debug data. + pub fn find_type(&self, data_type: u32) -> Option { + self.filter_map(Result::ok) + .find(|idd| idd.data_type == data_type) + } +} + /// Represents debug data extracted from a PE (Portable Executable) or TE (Terse Executable) file. #[derive(Debug, PartialEq, Clone, Default)] pub struct DebugData<'a> { @@ -184,17 +191,32 @@ impl<'a> DebugData<'a> { ))); } let data = &bytes[offset..offset + dd.size as usize]; - let iterator = ImageDebugDirectoryIterator { data, rva_offset }; - - let codeview_pdb70_debug_info = - CodeviewPDB70DebugInfo::parse_with_opts(bytes, iterator, opts)?; - let codeview_pdb20_debug_info = - CodeviewPDB20DebugInfo::parse_with_opts(bytes, iterator, opts)?; - let vcfeature_info = VCFeatureInfo::parse_with_opts(bytes, iterator, opts)?; - let ex_dll_characteristics_info = - ExDllCharacteristicsInfo::parse_with_opts(bytes, iterator, opts)?; - let repro_info = ReproInfo::parse_with_opts(bytes, iterator, opts)?; - let pogo_info = POGOInfo::parse_with_opts(bytes, iterator, opts)?; + let it = ImageDebugDirectoryIterator { data, rva_offset }; + + let mut codeview_pdb70_debug_info = None; + let mut codeview_pdb20_debug_info = None; + let mut vcfeature_info = None; + let mut ex_dll_characteristics_info = None; + let mut repro_info = None; + let mut pogo_info = None; + + if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_CODEVIEW) { + codeview_pdb70_debug_info = CodeviewPDB70DebugInfo::parse_with_opts(bytes, idd, opts)?; + codeview_pdb20_debug_info = CodeviewPDB20DebugInfo::parse_with_opts(bytes, idd, opts)?; + } + if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_VC_FEATURE) { + vcfeature_info = VCFeatureInfo::parse_with_opts(bytes, idd, opts)?; + } + if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS) { + ex_dll_characteristics_info = + ExDllCharacteristicsInfo::parse_with_opts(bytes, idd, opts)?; + } + if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_REPRO) { + repro_info = ReproInfo::parse_with_opts(bytes, idd, opts)?; + } + if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_POGO) { + pogo_info = POGOInfo::parse_with_opts(bytes, idd, opts)?; + } Ok(DebugData { data, @@ -215,9 +237,7 @@ impl<'a> DebugData<'a> { /// Find a specific debug type in the debug data. pub fn find_type(&self, data_type: u32) -> Option { - self.entries() - .filter_map(Result::ok) - .find(|idd| idd.data_type == data_type) + self.entries().find_type(data_type) } /// Returns iterator for [`ImageDebugDirectory`] @@ -326,67 +346,54 @@ pub struct CodeviewPDB70DebugInfo<'a> { } impl<'a> CodeviewPDB70DebugInfo<'a> { - pub fn parse( - bytes: &'a [u8], - idd: ImageDebugDirectoryIterator<'_>, - ) -> error::Result> { + 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: ImageDebugDirectoryIterator<'_>, + idd: &ImageDebugDirectory, opts: &options::ParseOptions, ) -> error::Result> { - let idd = idd.collect::, _>>()?; - let idd = idd - .iter() - .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_CODEVIEW); - - if let Some(idd) = idd { - // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly - 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; - if filename_length < 0 { - // the record is too short to be plausible - return Err(error::Error::Malformed(format!( - "ImageDebugDirectory size of data seems wrong: {:?}", - idd.size_of_data - ))); - } - let filename_length = filename_length as usize; + // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; - // check the codeview signature - let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if codeview_signature != CODEVIEW_PDB70_MAGIC { - return Ok(None); - } + // 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; + if filename_length < 0 { + // the record is too short to be plausible + return Err(error::Error::Malformed(format!( + "ImageDebugDirectory size of data seems wrong: {:?}", + idd.size_of_data + ))); + } + let filename_length = filename_length as usize; - // read the rest - let mut signature: [u8; 16] = [0; 16]; - signature.copy_from_slice(bytes.gread_with(&mut offset, 16)?); - let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if let Some(filename) = bytes.get(offset..offset + filename_length) { - Ok(Some(CodeviewPDB70DebugInfo { - codeview_signature, - signature, - age, - filename, - })) - } else { - Err(error::Error::Malformed(format!( - "ImageDebugDirectory seems corrupted: {:?}", - idd - ))) - } + // check the codeview signature + let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if codeview_signature != CODEVIEW_PDB70_MAGIC { + return Ok(None); + } + + // read the rest + let mut signature: [u8; 16] = [0; 16]; + signature.copy_from_slice(bytes.gread_with(&mut offset, 16)?); + let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if let Some(filename) = bytes.get(offset..offset + filename_length) { + Ok(Some(CodeviewPDB70DebugInfo { + codeview_signature, + signature, + age, + filename, + })) } else { - // CodeView debug info not found - Ok(None) + Err(error::Error::Malformed(format!( + "ImageDebugDirectory seems corrupted: {:?}", + idd + ))) } } } @@ -408,46 +415,33 @@ pub struct VCFeatureInfo { } impl<'a> VCFeatureInfo { - pub fn parse( - bytes: &'a [u8], - idd: ImageDebugDirectoryIterator<'_>, - ) -> error::Result> { + 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: ImageDebugDirectoryIterator<'_>, + idd: &ImageDebugDirectory, opts: &options::ParseOptions, ) -> error::Result> { - let idd = idd.collect::, _>>()?; - let idd = idd - .iter() - .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_VC_FEATURE); - - if let Some(idd) = idd { - let mut offset: usize = match opts.resolve_rva { - true => idd.pointer_to_raw_data as usize, - false => idd.address_of_raw_data as usize, - }; - - let pre_vc_plusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - let c_and_cplusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - let guard_stack_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - let sdl_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - let guard_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - - Ok(Some(VCFeatureInfo { - pre_vc_plusplus_count, - c_and_cplusplus_count, - guard_stack_count, - sdl_count, - guard_count, - })) - } else { - // VC Feature info not found - return Ok(None); - } + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; + + let pre_vc_plusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let c_and_cplusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let guard_stack_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let sdl_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let guard_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + + Ok(Some(VCFeatureInfo { + pre_vc_plusplus_count, + c_and_cplusplus_count, + guard_stack_count, + sdl_count, + guard_count, + })) } } @@ -463,69 +457,56 @@ pub struct CodeviewPDB20DebugInfo<'a> { } impl<'a> CodeviewPDB20DebugInfo<'a> { - pub fn parse( - bytes: &'a [u8], - idd: ImageDebugDirectoryIterator<'_>, - ) -> error::Result> { + 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: ImageDebugDirectoryIterator<'_>, + idd: &ImageDebugDirectory, opts: &options::ParseOptions, ) -> error::Result> { - let idd = idd.collect::, _>>()?; - let idd = idd - .iter() - .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_CODEVIEW); - - if let Some(idd) = idd { - // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly - 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 - 16; - if filename_length < 0 { - // the record is too short to be plausible - return Err(error::Error::Malformed(format!( - "ImageDebugDirectory size of data seems wrong: {:?}", - idd.size_of_data - ))); - } - let filename_length = filename_length as usize; + // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; - // check the codeview signature - let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if codeview_signature != CODEVIEW_PDB20_MAGIC { - return Ok(None); - } - let codeview_offset: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - - // read the rest - let signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if let Some(filename) = bytes.get(offset..offset + filename_length) { - Ok(Some(CodeviewPDB20DebugInfo { - codeview_signature, - codeview_offset, - signature, - age, - filename, - })) - } else { - Err(error::Error::Malformed(format!( - "ImageDebugDirectory seems corrupted: {:?}", - idd - ))) - } - } else { - // Codeview20 not found + // 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 - 16; + if filename_length < 0 { + // the record is too short to be plausible + return Err(error::Error::Malformed(format!( + "ImageDebugDirectory size of data seems wrong: {:?}", + idd.size_of_data + ))); + } + let filename_length = filename_length as usize; + + // check the codeview signature + let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if codeview_signature != CODEVIEW_PDB20_MAGIC { return Ok(None); } + let codeview_offset: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + + // read the rest + let signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if let Some(filename) = bytes.get(offset..offset + filename_length) { + Ok(Some(CodeviewPDB20DebugInfo { + codeview_signature, + codeview_offset, + signature, + age, + filename, + })) + } else { + Err(error::Error::Malformed(format!( + "ImageDebugDirectory seems corrupted: {:?}", + idd + ))) + } } } @@ -555,46 +536,34 @@ pub enum ReproInfo<'a> { } impl<'a> ReproInfo<'a> { - pub fn parse( - bytes: &'a [u8], - idd: ImageDebugDirectoryIterator<'_>, - ) -> error::Result> { + 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: ImageDebugDirectoryIterator<'_>, + idd: &ImageDebugDirectory, opts: &options::ParseOptions, ) -> error::Result> { - let idd = idd.collect::, _>>()?; - let idd = idd - .iter() - .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_REPRO); - - if let Some(idd) = idd { - let mut offset: usize = match opts.resolve_rva { - true => idd.pointer_to_raw_data as usize, - false => idd.address_of_raw_data as usize, - }; - - // Clang(LLD) produces no data, uses timestamp field instead - // MSVC(link.exe) produces 32-byte data - if idd.size_of_data > 0 { - let length: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - if let Some(buffer) = bytes.get(offset..offset + length as usize) { - Ok(Some(Self::Buffer { length, buffer })) - } else { - Err(error::Error::Malformed(format!( - "ImageDebugDirectory seems corrupted: {:?}", - idd - ))) - } + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; + + // Clang(LLD) produces no data, uses timestamp field instead + // MSVC(link.exe) produces 32-byte data + if idd.size_of_data > 0 { + let length: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + if let Some(buffer) = bytes.get(offset..offset + length as usize) { + Ok(Some(Self::Buffer { length, buffer })) } else { - Ok(Some(Self::TimeDateStamp(idd.time_date_stamp))) + Err(error::Error::Malformed(format!( + "ImageDebugDirectory seems corrupted: {:?}", + idd + ))) } } else { - return Ok(None); + Ok(Some(Self::TimeDateStamp(idd.time_date_stamp))) } } } @@ -658,36 +627,24 @@ pub const IMAGE_DLLCHARACTERISTICS_EX_FORWARD_CFI_COMPAT: u32 = 0x40; pub const IMAGE_DLLCHARACTERISTICS_EX_HOTPATCH_COMPATIBLE: u32 = 0x80; impl<'a> ExDllCharacteristicsInfo { - pub fn parse( - bytes: &'a [u8], - idd: ImageDebugDirectoryIterator<'_>, - ) -> error::Result> { + 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: ImageDebugDirectoryIterator<'_>, + idd: &ImageDebugDirectory, opts: &options::ParseOptions, ) -> error::Result> { - let idd = idd.collect::, _>>()?; - let idd = idd - .iter() - .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS); - - if let Some(idd) = idd { - // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly - let mut offset: usize = match opts.resolve_rva { - true => idd.pointer_to_raw_data as usize, - false => idd.address_of_raw_data as usize, - }; + // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; - let characteristics_ex: u32 = bytes.gread_with(&mut offset, scroll::LE)?; + let characteristics_ex: u32 = bytes.gread_with(&mut offset, scroll::LE)?; - Ok(Some(ExDllCharacteristicsInfo { characteristics_ex })) - } else { - return Ok(None); - } + Ok(Some(ExDllCharacteristicsInfo { characteristics_ex })) } } @@ -749,49 +706,37 @@ pub const IMAGE_DEBUG_POGO_SIGNATURE_PGU: u32 = 0x50475500; pub const POGO_SIGNATURE_SIZE: usize = core::mem::size_of::(); impl<'a> POGOInfo<'a> { - pub fn parse( - bytes: &'a [u8], - idd: ImageDebugDirectoryIterator<'_>, - ) -> error::Result> { + 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: ImageDebugDirectoryIterator<'_>, + idd: &ImageDebugDirectory, opts: &options::ParseOptions, ) -> error::Result> { - let idd = idd.collect::, _>>()?; - let idd = idd - .iter() - .find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_POGO); - - if let Some(idd) = idd { - // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly - let mut offset: usize = match opts.resolve_rva { - true => idd.pointer_to_raw_data as usize, - false => idd.address_of_raw_data as usize, - }; - - let signature = bytes.gread_with::(&mut offset, scroll::LE)?; - if signature != IMAGE_DEBUG_POGO_SIGNATURE_LTCG - && signature != IMAGE_DEBUG_POGO_SIGNATURE_PGU - { - // This is not something we support - return Ok(None); - } + // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly + let mut offset: usize = match opts.resolve_rva { + true => idd.pointer_to_raw_data as usize, + false => idd.address_of_raw_data as usize, + }; - if offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE > bytes.len() { - return Err(error::Error::Malformed(format!( + let signature = bytes.gread_with::(&mut offset, scroll::LE)?; + if signature != IMAGE_DEBUG_POGO_SIGNATURE_LTCG + && signature != IMAGE_DEBUG_POGO_SIGNATURE_PGU + { + // This is not something we support + return Ok(None); + } + + if offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE > bytes.len() { + return Err(error::Error::Malformed(format!( "ImageDebugDirectory offset {:#x} and size {:#x} exceeds the bounds of the bytes size {:#x}", offset, idd.size_of_data, bytes.len() ))); - } - let data = &bytes[offset..offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE]; - Ok(Some(POGOInfo { signature, data })) - } else { - return Ok(None); } + let data = &bytes[offset..offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE]; + Ok(Some(POGOInfo { signature, data })) } /// Returns iterator for [`POGOInfoEntry`]