| //! 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<authenticode::ExcludedSections>, |
| /// The PE header |
| pub header: header::Header, |
| /// A list of the sections in this PE binary |
| pub sections: Vec<section_table::SectionTable>, |
| /// 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<export::ExportData<'a>>, |
| /// Data for any imported symbols, and from which `dll`, etc., in this binary |
| pub import_data: Option<import::ImportData<'a>>, |
| /// The list of exported symbols in this binary, contains synthetic information for easier analysis |
| pub exports: Vec<export::Export<'a>>, |
| /// The list symbols imported by this binary from other `dll`s |
| pub imports: Vec<import::Import<'a>>, |
| /// 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<debug::DebugData<'a>>, |
| /// Exception handling and stack unwind information, if any, contained in the PE header |
| pub exception_data: Option<exception::ExceptionData<'a>>, |
| /// 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> { |
| 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<Self> { |
| 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::<u64>( |
| bytes, |
| import_table, |
| §ions, |
| file_alignment, |
| opts, |
| )? |
| } else { |
| import::ImportData::parse_with_opts::<u32>( |
| bytes, |
| import_table, |
| §ions, |
| file_alignment, |
| opts, |
| )? |
| }; |
| debug!("import data {:#?}", id); |
| if is_64 { |
| imports = import::Import::parse::<u64>(bytes, &id, §ions)? |
| } else { |
| imports = import::Import::parse::<u32>(bytes, &id, §ions)? |
| } |
| libraries = id |
| .import_data |
| .iter() |
| .map(|data| data.name) |
| .collect::<Vec<&'a str>>(); |
| 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<usize>, |
| ctx: scroll::Endian, |
| ) -> Result<usize, error::Error> { |
| // 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<usize, error::Error> { |
| 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<scroll::Endian> for PE<'a> { |
| type Error = error::Error; |
| |
| fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> { |
| 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<usize> = 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<section_table::SectionTable>, |
| /// 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<symbol::SymbolTable<'a>>, |
| /// The string table, they don't exist if COFF symbol table does not exist. |
| pub strings: Option<strtab::Strtab<'a>>, |
| } |
| |
| impl<'a> Coff<'a> { |
| /// Reads a COFF object from the underlying `bytes` |
| pub fn parse(bytes: &'a [u8]) -> error::Result<Self> { |
| 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 <filename>.asm -o <filename>.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::<Vec<_>>(); |
| 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"); |
| } |
| } |
| } |