//! A PE32 and PE32+ parser //! // TODO: panics with unwrap on None for apisetschema.dll, fhuxgraphics.dll and some others use core::cmp::max; use alloc::borrow::Cow; use alloc::string::String; use alloc::vec::Vec; use log::warn; pub mod authenticode; pub mod certificate_table; pub mod characteristic; pub mod data_directories; pub mod debug; pub mod exception; pub mod export; pub mod header; pub mod import; pub mod optional_header; pub mod options; pub mod relocation; pub mod section_table; pub mod symbol; pub mod utils; use crate::container; use crate::error; use crate::pe::utils::pad; use crate::strtab; use scroll::{ctx, Pwrite}; use log::debug; #[derive(Debug)] /// An analyzed PE32/PE32+ binary pub struct PE<'a> { /// Underlying bytes bytes: &'a [u8], authenticode_excluded_sections: Option, /// The PE header pub header: header::Header, /// A list of the sections in this PE binary pub sections: Vec, /// The size of the binary pub size: usize, /// The name of this `dll`, if it has one pub name: Option<&'a str>, /// Whether this is a `dll` or not pub is_lib: bool, /// Whether the binary is 64-bit (PE32+) pub is_64: bool, /// the entry point of the binary pub entry: usize, /// The binary's RVA, or image base - useful for computing virtual addreses pub image_base: usize, /// Data about any exported symbols in this binary (e.g., if it's a `dll`) pub export_data: Option>, /// Data for any imported symbols, and from which `dll`, etc., in this binary pub import_data: Option>, /// The list of exported symbols in this binary, contains synthetic information for easier analysis pub exports: Vec>, /// The list symbols imported by this binary from other `dll`s pub imports: Vec>, /// The list of libraries which this binary imports symbols from pub libraries: Vec<&'a str>, /// Debug information, if any, contained in the PE header pub debug_data: Option>, /// Exception handling and stack unwind information, if any, contained in the PE header pub exception_data: Option>, /// Certificates present, if any, described by the Certificate Table pub certificates: certificate_table::CertificateDirectoryTable<'a>, } impl<'a> PE<'a> { /// Reads a PE binary from the underlying `bytes` pub fn parse(bytes: &'a [u8]) -> error::Result { Self::parse_with_opts(bytes, &options::ParseOptions::default()) } /// Reads a PE binary from the underlying `bytes` pub fn parse_with_opts(bytes: &'a [u8], opts: &options::ParseOptions) -> error::Result { let header = header::Header::parse(bytes)?; let mut authenticode_excluded_sections = None; debug!("{:#?}", header); let optional_header_offset = header.dos_header.pe_pointer as usize + header::SIZEOF_PE_MAGIC + header::SIZEOF_COFF_HEADER; let offset = &mut (optional_header_offset + header.coff_header.size_of_optional_header as usize); let sections = header.coff_header.sections(bytes, offset)?; let is_lib = characteristic::is_dll(header.coff_header.characteristics); let mut entry = 0; let mut image_base = 0; let mut exports = vec![]; let mut export_data = None; let mut name = None; let mut imports = vec![]; let mut import_data = None; let mut libraries = vec![]; let mut debug_data = None; let mut exception_data = None; let mut certificates = Default::default(); let mut is_64 = false; if let Some(optional_header) = header.optional_header { // Sections we are assembling through the parsing, eventually, it will be passed // to the authenticode_sections attribute of `PE`. let (checksum, datadir_entry_certtable) = match optional_header.standard_fields.magic { optional_header::MAGIC_32 => { let standard_field_offset = optional_header_offset + optional_header::SIZEOF_STANDARD_FIELDS_32; let checksum_field_offset = standard_field_offset + optional_header::OFFSET_WINDOWS_FIELDS_32_CHECKSUM; ( checksum_field_offset..checksum_field_offset + 4, optional_header_offset + 128..optional_header_offset + 136, ) } optional_header::MAGIC_64 => { let standard_field_offset = optional_header_offset + optional_header::SIZEOF_STANDARD_FIELDS_64; let checksum_field_offset = standard_field_offset + optional_header::OFFSET_WINDOWS_FIELDS_64_CHECKSUM; ( checksum_field_offset..checksum_field_offset + 4, optional_header_offset + 144..optional_header_offset + 152, ) } magic => { return Err(error::Error::Malformed(format!( "Unsupported header magic ({:#x})", magic ))) } }; entry = optional_header.standard_fields.address_of_entry_point as usize; image_base = optional_header.windows_fields.image_base as usize; is_64 = optional_header.container()? == container::Container::Big; debug!( "entry {:#x} image_base {:#x} is_64: {}", entry, image_base, is_64 ); let file_alignment = optional_header.windows_fields.file_alignment; if let Some(&export_table) = optional_header.data_directories.get_export_table() { if let Ok(ed) = export::ExportData::parse_with_opts( bytes, export_table, §ions, file_alignment, opts, ) { debug!("export data {:#?}", ed); exports = export::Export::parse_with_opts( bytes, &ed, §ions, file_alignment, opts, )?; name = ed.name; debug!("name: {:#?}", name); export_data = Some(ed); } } debug!("exports: {:#?}", exports); if let Some(&import_table) = optional_header.data_directories.get_import_table() { let id = if is_64 { import::ImportData::parse_with_opts::( bytes, import_table, §ions, file_alignment, opts, )? } else { import::ImportData::parse_with_opts::( bytes, import_table, §ions, file_alignment, opts, )? }; debug!("import data {:#?}", id); if is_64 { imports = import::Import::parse::(bytes, &id, §ions)? } else { imports = import::Import::parse::(bytes, &id, §ions)? } libraries = id .import_data .iter() .map(|data| data.name) .collect::>(); libraries.sort(); libraries.dedup(); import_data = Some(id); } debug!("imports: {:#?}", imports); if let Some(&debug_table) = optional_header.data_directories.get_debug_table() { debug_data = Some(debug::DebugData::parse_with_opts( bytes, debug_table, §ions, file_alignment, opts, )?); } if header.coff_header.machine == header::COFF_MACHINE_X86_64 { // currently only x86_64 is supported debug!("exception data: {:#?}", exception_data); if let Some(&exception_table) = optional_header.data_directories.get_exception_table() { exception_data = Some(exception::ExceptionData::parse_with_opts( bytes, exception_table, §ions, file_alignment, opts, )?); } } // Parse attribute certificates unless opted out of let certificate_table_size = if opts.parse_attribute_certificates { if let Some(&certificate_table) = optional_header.data_directories.get_certificate_table() { certificates = certificate_table::enumerate_certificates( bytes, certificate_table.virtual_address, certificate_table.size, )?; certificate_table.size as usize } else { 0 } } else { 0 }; authenticode_excluded_sections = Some(authenticode::ExcludedSections::new( checksum, datadir_entry_certtable, certificate_table_size, optional_header.windows_fields.size_of_headers as usize, )); } Ok(PE { bytes, authenticode_excluded_sections, header, sections, size: 0, name, is_lib, is_64, entry, image_base, export_data, import_data, exports, imports, libraries, debug_data, exception_data, certificates, }) } pub fn write_sections( &self, bytes: &mut [u8], offset: &mut usize, file_alignment: Option, ctx: scroll::Endian, ) -> Result { // sections table and data debug_assert!( self.sections .iter() .flat_map(|section_a| { self.sections .iter() .map(move |section_b| (section_a, section_b)) }) // given sections = (s_1, …, s_n) // for all (s_i, s_j), i != j, verify that s_i does not overlap with s_j and vice versa. .all(|(section_i, section_j)| section_i == section_j || !section_i.overlaps_with(section_j)), "Overlapping sections were found, this is not supported." ); for section in &self.sections { let section_data = section.data(&self.bytes)?.ok_or_else(|| { error::Error::Malformed(format!( "Section data `{}` is malformed", section.name().unwrap_or("unknown name") )) })?; let file_section_offset = usize::try_from(section.pointer_to_raw_data).map_err(|_| { error::Error::Malformed(format!( "Section `{}`'s pointer to raw data does not fit in platform `usize`", section.name().unwrap_or("unknown name") )) })?; let vsize: usize = section.virtual_size.try_into()?; let ondisk_size: usize = section.size_of_raw_data.try_into()?; let section_name = String::from(section.name().unwrap_or("unknown name")); let mut file_offset = file_section_offset; // `file_section_offset` is a on-disk offset which can be anywhere in the file. // Write section data first to avoid the final consumption. match section_data { Cow::Borrowed(borrowed) => bytes.gwrite(borrowed, &mut file_offset)?, Cow::Owned(owned) => bytes.gwrite(owned.as_slice(), &mut file_offset)?, }; // Section tables follows the header. bytes.gwrite_with(section, offset, ctx)?; // for size size_of_raw_data // if < virtual_size, pad with 0 // Pad with zeros if necessary if file_offset < vsize { bytes.gwrite(vec![0u8; vsize - file_offset].as_slice(), &mut file_offset)?; } // Align on a boundary as per file alignement field. if let Some(pad) = pad(file_offset - file_section_offset, file_alignment) { debug!( "aligning `{}` {:#x} -> {:#x} bytes'", section_name, file_offset - file_section_offset, file_offset - file_section_offset + pad.len() ); bytes.gwrite(pad.as_slice(), &mut file_offset)?; } let written_data_size = file_offset - file_section_offset; if ondisk_size != written_data_size { warn!("Original PE is inefficient or bug (on-disk data size in PE: {:#x}), we wrote {:#x} bytes", ondisk_size, written_data_size); } } Ok(*offset) } pub fn write_certificates( &self, bytes: &mut [u8], ctx: scroll::Endian, ) -> Result { let opt_header = self .header .optional_header .ok_or(error::Error::Malformed(format!( "This PE binary has no optional header; it is required to write certificates" )))?; let mut max_offset = 0; if let Some(certificate_directory) = opt_header.data_directories.get_certificate_table() { let mut certificate_start = certificate_directory.virtual_address.try_into()?; for certificate in &self.certificates { bytes.gwrite_with(certificate, &mut certificate_start, ctx)?; max_offset = max(certificate_start, max_offset); } } Ok(max_offset) } } impl<'a> ctx::TryIntoCtx for PE<'a> { type Error = error::Error; fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result { let mut offset = 0; // We need to maintain a `max_offset` because // we could be writing sections in the wrong order (i.e. not an increasing order for the // pointer on raw disk) // and there could be holes between sections. // If we don't re-layout sections, we cannot fix that ourselves. // Same can be said about the certificate table, there could be a hole between sections // and the certificate data. // To avoid those troubles, we maintain the max over all offsets we see so far. let mut max_offset = 0; let file_alignment: Option = match self.header.optional_header { Some(opt_header) => { debug_assert!( opt_header.windows_fields.file_alignment.count_ones() == 1, "file alignment should be a power of 2" ); Some(opt_header.windows_fields.file_alignment.try_into()?) } _ => None, }; bytes.gwrite_with(self.header, &mut offset, ctx)?; max_offset = max(offset, max_offset); self.write_sections(bytes, &mut offset, file_alignment, ctx)?; // We want the section offset for which we have the highest pointer on disk. // The next offset is reserved for debug tables (outside of sections) and/or certificate // tables. max_offset = max( self.sections .iter() .max_by_key(|section| section.pointer_to_raw_data as usize) .map(|section| (section.pointer_to_raw_data + section.size_of_raw_data) as usize) .unwrap_or(offset), max_offset, ); // COFF Symbol Table // Auxiliary Symbol Records // COFF String Table assert!( self.header.coff_header.pointer_to_symbol_table == 0, "Symbol tables in PE are deprecated and not supported to write" ); // The following data directories are // taken care inside a section: // - export table (.edata) // - import table (.idata) // - bound import table // - import address table // - delay import tables // - resource table (.rsrc) // - exception table (.pdata) // - base relocation table (.reloc) // - debug table (.debug) <- this one is special, it can be outside of a // section. // - load config table // - tls table (.tls) // - architecture (reserved, 0 for now) // - global ptr is a "empty" data directory (header-only) // - clr runtime header (.cormeta is object-only) // // Nonetheless, we need to write the attribute certificate table one. max_offset = max(max_offset, self.write_certificates(bytes, ctx)?); // TODO: we would like to support debug table outside of a section. // i.e. debug tables that are never mapped in memory // See https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#debug-directory-image-only // > The debug directory can be in a discardable .debug section (if one exists), or it can be included in any other section in the image file, or not be in a section at all. // In case it's not in a section at all, we need to find a way // to rewrite it again. // and we need to respect the ordering between attribute certificates // and debug table. Ok(max_offset) } } /// An analyzed COFF object #[derive(Debug)] pub struct Coff<'a> { /// The COFF header pub header: header::CoffHeader, /// A list of the sections in this COFF binary pub sections: Vec, /// The COFF symbol table, they are not guaranteed to exist. /// For an image, this is expected to be None as COFF debugging information /// has been deprecated. pub symbols: Option>, /// The string table, they don't exist if COFF symbol table does not exist. pub strings: Option>, } impl<'a> Coff<'a> { /// Reads a COFF object from the underlying `bytes` pub fn parse(bytes: &'a [u8]) -> error::Result { let offset = &mut 0; let header = header::CoffHeader::parse(bytes, offset)?; debug!("{:#?}", header); // TODO: maybe parse optional header, but it isn't present for Windows. *offset += header.size_of_optional_header as usize; let sections = header.sections(bytes, offset)?; let symbols = header.symbols(bytes)?; let strings = header.strings(bytes)?; Ok(Coff { header, sections, symbols, strings, }) } } #[cfg(test)] mod tests { use super::Coff; use super::PE; static INVALID_DOS_SIGNATURE: [u8; 512] = [ 0x3D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20, 0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x0D, 0x0D, 0x0A, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x31, 0xE2, 0xB1, 0x67, 0x50, 0x8C, 0xE2, 0x67, 0x50, 0x8C, 0xE2, 0x67, 0x50, 0x8C, 0xE2, 0x3C, 0x38, 0x88, 0xE3, 0x6D, 0x50, 0x8C, 0xE2, 0x3C, 0x38, 0x8F, 0xE3, 0x62, 0x50, 0x8C, 0xE2, 0x3C, 0x38, 0x89, 0xE3, 0xE0, 0x50, 0x8C, 0xE2, 0xAC, 0x3F, 0x89, 0xE3, 0x42, 0x50, 0x8C, 0xE2, 0xAC, 0x3F, 0x88, 0xE3, 0x77, 0x50, 0x8C, 0xE2, 0xAC, 0x3F, 0x8F, 0xE3, 0x6E, 0x50, 0x8C, 0xE2, 0x3C, 0x38, 0x8D, 0xE3, 0x64, 0x50, 0x8C, 0xE2, 0x67, 0x50, 0x8D, 0xE2, 0x3F, 0x50, 0x8C, 0xE2, 0xE1, 0x20, 0x85, 0xE3, 0x66, 0x50, 0x8C, 0xE2, 0xE1, 0x20, 0x73, 0xE2, 0x66, 0x50, 0x8C, 0xE2, 0xE1, 0x20, 0x8E, 0xE3, 0x66, 0x50, 0x8C, 0xE2, 0x52, 0x69, 0x63, 0x68, 0x67, 0x50, 0x8C, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x64, 0x86, 0x07, 0x00, 0x5F, 0x41, 0xFC, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x22, 0x00, 0x0B, 0x02, 0x0E, 0x1A, 0x00, 0xFC, 0x00, 0x00, 0x00, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0xE0, 0x68, 0x02, 0x00, 0x03, 0x00, 0x60, 0x81, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA3, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0xD0, 0x01, 0x00, 0x60, 0x0F, 0x00, 0x00, 0x00, 0xC4, 0x01, 0x00, 0xF8, 0x46, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x54, 0x06, 0x00, 0x00, 0xF0, 0x91, 0x01, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x92, 0x01, 0x00, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x48, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; static INVALID_PE_SIGNATURE: [u8; 512] = [ 0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20, 0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x0D, 0x0D, 0x0A, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x31, 0xE2, 0xB1, 0x67, 0x50, 0x8C, 0xE2, 0x67, 0x50, 0x8C, 0xE2, 0x67, 0x50, 0x8C, 0xE2, 0x3C, 0x38, 0x88, 0xE3, 0x6D, 0x50, 0x8C, 0xE2, 0x3C, 0x38, 0x8F, 0xE3, 0x62, 0x50, 0x8C, 0xE2, 0x3C, 0x38, 0x89, 0xE3, 0xE0, 0x50, 0x8C, 0xE2, 0xAC, 0x3F, 0x89, 0xE3, 0x42, 0x50, 0x8C, 0xE2, 0xAC, 0x3F, 0x88, 0xE3, 0x77, 0x50, 0x8C, 0xE2, 0xAC, 0x3F, 0x8F, 0xE3, 0x6E, 0x50, 0x8C, 0xE2, 0x3C, 0x38, 0x8D, 0xE3, 0x64, 0x50, 0x8C, 0xE2, 0x67, 0x50, 0x8D, 0xE2, 0x3F, 0x50, 0x8C, 0xE2, 0xE1, 0x20, 0x85, 0xE3, 0x66, 0x50, 0x8C, 0xE2, 0xE1, 0x20, 0x73, 0xE2, 0x66, 0x50, 0x8C, 0xE2, 0xE1, 0x20, 0x8E, 0xE3, 0x66, 0x50, 0x8C, 0xE2, 0x52, 0x69, 0x63, 0x68, 0x67, 0x50, 0x8C, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x05, 0x00, 0x00, 0x64, 0x86, 0x07, 0x00, 0x5F, 0x41, 0xFC, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x22, 0x00, 0x0B, 0x02, 0x0E, 0x1A, 0x00, 0xFC, 0x00, 0x00, 0x00, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0xE0, 0x68, 0x02, 0x00, 0x03, 0x00, 0x60, 0x81, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA3, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0xD0, 0x01, 0x00, 0x60, 0x0F, 0x00, 0x00, 0x00, 0xC4, 0x01, 0x00, 0xF8, 0x46, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x54, 0x06, 0x00, 0x00, 0xF0, 0x91, 0x01, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x92, 0x01, 0x00, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x48, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; // The assembler program used to generate this string is as follows: // // bits 64 // default rel // segment .text // global main // extern ExitProcess // main: // xor rax, rax // call ExitProcess // // // The code can be compiled using nasm (https://nasm.us) with the command below: // nasm -f win64 .asm -o .obj static COFF_FILE_SINGLE_STRING_IN_STRING_TABLE: [u8; 220] = [ 0x64, 0x86, 0x1, 0x0, 0xb5, 0x39, 0x91, 0x62, 0x4e, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x20, 0x0, 0x50, 0x60, 0x48, 0x31, 0xc0, 0xe8, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0xff, 0x0, 0x0, 0x67, 0x1, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x61, 0x73, 0x6d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x3, 0x1, 0x8, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2e, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x10, 0x0, 0x0, 0x0, 0x45, 0x78, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x0, ]; #[test] fn string_table_excludes_length() { let coff = Coff::parse(&&COFF_FILE_SINGLE_STRING_IN_STRING_TABLE[..]).unwrap(); let string_table = coff.strings.unwrap().to_vec().unwrap(); assert!(string_table == vec!["ExitProcess"]); } #[test] fn symbol_name_excludes_length() { let coff = Coff::parse(&COFF_FILE_SINGLE_STRING_IN_STRING_TABLE).unwrap(); let strings = coff.strings.unwrap(); let symbols = coff .symbols .unwrap() .iter() .filter(|(_, name, _)| name.is_none()) .map(|(_, _, sym)| sym.name(&strings).unwrap().to_owned()) .collect::>(); assert_eq!(symbols, vec!["ExitProcess"]) } #[test] fn invalid_dos_header() { if let Ok(_) = PE::parse(&INVALID_DOS_SIGNATURE) { panic!("must not parse PE with invalid DOS header"); } } #[test] fn invalid_pe_header() { if let Ok(_) = PE::parse(&INVALID_PE_SIGNATURE) { panic!("must not parse PE with invalid PE header"); } } }