summaryrefslogtreecommitdiffstats
path: root/third_party/rust/goblin/src/pe/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/goblin/src/pe/mod.rs')
-rw-r--r--third_party/rust/goblin/src/pe/mod.rs249
1 files changed, 225 insertions, 24 deletions
diff --git a/third_party/rust/goblin/src/pe/mod.rs b/third_party/rust/goblin/src/pe/mod.rs
index 5a7337630a..2336fddc57 100644
--- a/third_party/rust/goblin/src/pe/mod.rs
+++ b/third_party/rust/goblin/src/pe/mod.rs
@@ -3,7 +3,12 @@
// 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;
@@ -23,8 +28,11 @@ 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)]
@@ -140,7 +148,7 @@ impl<'a> PE<'a> {
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 Some(&export_table) = optional_header.data_directories.get_export_table() {
if let Ok(ed) = export::ExportData::parse_with_opts(
bytes,
export_table,
@@ -162,7 +170,7 @@ impl<'a> PE<'a> {
}
}
debug!("exports: {:#?}", exports);
- if let Some(import_table) = *optional_header.data_directories.get_import_table() {
+ if let Some(&import_table) = optional_header.data_directories.get_import_table() {
let id = if is_64 {
import::ImportData::parse_with_opts::<u64>(
bytes,
@@ -196,7 +204,7 @@ impl<'a> PE<'a> {
import_data = Some(id);
}
debug!("imports: {:#?}", imports);
- if let Some(debug_table) = *optional_header.data_directories.get_debug_table() {
+ if let Some(&debug_table) = optional_header.data_directories.get_debug_table() {
debug_data = Some(debug::DebugData::parse_with_opts(
bytes,
debug_table,
@@ -209,8 +217,8 @@ impl<'a> PE<'a> {
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()
+ if let Some(&exception_table) =
+ optional_header.data_directories.get_exception_table()
{
exception_data = Some(exception::ExceptionData::parse_with_opts(
bytes,
@@ -222,26 +230,30 @@ impl<'a> PE<'a> {
}
}
- let certtable = 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,
- )?;
+ // 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,
+ )?;
- let start = certificate_table.virtual_address as usize;
- let end = start + certificate_table.size as usize;
- Some(start..end)
+ certificate_table.size as usize
+ } else {
+ 0
+ }
} else {
- None
+ 0
};
authenticode_excluded_sections = Some(authenticode::ExcludedSections::new(
checksum,
datadir_entry_certtable,
- certtable,
+ certificate_table_size,
+ optional_header.windows_fields.size_of_headers as usize,
));
}
Ok(PE {
@@ -265,6 +277,192 @@ impl<'a> PE<'a> {
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
@@ -274,10 +472,12 @@ pub struct Coff<'a> {
pub header: header::CoffHeader,
/// A list of the sections in this COFF binary
pub sections: Vec<section_table::SectionTable>,
- /// The COFF symbol table.
- pub symbols: symbol::SymbolTable<'a>,
- /// The string table.
- pub strings: strtab::Strtab<'a>,
+ /// 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> {
@@ -414,7 +614,7 @@ mod tests {
#[test]
fn string_table_excludes_length() {
let coff = Coff::parse(&&COFF_FILE_SINGLE_STRING_IN_STRING_TABLE[..]).unwrap();
- let string_table = coff.strings.to_vec().unwrap();
+ let string_table = coff.strings.unwrap().to_vec().unwrap();
assert!(string_table == vec!["ExitProcess"]);
}
@@ -422,9 +622,10 @@ mod tests {
#[test]
fn symbol_name_excludes_length() {
let coff = Coff::parse(&COFF_FILE_SINGLE_STRING_IN_STRING_TABLE).unwrap();
- let strings = coff.strings;
+ let strings = coff.strings.unwrap();
let symbols = coff
.symbols
+ .unwrap()
.iter()
.filter(|(_, name, _)| name.is_none())
.map(|(_, _, sym)| sym.name(&strings).unwrap().to_owned())