summaryrefslogtreecommitdiffstats
path: root/third_party/rust/goblin/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /third_party/rust/goblin/src
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/goblin/src')
-rw-r--r--third_party/rust/goblin/src/elf/reloc.rs2
-rw-r--r--third_party/rust/goblin/src/error.rs7
-rw-r--r--third_party/rust/goblin/src/lib.rs24
-rw-r--r--third_party/rust/goblin/src/mach/load_command.rs3
-rw-r--r--third_party/rust/goblin/src/pe/authenticode.rs200
-rw-r--r--third_party/rust/goblin/src/pe/certificate_table.rs28
-rw-r--r--third_party/rust/goblin/src/pe/data_directories.rs175
-rw-r--r--third_party/rust/goblin/src/pe/header.rs150
-rw-r--r--third_party/rust/goblin/src/pe/mod.rs249
-rw-r--r--third_party/rust/goblin/src/pe/optional_header.rs83
-rw-r--r--third_party/rust/goblin/src/pe/options.rs10
-rw-r--r--third_party/rust/goblin/src/pe/section_table.rs41
-rw-r--r--third_party/rust/goblin/src/pe/symbol.rs9
-rw-r--r--third_party/rust/goblin/src/pe/utils.rs16
-rw-r--r--third_party/rust/goblin/src/strtab.rs5
15 files changed, 876 insertions, 126 deletions
diff --git a/third_party/rust/goblin/src/elf/reloc.rs b/third_party/rust/goblin/src/elf/reloc.rs
index eeb3e1a2ac..d8a1df9d6a 100644
--- a/third_party/rust/goblin/src/elf/reloc.rs
+++ b/third_party/rust/goblin/src/elf/reloc.rs
@@ -51,7 +51,7 @@
//! | `R_X86_64_GOTPC32` | 26 | 32 | GOT + A - P |
//! | `R_X86_64_SIZE32` | 32 | 32 | Z + A |
//! | `R_X86_64_SIZE64` | 33 | 64 | Z + A |
-//! | `R_X86_64_GOTPC32_TLSDESC` 34 | 32 | |
+//! | `R_X86_64_GOTPC32_TLSDESC`| 34 | 32 | |
//! | `R_X86_64_TLSDESC_CALL` | 35 | NONE | |
//! | `R_X86_64_TLSDESC` | 36 | 64 × 2 | |
//! | `R_X86_64_IRELATIVE` | 37 | 64 | indirect (B + A) |
diff --git a/third_party/rust/goblin/src/error.rs b/third_party/rust/goblin/src/error.rs
index e2dd517e15..0ec4e1e0c9 100644
--- a/third_party/rust/goblin/src/error.rs
+++ b/third_party/rust/goblin/src/error.rs
@@ -3,6 +3,7 @@
use alloc::string::String;
use core::fmt;
+use core::num::TryFromIntError;
use core::result;
#[cfg(feature = "std")]
use std::{error, io};
@@ -42,6 +43,12 @@ impl From<io::Error> for Error {
}
}
+impl From<TryFromIntError> for Error {
+ fn from(err: TryFromIntError) -> Error {
+ Error::Malformed(format!("Integer do not fit: {err}"))
+ }
+}
+
impl From<scroll::Error> for Error {
fn from(err: scroll::Error) -> Error {
Error::Scroll(err)
diff --git a/third_party/rust/goblin/src/lib.rs b/third_party/rust/goblin/src/lib.rs
index 25ab841322..ec77f93d5c 100644
--- a/third_party/rust/goblin/src/lib.rs
+++ b/third_party/rust/goblin/src/lib.rs
@@ -42,13 +42,17 @@
//! Object::PE(pe) => {
//! println!("pe: {:#?}", &pe);
//! },
+//! Object::COFF(coff) => {
+//! println!("coff: {:#?}", &coff);
+//! },
//! Object::Mach(mach) => {
//! println!("mach: {:#?}", &mach);
//! },
//! Object::Archive(archive) => {
//! println!("archive: {:#?}", &archive);
//! },
-//! Object::Unknown(magic) => { println!("unknown magic: {:#x}", magic) }
+//! Object::Unknown(magic) => { println!("unknown magic: {:#x}", magic) },
+//! _ => { }
//! }
//! }
//! }
@@ -218,12 +222,14 @@ pub struct HintData {
}
#[derive(Debug)]
+#[non_exhaustive]
/// A hint at the underlying binary format for 16 bytes of arbitrary data
pub enum Hint {
Elf(HintData),
Mach(HintData),
MachFat(usize),
PE,
+ COFF,
Archive,
Unknown(u64),
}
@@ -253,10 +259,14 @@ if_everything! {
Ok(Hint::Elf(HintData { is_lsb, is_64 }))
} else if &bytes[0..archive::SIZEOF_MAGIC] == archive::MAGIC {
Ok(Hint::Archive)
- } else if (&bytes[0..2]).pread_with::<u16>(0, LE)? == pe::header::DOS_MAGIC {
- Ok(Hint::PE)
} else {
- mach::peek_bytes(bytes)
+ match *&bytes[0..2].pread_with::<u16>(0, LE)? {
+ pe::header::DOS_MAGIC => Ok(Hint::PE),
+ pe::header::COFF_MACHINE_X86 |
+ pe::header::COFF_MACHINE_X86_64 |
+ pe::header::COFF_MACHINE_ARM64 => Ok(Hint::COFF),
+ _ => mach::peek_bytes(bytes)
+ }
}
}
@@ -273,12 +283,15 @@ if_everything! {
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
+ #[non_exhaustive]
/// A parseable object that goblin understands
pub enum Object<'a> {
/// An ELF32/ELF64!
Elf(elf::Elf<'a>),
/// A PE32/PE32+!
PE(pe::PE<'a>),
+ /// A COFF
+ COFF(pe::Coff<'a>),
/// A 32/64-bit Mach-o binary _OR_ it is a multi-architecture binary container!
Mach(mach::Mach<'a>),
/// A Unix archive
@@ -296,7 +309,8 @@ if_everything! {
Hint::Mach(_) | Hint::MachFat(_) => Ok(Object::Mach(mach::Mach::parse(bytes)?)),
Hint::Archive => Ok(Object::Archive(archive::Archive::parse(bytes)?)),
Hint::PE => Ok(Object::PE(pe::PE::parse(bytes)?)),
- Hint::Unknown(magic) => Ok(Object::Unknown(magic))
+ Hint::COFF => Ok(Object::COFF(pe::Coff::parse(bytes)?)),
+ Hint::Unknown(magic) => Ok(Object::Unknown(magic)),
}
} else {
Err(error::Error::Malformed(format!("Object is too small.")))
diff --git a/third_party/rust/goblin/src/mach/load_command.rs b/third_party/rust/goblin/src/mach/load_command.rs
index 34a44e2bae..305c3b823f 100644
--- a/third_party/rust/goblin/src/mach/load_command.rs
+++ b/third_party/rust/goblin/src/mach/load_command.rs
@@ -1343,9 +1343,12 @@ pub const PLATFORM_IOSSIMULATOR: u32 = 7;
pub const PLATFORM_TVOSSIMULATOR: u32 = 8;
pub const PLATFORM_WATCHOSSIMULATOR: u32 = 9;
pub const PLATFORM_DRIVERKIT: u32 = 10;
+pub const PLATFORM_VISIONOS: u32 = 11;
+pub const PLATFORM_VISIONOSSIMULATOR: u32 = 12;
pub const TOOL_CLANG: u32 = 1;
pub const TOOL_SWIFT: u32 = 2;
pub const TOOL_LD: u32 = 3;
+pub const TOOL_LLD: u32 = 4;
pub fn cmd_to_str(cmd: u32) -> &'static str {
match cmd {
diff --git a/third_party/rust/goblin/src/pe/authenticode.rs b/third_party/rust/goblin/src/pe/authenticode.rs
index e16b7997cd..0f738e6888 100644
--- a/third_party/rust/goblin/src/pe/authenticode.rs
+++ b/third_party/rust/goblin/src/pe/authenticode.rs
@@ -8,9 +8,13 @@
// - data directory entry for certtable
// - certtable
+use alloc::collections::VecDeque;
use core::ops::Range;
+use log::debug;
-use super::PE;
+use super::{section_table::SectionTable, PE};
+
+static PADDING: [u8; 7] = [0; 7];
impl PE<'_> {
/// [`authenticode_ranges`] returns the various ranges of the binary that are relevant for
@@ -19,6 +23,7 @@ impl PE<'_> {
ExcludedSectionsIter {
pe: self,
state: IterState::default(),
+ sections: VecDeque::default(),
}
}
}
@@ -29,19 +34,22 @@ impl PE<'_> {
pub(super) struct ExcludedSections {
checksum: Range<usize>,
datadir_entry_certtable: Range<usize>,
- certtable: Option<Range<usize>>,
+ certificate_table_size: usize,
+ end_image_header: usize,
}
impl ExcludedSections {
pub(super) fn new(
checksum: Range<usize>,
datadir_entry_certtable: Range<usize>,
- certtable: Option<Range<usize>>,
+ certificate_table_size: usize,
+ end_image_header: usize,
) -> Self {
Self {
checksum,
datadir_entry_certtable,
- certtable,
+ certificate_table_size,
+ end_image_header,
}
}
}
@@ -49,14 +57,26 @@ impl ExcludedSections {
pub struct ExcludedSectionsIter<'s> {
pe: &'s PE<'s>,
state: IterState,
+ sections: VecDeque<SectionTable>,
}
#[derive(Debug, PartialEq)]
enum IterState {
Initial,
- DatadirEntry(usize),
- CertTable(usize),
- Final(usize),
+ ChecksumEnd(usize),
+ CertificateTableEnd(usize),
+ HeaderEnd {
+ end_image_header: usize,
+ sum_of_bytes_hashed: usize,
+ },
+ Sections {
+ tail: usize,
+ sum_of_bytes_hashed: usize,
+ },
+ Final {
+ sum_of_bytes_hashed: usize,
+ },
+ Padding(usize),
Done,
}
@@ -76,24 +96,166 @@ impl<'s> Iterator for ExcludedSectionsIter<'s> {
loop {
match self.state {
IterState::Initial => {
- self.state = IterState::DatadirEntry(sections.checksum.end);
- return Some(&bytes[..sections.checksum.start]);
+ // 3. Hash the image header from its base to immediately before the start of the
+ // checksum address, as specified in Optional Header Windows-Specific Fields.
+ let out = Some(&bytes[..sections.checksum.start]);
+ debug!("hashing {:#x} {:#x}", 0, sections.checksum.start);
+
+ // 4. Skip over the checksum, which is a 4-byte field.
+ debug_assert_eq!(sections.checksum.end - sections.checksum.start, 4);
+ self.state = IterState::ChecksumEnd(sections.checksum.end);
+
+ return out;
+ }
+ IterState::ChecksumEnd(checksum_end) => {
+ // 5. Hash everything from the end of the checksum field to immediately before the start
+ // of the Certificate Table entry, as specified in Optional Header Data Directories.
+ let out =
+ Some(&bytes[checksum_end..sections.datadir_entry_certtable.start]);
+ debug!(
+ "hashing {checksum_end:#x} {:#x}",
+ sections.datadir_entry_certtable.start
+ );
+
+ // 6. Get the Attribute Certificate Table address and size from the Certificate Table entry.
+ // For details, see section 5.7 of the PE/COFF specification.
+ // 7. Exclude the Certificate Table entry from the calculation
+ self.state =
+ IterState::CertificateTableEnd(sections.datadir_entry_certtable.end);
+
+ return out;
+ }
+ IterState::CertificateTableEnd(start) => {
+ // 7. Exclude the Certificate Table entry from the calculation and hash everything from
+ // the end of the Certificate Table entry to the end of image header, including
+ // Section Table (headers). The Certificate Table entry is 8 bytes long, as specified
+ // in Optional Header Data Directories.
+ let end_image_header = sections.end_image_header;
+ let buf = Some(&bytes[start..end_image_header]);
+ debug!("hashing {start:#x} {:#x}", end_image_header - start);
+
+ // 8. Create a counter called SUM_OF_BYTES_HASHED, which is not part of the signature.
+ // Set this counter to the SizeOfHeaders field, as specified in
+ // Optional Header Windows-Specific Field.
+ let sum_of_bytes_hashed = end_image_header;
+
+ self.state = IterState::HeaderEnd {
+ end_image_header,
+ sum_of_bytes_hashed,
+ };
+
+ return buf;
}
- IterState::DatadirEntry(start) => {
- self.state = IterState::CertTable(sections.datadir_entry_certtable.end);
- return Some(&bytes[start..sections.datadir_entry_certtable.start]);
+ IterState::HeaderEnd {
+ end_image_header,
+ sum_of_bytes_hashed,
+ } => {
+ // 9. Build a temporary table of pointers to all of the section headers in the
+ // image. The NumberOfSections field of COFF File Header indicates how big
+ // the table should be. Do not include any section headers in the table whose
+ // SizeOfRawData field is zero.
+
+ // Implementation detail:
+ // We require allocation here because the section table has a variable size and
+ // needs to be sorted.
+ let mut sections: VecDeque<SectionTable> = self
+ .pe
+ .sections
+ .iter()
+ .filter(|section| section.size_of_raw_data != 0)
+ .cloned()
+ .collect();
+
+ // 10. Using the PointerToRawData field (offset 20) in the referenced SectionHeader
+ // structure as a key, arrange the table's elements in ascending order. In
+ // other words, sort the section headers in ascending order according to the
+ // disk-file offset of the sections.
+ sections
+ .make_contiguous()
+ .sort_by_key(|section| section.pointer_to_raw_data);
+
+ self.sections = sections;
+
+ self.state = IterState::Sections {
+ tail: end_image_header,
+ sum_of_bytes_hashed,
+ };
}
- IterState::CertTable(start) => {
- if let Some(certtable) = sections.certtable.as_ref() {
- self.state = IterState::Final(certtable.end);
- return Some(&bytes[start..certtable.start]);
+ IterState::Sections {
+ mut tail,
+ mut sum_of_bytes_hashed,
+ } => {
+ // 11. Walk through the sorted table, load the corresponding section into memory,
+ // and hash the entire section. Use the SizeOfRawData field in the SectionHeader
+ // structure to determine the amount of data to hash.
+ if let Some(section) = self.sections.pop_front() {
+ let start = section.pointer_to_raw_data as usize;
+ let end = start + section.size_of_raw_data as usize;
+ tail = end;
+
+ // 12. Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED.
+ sum_of_bytes_hashed += section.size_of_raw_data as usize;
+
+ debug!("hashing {start:#x} {:#x}", end - start);
+ let buf = &bytes[start..end];
+
+ // 13. Repeat steps 11 and 12 for all of the sections in the sorted table.
+ self.state = IterState::Sections {
+ tail,
+ sum_of_bytes_hashed,
+ };
+
+ return Some(buf);
} else {
- self.state = IterState::Final(start)
+ self.state = IterState::Final {
+ sum_of_bytes_hashed,
+ };
+ }
+ }
+ IterState::Final {
+ sum_of_bytes_hashed,
+ } => {
+ // 14. Create a value called FILE_SIZE, which is not part of the signature.
+ // Set this value to the image’s file size, acquired from the underlying
+ // file system. If FILE_SIZE is greater than SUM_OF_BYTES_HASHED, the
+ // file contains extra data that must be added to the hash. This data
+ // begins at the SUM_OF_BYTES_HASHED file offset, and its length is:
+ // (File Size) - ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED)
+ //
+ // Note: The size of Attribute Certificate Table is specified in the second
+ // ULONG value in the Certificate Table entry (32 bit: offset 132,
+ // 64 bit: offset 148) in Optional Header Data Directories.
+ let file_size = bytes.len();
+
+ // If FILE_SIZE is not a multiple of 8 bytes, the data added to the hash must
+ // be appended with zero padding of length (8 – (FILE_SIZE % 8)) bytes
+ let pad_size = (8 - file_size % 8) % 8;
+ self.state = IterState::Padding(pad_size);
+
+ if file_size > sum_of_bytes_hashed {
+ let extra_data_start = sum_of_bytes_hashed;
+ let len =
+ file_size - sections.certificate_table_size - sum_of_bytes_hashed;
+
+ debug!("hashing {extra_data_start:#x} {len:#x}",);
+ let buf = &bytes[extra_data_start..extra_data_start + len];
+
+ return Some(buf);
}
}
- IterState::Final(start) => {
+ IterState::Padding(pad_size) => {
self.state = IterState::Done;
- return Some(&bytes[start..]);
+
+ if pad_size != 0 {
+ debug!("hashing {pad_size:#x}");
+
+ // NOTE (safety): pad size will be at most 7, and PADDING has a size of 7
+ // pad_size is computed ~10 lines above.
+ debug_assert!(pad_size <= 7);
+ debug_assert_eq!(PADDING.len(), 7);
+
+ return Some(&PADDING[..pad_size]);
+ }
}
IterState::Done => return None,
}
diff --git a/third_party/rust/goblin/src/pe/certificate_table.rs b/third_party/rust/goblin/src/pe/certificate_table.rs
index 353a6c70ce..be45ce48dc 100644
--- a/third_party/rust/goblin/src/pe/certificate_table.rs
+++ b/third_party/rust/goblin/src/pe/certificate_table.rs
@@ -3,12 +3,15 @@
/// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-attribute-certificate-table-image-only
/// https://learn.microsoft.com/en-us/windows/win32/api/wintrust/ns-wintrust-win_certificate
use crate::error;
-use scroll::Pread;
+use scroll::{ctx, Pread, Pwrite};
use alloc::string::ToString;
use alloc::vec::Vec;
+use super::utils::pad;
+
#[repr(u16)]
+#[non_exhaustive]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum AttributeCertificateRevision {
/// WIN_CERT_REVISION_1_0
@@ -38,7 +41,7 @@ impl TryFrom<u16> for AttributeCertificateRevision {
}
#[repr(u16)]
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Copy, Clone)]
pub enum AttributeCertificateType {
/// WIN_CERT_TYPE_X509
X509 = 0x0001,
@@ -127,7 +130,28 @@ impl<'a> AttributeCertificate<'a> {
}
}
+impl<'a> ctx::TryIntoCtx<scroll::Endian> for &AttributeCertificate<'a> {
+ type Error = error::Error;
+
+ /// Writes an aligned attribute certificate in the buffer.
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let offset = &mut 0;
+ bytes.gwrite_with(self.length, offset, ctx)?;
+ bytes.gwrite_with(self.revision as u16, offset, ctx)?;
+ bytes.gwrite_with(self.certificate_type as u16, offset, ctx)?;
+ // Extend by zero the buffer until it is aligned on a quadword (16 bytes).
+ let maybe_certificate_padding = pad(self.certificate.len(), Some(16usize));
+ bytes.gwrite(self.certificate, offset)?;
+ if let Some(cert_padding) = maybe_certificate_padding {
+ bytes.gwrite(&cert_padding[..], offset)?;
+ }
+
+ Ok(*offset)
+ }
+}
+
pub type CertificateDirectoryTable<'a> = Vec<AttributeCertificate<'a>>;
+
pub(crate) fn enumerate_certificates(
bytes: &[u8],
table_virtual_address: u32,
diff --git a/third_party/rust/goblin/src/pe/data_directories.rs b/third_party/rust/goblin/src/pe/data_directories.rs
index 265e4e27f7..e65db5953d 100644
--- a/third_party/rust/goblin/src/pe/data_directories.rs
+++ b/third_party/rust/goblin/src/pe/data_directories.rs
@@ -1,5 +1,8 @@
use crate::error;
-use scroll::{Pread, Pwrite, SizeWith};
+use scroll::{
+ ctx::{self},
+ Pread, Pwrite, SizeWith,
+};
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)]
@@ -13,14 +16,86 @@ const NUM_DATA_DIRECTORIES: usize = 16;
impl DataDirectory {
pub fn parse(bytes: &[u8], offset: &mut usize) -> error::Result<Self> {
- let dd = bytes.gread_with(offset, scroll::LE)?;
- Ok(dd)
+ Ok(bytes.gread_with(offset, scroll::LE)?)
+ }
+}
+
+#[derive(Debug, PartialEq, Copy, Clone)]
+pub enum DataDirectoryType {
+ ExportTable,
+ ImportTable,
+ ResourceTable,
+ ExceptionTable,
+ CertificateTable,
+ BaseRelocationTable,
+ DebugTable,
+ Architecture,
+ GlobalPtr,
+ TlsTable,
+ LoadConfigTable,
+ BoundImportTable,
+ ImportAddressTable,
+ DelayImportDescriptor,
+ ClrRuntimeHeader,
+}
+
+impl TryFrom<usize> for DataDirectoryType {
+ type Error = error::Error;
+ fn try_from(value: usize) -> Result<Self, Self::Error> {
+ Ok(match value {
+ 0 => Self::ExportTable,
+ 1 => Self::ImportTable,
+ 2 => Self::ResourceTable,
+ 3 => Self::ExceptionTable,
+ 4 => Self::CertificateTable,
+ 5 => Self::BaseRelocationTable,
+ 6 => Self::DebugTable,
+ 7 => Self::Architecture,
+ 8 => Self::GlobalPtr,
+ 9 => Self::TlsTable,
+ 10 => Self::LoadConfigTable,
+ 11 => Self::BoundImportTable,
+ 12 => Self::ImportAddressTable,
+ 13 => Self::DelayImportDescriptor,
+ 14 => Self::ClrRuntimeHeader,
+ _ => {
+ return Err(error::Error::Malformed(
+ "Wrong data directory index number".into(),
+ ))
+ }
+ })
}
}
#[derive(Debug, PartialEq, Copy, Clone, Default)]
pub struct DataDirectories {
- pub data_directories: [Option<DataDirectory>; NUM_DATA_DIRECTORIES],
+ pub data_directories: [Option<(usize, DataDirectory)>; NUM_DATA_DIRECTORIES],
+}
+
+impl ctx::TryIntoCtx<scroll::Endian> for DataDirectories {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let offset = &mut 0;
+ for opt_dd in self.data_directories {
+ if let Some((dd_offset, dd)) = opt_dd {
+ bytes.pwrite_with(dd, dd_offset, ctx)?;
+ *offset += dd_offset;
+ } else {
+ bytes.gwrite(&[0; SIZEOF_DATA_DIRECTORY][..], offset)?;
+ }
+ }
+ Ok(NUM_DATA_DIRECTORIES * SIZEOF_DATA_DIRECTORY)
+ }
+}
+
+macro_rules! build_dd_getter {
+ ($dd_name:tt, $index:tt) => {
+ pub fn $dd_name(&self) -> Option<&DataDirectory> {
+ let idx = $index;
+ self.data_directories[idx].as_ref().map(|(_, dd)| dd)
+ }
+ };
}
impl DataDirectories {
@@ -37,70 +112,42 @@ impl DataDirectories {
let dd = if dd.virtual_address == 0 && dd.size == 0 {
None
} else {
- Some(dd)
+ Some((*offset, dd))
};
*dir = dd;
}
Ok(DataDirectories { data_directories })
}
- pub fn get_export_table(&self) -> &Option<DataDirectory> {
- let idx = 0;
- &self.data_directories[idx]
- }
- pub fn get_import_table(&self) -> &Option<DataDirectory> {
- let idx = 1;
- &self.data_directories[idx]
- }
- pub fn get_resource_table(&self) -> &Option<DataDirectory> {
- let idx = 2;
- &self.data_directories[idx]
- }
- pub fn get_exception_table(&self) -> &Option<DataDirectory> {
- let idx = 3;
- &self.data_directories[idx]
- }
- pub fn get_certificate_table(&self) -> &Option<DataDirectory> {
- let idx = 4;
- &self.data_directories[idx]
- }
- pub fn get_base_relocation_table(&self) -> &Option<DataDirectory> {
- let idx = 5;
- &self.data_directories[idx]
- }
- pub fn get_debug_table(&self) -> &Option<DataDirectory> {
- let idx = 6;
- &self.data_directories[idx]
- }
- pub fn get_architecture(&self) -> &Option<DataDirectory> {
- let idx = 7;
- &self.data_directories[idx]
- }
- pub fn get_global_ptr(&self) -> &Option<DataDirectory> {
- let idx = 8;
- &self.data_directories[idx]
- }
- pub fn get_tls_table(&self) -> &Option<DataDirectory> {
- let idx = 9;
- &self.data_directories[idx]
- }
- pub fn get_load_config_table(&self) -> &Option<DataDirectory> {
- let idx = 10;
- &self.data_directories[idx]
- }
- pub fn get_bound_import_table(&self) -> &Option<DataDirectory> {
- let idx = 11;
- &self.data_directories[idx]
- }
- pub fn get_import_address_table(&self) -> &Option<DataDirectory> {
- let idx = 12;
- &self.data_directories[idx]
- }
- pub fn get_delay_import_descriptor(&self) -> &Option<DataDirectory> {
- let idx = 13;
- &self.data_directories[idx]
- }
- pub fn get_clr_runtime_header(&self) -> &Option<DataDirectory> {
- let idx = 14;
- &self.data_directories[idx]
+
+ build_dd_getter!(get_export_table, 0);
+ build_dd_getter!(get_import_table, 1);
+ build_dd_getter!(get_resource_table, 2);
+ build_dd_getter!(get_exception_table, 3);
+ build_dd_getter!(get_certificate_table, 4);
+ build_dd_getter!(get_base_relocation_table, 5);
+ build_dd_getter!(get_debug_table, 6);
+ build_dd_getter!(get_architecture, 7);
+ build_dd_getter!(get_global_ptr, 8);
+ build_dd_getter!(get_tls_table, 9);
+ build_dd_getter!(get_load_config_table, 10);
+ build_dd_getter!(get_bound_import_table, 11);
+ build_dd_getter!(get_import_address_table, 12);
+ build_dd_getter!(get_delay_import_descriptor, 13);
+ build_dd_getter!(get_clr_runtime_header, 14);
+
+ pub fn dirs(&self) -> impl Iterator<Item = (DataDirectoryType, DataDirectory)> {
+ self.data_directories
+ .into_iter()
+ .enumerate()
+ // (Index, Option<DD>) -> Option<(Index, DD)> -> (DDT, DD)
+ .filter_map(|(i, o)|
+ // We should not have invalid indexes.
+ // Indeed: `data_directories: &[_; N]` where N is the number
+ // of data directories.
+ // The `TryFrom` trait for integers to DataDirectoryType
+ // takes into account the N possible data directories.
+ // Therefore, the unwrap can never fail as long as Rust guarantees
+ // on types are honored.
+ o.map(|(_, v)| (i.try_into().unwrap(), v)))
}
}
diff --git a/third_party/rust/goblin/src/pe/header.rs b/third_party/rust/goblin/src/pe/header.rs
index c1ded9dd9f..06e23c0b03 100644
--- a/third_party/rust/goblin/src/pe/header.rs
+++ b/third_party/rust/goblin/src/pe/header.rs
@@ -3,24 +3,60 @@ use crate::pe::{optional_header, section_table, symbol};
use crate::strtab;
use alloc::vec::Vec;
use log::debug;
-use scroll::{IOread, IOwrite, Pread, Pwrite, SizeWith};
+use scroll::{ctx, IOread, IOwrite, Pread, Pwrite, SizeWith};
/// DOS header present in all PE binaries
#[repr(C)]
-#[derive(Debug, PartialEq, Copy, Clone, Default)]
+#[derive(Debug, PartialEq, Copy, Clone, Default, Pwrite)]
pub struct DosHeader {
/// Magic number: 5a4d
pub signature: u16,
- /// Pointer to PE header, always at offset 0x3c
+ /// e_cblp
+ pub bytes_on_last_page: u16,
+ /// e_cp
+ pub pages_in_file: u16,
+ /// e_crlc
+ pub relocations: u16,
+ /// e_cparhdr
+ pub size_of_header_in_paragraphs: u16,
+ /// e_minalloc
+ pub minimum_extra_paragraphs_needed: u16,
+ /// e_maxalloc
+ pub maximum_extra_paragraphs_needed: u16,
+ /// e_ss
+ pub initial_relative_ss: u16,
+ /// e_sp
+ pub initial_sp: u16,
+ /// e_csum
+ pub checksum: u16,
+ /// e_ip
+ pub initial_ip: u16,
+ /// e_cs
+ pub initial_relative_cs: u16,
+ /// e_lfarlc
+ pub file_address_of_relocation_table: u16,
+ /// e_ovno
+ pub overlay_number: u16,
+ /// e_res[4]
+ pub reserved: [u16; 4],
+ /// e_oemid
+ pub oem_id: u16,
+ /// e_oeminfo
+ pub oem_info: u16,
+ /// e_res2[10]
+ pub reserved2: [u16; 10],
+ /// e_lfanew: pointer to PE header, always at offset 0x3c
pub pe_pointer: u32,
}
pub const DOS_MAGIC: u16 = 0x5a4d;
pub const PE_POINTER_OFFSET: u32 = 0x3c;
+pub const DOS_STUB_OFFSET: u32 = PE_POINTER_OFFSET + (core::mem::size_of::<u32>() as u32);
impl DosHeader {
pub fn parse(bytes: &[u8]) -> error::Result<Self> {
- let signature = bytes.pread_with(0, scroll::LE).map_err(|_| {
+ let mut offset = 0;
+ let signature = bytes.gread_with(&mut offset, scroll::LE).map_err(|_| {
error::Error::Malformed(format!("cannot parse DOS signature (offset {:#x})", 0))
})?;
if signature != DOS_MAGIC {
@@ -29,6 +65,33 @@ impl DosHeader {
signature
)));
}
+
+ let bytes_on_last_page = bytes.gread_with(&mut offset, scroll::LE)?;
+ let pages_in_file = bytes.gread_with(&mut offset, scroll::LE)?;
+ let relocations = bytes.gread_with(&mut offset, scroll::LE)?;
+ let size_of_header_in_paragraphs = bytes.gread_with(&mut offset, scroll::LE)?;
+ let minimum_extra_paragraphs_needed = bytes.gread_with(&mut offset, scroll::LE)?;
+ let maximum_extra_paragraphs_needed = bytes.gread_with(&mut offset, scroll::LE)?;
+ let initial_relative_ss = bytes.gread_with(&mut offset, scroll::LE)?;
+ let initial_sp = bytes.gread_with(&mut offset, scroll::LE)?;
+ let checksum = bytes.gread_with(&mut offset, scroll::LE)?;
+ let initial_ip = bytes.gread_with(&mut offset, scroll::LE)?;
+ let initial_relative_cs = bytes.gread_with(&mut offset, scroll::LE)?;
+ let file_address_of_relocation_table = bytes.gread_with(&mut offset, scroll::LE)?;
+ let overlay_number = bytes.gread_with(&mut offset, scroll::LE)?;
+ let reserved = [0x0; 4];
+ offset += core::mem::size_of_val(&reserved);
+ let oem_id = bytes.gread_with(&mut offset, scroll::LE)?;
+ let oem_info = bytes.gread_with(&mut offset, scroll::LE)?;
+ let reserved2 = [0x0; 10];
+ offset += core::mem::size_of_val(&reserved2);
+
+ debug_assert!(
+ offset == PE_POINTER_OFFSET as usize,
+ "expected offset ({:#x}) after reading DOS header to be at 0x3C",
+ offset
+ );
+
let pe_pointer = bytes
.pread_with(PE_POINTER_OFFSET as usize, scroll::LE)
.map_err(|_| {
@@ -37,6 +100,7 @@ impl DosHeader {
PE_POINTER_OFFSET
))
})?;
+
let pe_signature: u32 =
bytes
.pread_with(pe_pointer as usize, scroll::LE)
@@ -52,13 +116,48 @@ impl DosHeader {
pe_signature
)));
}
+
Ok(DosHeader {
signature,
+ bytes_on_last_page,
+ pages_in_file,
+ relocations,
+ size_of_header_in_paragraphs,
+ minimum_extra_paragraphs_needed,
+ maximum_extra_paragraphs_needed,
+ initial_relative_ss,
+ initial_sp,
+ checksum,
+ initial_ip,
+ initial_relative_cs,
+ file_address_of_relocation_table,
+ overlay_number,
+ reserved,
+ oem_id,
+ oem_info,
+ reserved2,
pe_pointer,
})
}
}
+#[repr(C)]
+#[derive(Debug, PartialEq, Copy, Clone, Pread, Pwrite)]
+/// The DOS stub program which should be executed in DOS mode
+pub struct DosStub(pub [u8; 0x40]);
+impl Default for DosStub {
+ fn default() -> Self {
+ // "This program cannot be run in DOS mode" error program
+ Self([
+ 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,
+ ])
+ }
+}
+
/// COFF Header
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, IOread, IOwrite, SizeWith)]
@@ -163,14 +262,24 @@ impl CoffHeader {
}
/// Return the COFF symbol table.
- pub fn symbols<'a>(&self, bytes: &'a [u8]) -> error::Result<symbol::SymbolTable<'a>> {
+ pub fn symbols<'a>(&self, bytes: &'a [u8]) -> error::Result<Option<symbol::SymbolTable<'a>>> {
let offset = self.pointer_to_symbol_table as usize;
let number = self.number_of_symbol_table as usize;
- symbol::SymbolTable::parse(bytes, offset, number)
+ if offset == 0 {
+ Ok(None)
+ } else {
+ symbol::SymbolTable::parse(bytes, offset, number).map(Some)
+ }
}
/// Return the COFF string table.
- pub fn strings<'a>(&self, bytes: &'a [u8]) -> error::Result<strtab::Strtab<'a>> {
+ pub fn strings<'a>(&self, bytes: &'a [u8]) -> error::Result<Option<strtab::Strtab<'a>>> {
+ // > The file offset of the COFF symbol table, or zero if no COFF symbol table is present.
+ // > This value should be zero for an image because COFF debugging information is deprecated.
+ if self.pointer_to_symbol_table == 0 {
+ return Ok(None);
+ }
+
let mut offset = self.pointer_to_symbol_table as usize
+ symbol::SymbolTable::size(self.number_of_symbol_table as usize);
@@ -180,13 +289,15 @@ impl CoffHeader {
// The offset needs to be advanced in order to read the strings.
offset += length_field_size;
- Ok(strtab::Strtab::parse(bytes, offset, length, 0)?)
+ Ok(Some(strtab::Strtab::parse(bytes, offset, length, 0)?))
}
}
#[derive(Debug, PartialEq, Copy, Clone, Default)]
pub struct Header {
pub dos_header: DosHeader,
+ /// DOS program for legacy loaders
+ pub dos_stub: DosStub,
/// PE Magic: PE\0\0, little endian
pub signature: u32,
pub coff_header: CoffHeader,
@@ -196,6 +307,12 @@ pub struct Header {
impl Header {
pub fn parse(bytes: &[u8]) -> error::Result<Self> {
let dos_header = DosHeader::parse(&bytes)?;
+ let dos_stub = bytes.pread(DOS_STUB_OFFSET as usize).map_err(|_| {
+ error::Error::Malformed(format!(
+ "cannot parse DOS stub (offset {:#x})",
+ DOS_STUB_OFFSET
+ ))
+ })?;
let mut offset = dos_header.pe_pointer as usize;
let signature = bytes.gread_with(&mut offset, scroll::LE).map_err(|_| {
error::Error::Malformed(format!("cannot parse PE signature (offset {:#x})", offset))
@@ -208,6 +325,7 @@ impl Header {
};
Ok(Header {
dos_header,
+ dos_stub,
signature,
coff_header,
optional_header,
@@ -215,6 +333,22 @@ impl Header {
}
}
+impl ctx::TryIntoCtx<scroll::Endian> for Header {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let offset = &mut 0;
+ bytes.gwrite_with(self.dos_header, offset, ctx)?;
+ bytes.gwrite_with(self.dos_stub, offset, ctx)?;
+ bytes.gwrite_with(self.signature, offset, scroll::LE)?;
+ bytes.gwrite_with(self.coff_header, offset, ctx)?;
+ if let Some(opt_header) = self.optional_header {
+ bytes.gwrite_with(opt_header, offset, ctx)?;
+ }
+ Ok(*offset)
+ }
+}
+
/// Convert machine to str representation
pub fn machine_to_str(machine: u16) -> &'static str {
match machine {
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())
diff --git a/third_party/rust/goblin/src/pe/optional_header.rs b/third_party/rust/goblin/src/pe/optional_header.rs
index b1d3192764..852f4dced0 100644
--- a/third_party/rust/goblin/src/pe/optional_header.rs
+++ b/third_party/rust/goblin/src/pe/optional_header.rs
@@ -71,6 +71,22 @@ impl From<StandardFields32> for StandardFields {
}
}
+impl From<StandardFields> for StandardFields32 {
+ fn from(fields: StandardFields) -> Self {
+ StandardFields32 {
+ magic: fields.magic,
+ major_linker_version: fields.major_linker_version,
+ minor_linker_version: fields.minor_linker_version,
+ size_of_code: fields.size_of_code as u32,
+ size_of_initialized_data: fields.size_of_initialized_data as u32,
+ size_of_uninitialized_data: fields.size_of_uninitialized_data as u32,
+ address_of_entry_point: fields.address_of_entry_point as u32,
+ base_of_code: fields.base_of_code as u32,
+ base_of_data: fields.base_of_data,
+ }
+ }
+}
+
impl From<StandardFields64> for StandardFields {
fn from(fields: StandardFields64) -> Self {
StandardFields {
@@ -87,6 +103,21 @@ impl From<StandardFields64> for StandardFields {
}
}
+impl From<StandardFields> for StandardFields64 {
+ fn from(fields: StandardFields) -> Self {
+ StandardFields64 {
+ magic: fields.magic,
+ major_linker_version: fields.major_linker_version,
+ minor_linker_version: fields.minor_linker_version,
+ size_of_code: fields.size_of_code as u32,
+ size_of_initialized_data: fields.size_of_initialized_data as u32,
+ size_of_uninitialized_data: fields.size_of_uninitialized_data as u32,
+ address_of_entry_point: fields.address_of_entry_point as u32,
+ base_of_code: fields.base_of_code as u32,
+ }
+ }
+}
+
/// Standard fields magic number for 32-bit binary
pub const MAGIC_32: u16 = 0x10b;
/// Standard fields magic number for 64-bit binary
@@ -208,6 +239,36 @@ impl From<WindowsFields32> for WindowsFields {
}
}
+impl TryFrom<WindowsFields64> for WindowsFields32 {
+ type Error = crate::error::Error;
+
+ fn try_from(value: WindowsFields64) -> Result<Self, Self::Error> {
+ Ok(WindowsFields32 {
+ image_base: value.image_base.try_into()?,
+ section_alignment: value.section_alignment,
+ file_alignment: value.file_alignment,
+ major_operating_system_version: value.major_operating_system_version,
+ minor_operating_system_version: value.minor_operating_system_version,
+ major_image_version: value.major_image_version,
+ minor_image_version: value.minor_image_version,
+ major_subsystem_version: value.major_subsystem_version,
+ minor_subsystem_version: value.minor_subsystem_version,
+ win32_version_value: value.win32_version_value,
+ size_of_image: value.size_of_image,
+ size_of_headers: value.size_of_headers,
+ check_sum: value.check_sum,
+ subsystem: value.subsystem,
+ dll_characteristics: value.dll_characteristics,
+ size_of_stack_reserve: value.size_of_stack_reserve.try_into()?,
+ size_of_stack_commit: value.size_of_stack_commit.try_into()?,
+ size_of_heap_reserve: value.size_of_heap_reserve.try_into()?,
+ size_of_heap_commit: value.size_of_heap_commit.try_into()?,
+ loader_flags: value.loader_flags,
+ number_of_rva_and_sizes: value.number_of_rva_and_sizes,
+ })
+ }
+}
+
// impl From<WindowsFields32> for WindowsFields {
// fn from(windows: WindowsFields32) -> Self {
// WindowsFields {
@@ -289,6 +350,28 @@ impl<'a> ctx::TryFromCtx<'a, Endian> for OptionalHeader {
}
}
+impl ctx::TryIntoCtx<scroll::Endian> for OptionalHeader {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let offset = &mut 0;
+ match self.standard_fields.magic {
+ MAGIC_32 => {
+ bytes.gwrite_with::<StandardFields32>(self.standard_fields.into(), offset, ctx)?;
+ bytes.gwrite_with(WindowsFields32::try_from(self.windows_fields)?, offset, ctx)?;
+ bytes.gwrite_with(self.data_directories, offset, ctx)?;
+ }
+ MAGIC_64 => {
+ bytes.gwrite_with::<StandardFields64>(self.standard_fields.into(), offset, ctx)?;
+ bytes.gwrite_with(self.windows_fields, offset, ctx)?;
+ bytes.gwrite_with(self.data_directories, offset, ctx)?;
+ }
+ _ => panic!(),
+ }
+ Ok(*offset)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/third_party/rust/goblin/src/pe/options.rs b/third_party/rust/goblin/src/pe/options.rs
index 5fea632f75..4461a4c7bd 100644
--- a/third_party/rust/goblin/src/pe/options.rs
+++ b/third_party/rust/goblin/src/pe/options.rs
@@ -3,11 +3,19 @@
pub struct ParseOptions {
/// Wether the parser should resolve rvas or not. Default: true
pub resolve_rva: bool,
+ /// Whether or not to parse attribute certificates.
+ /// Set to false for in-memory representation, as the [loader does not map this info into
+ /// memory](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#other-contents-of-the-file).
+ /// For on-disk representations, leave as true. Default: true
+ pub parse_attribute_certificates: bool,
}
impl ParseOptions {
/// Returns a parse options structure with default values
pub fn default() -> Self {
- ParseOptions { resolve_rva: true }
+ ParseOptions {
+ resolve_rva: true,
+ parse_attribute_certificates: true,
+ }
}
}
diff --git a/third_party/rust/goblin/src/pe/section_table.rs b/third_party/rust/goblin/src/pe/section_table.rs
index 4491827f16..f49c2b689c 100644
--- a/third_party/rust/goblin/src/pe/section_table.rs
+++ b/third_party/rust/goblin/src/pe/section_table.rs
@@ -1,6 +1,8 @@
use crate::error::{self, Error};
use crate::pe::relocation;
+use alloc::borrow::Cow;
use alloc::string::{String, ToString};
+use alloc::vec::Vec;
use scroll::{ctx, Pread, Pwrite};
#[repr(C)]
@@ -79,6 +81,32 @@ impl SectionTable {
Ok(table)
}
+ pub fn data<'a, 'b: 'a>(&'a self, pe_bytes: &'b [u8]) -> error::Result<Option<Cow<[u8]>>> {
+ let section_start: usize = self.pointer_to_raw_data.try_into().map_err(|_| {
+ Error::Malformed(format!("Virtual address cannot fit in platform `usize`"))
+ })?;
+
+ // assert!(self.virtual_size <= self.size_of_raw_data);
+ // if vsize > size_of_raw_data, the section is zero padded.
+ let section_end: usize = section_start
+ + usize::try_from(self.size_of_raw_data).map_err(|_| {
+ Error::Malformed(format!("Virtual size cannot fit in platform `usize`"))
+ })?;
+
+ let original_bytes = pe_bytes.get(section_start..section_end).map(Cow::Borrowed);
+
+ if original_bytes.is_some() && self.virtual_size > self.size_of_raw_data {
+ let mut bytes: Vec<u8> = Vec::new();
+ bytes.resize(self.size_of_raw_data.try_into()?, 0);
+ bytes.copy_from_slice(&original_bytes.unwrap());
+ bytes.resize(self.virtual_size.try_into()?, 0);
+
+ Ok(Some(Cow::Owned(bytes)))
+ } else {
+ Ok(original_bytes)
+ }
+ }
+
pub fn name_offset(&self) -> error::Result<Option<usize>> {
// Based on https://github.com/llvm-mirror/llvm/blob/af7b1832a03ab6486c42a40d21695b2c03b2d8a3/lib/Object/COFFObjectFile.cpp#L1054
if self.name[0] == b'/' {
@@ -163,6 +191,15 @@ impl SectionTable {
let number = self.number_of_relocations as usize;
relocation::Relocations::parse(bytes, offset, number)
}
+
+ /// Tests if `another_section` on-disk ranges will collide.
+ pub fn overlaps_with(&self, another_section: &SectionTable) -> bool {
+ let self_end = self.pointer_to_raw_data + self.size_of_raw_data;
+ let another_end = another_section.pointer_to_raw_data + another_section.size_of_raw_data;
+
+ !((self_end <= another_section.pointer_to_raw_data)
+ || (another_end <= self.pointer_to_raw_data))
+ }
}
impl ctx::SizeWith<scroll::Endian> for SectionTable {
@@ -171,7 +208,7 @@ impl ctx::SizeWith<scroll::Endian> for SectionTable {
}
}
-impl ctx::TryIntoCtx<scroll::Endian> for SectionTable {
+impl ctx::TryIntoCtx<scroll::Endian> for &SectionTable {
type Error = error::Error;
fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
let offset = &mut 0;
@@ -189,7 +226,7 @@ impl ctx::TryIntoCtx<scroll::Endian> for SectionTable {
}
}
-impl ctx::IntoCtx<scroll::Endian> for SectionTable {
+impl ctx::IntoCtx<scroll::Endian> for &SectionTable {
fn into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) {
bytes.pwrite_with(self, 0, ctx).unwrap();
}
diff --git a/third_party/rust/goblin/src/pe/symbol.rs b/third_party/rust/goblin/src/pe/symbol.rs
index b2ced808a1..e5aba25bec 100644
--- a/third_party/rust/goblin/src/pe/symbol.rs
+++ b/third_party/rust/goblin/src/pe/symbol.rs
@@ -412,6 +412,7 @@ pub struct AuxSectionDefinition {
}
/// A COFF symbol table.
+// TODO: #[derive(Pwrite)] produce unparseable tokens
pub struct SymbolTable<'a> {
symbols: &'a [u8],
}
@@ -483,6 +484,14 @@ impl<'a> SymbolTable<'a> {
}
}
+impl<'a> ctx::TryIntoCtx<scroll::Endian> for SymbolTable<'a> {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], _ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ bytes.pwrite(self.symbols, 0).map_err(|err| err.into())
+ }
+}
+
impl<'a> Debug for SymbolTable<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("SymbolTable")
diff --git a/third_party/rust/goblin/src/pe/utils.rs b/third_party/rust/goblin/src/pe/utils.rs
index 07a320d57b..289ccc529b 100644
--- a/third_party/rust/goblin/src/pe/utils.rs
+++ b/third_party/rust/goblin/src/pe/utils.rs
@@ -1,5 +1,6 @@
use crate::error;
use alloc::string::ToString;
+use alloc::vec::Vec;
use scroll::Pread;
use super::options;
@@ -178,3 +179,18 @@ where
let result: T = bytes.pread_with(offset, scroll::LE)?;
Ok(result)
}
+
+pub(crate) fn pad(length: usize, alignment: Option<usize>) -> Option<Vec<u8>> {
+ match alignment {
+ Some(alignment) => {
+ let overhang = length % alignment;
+ if overhang != 0 {
+ let repeat = alignment - overhang;
+ Some(vec![0u8; repeat])
+ } else {
+ None
+ }
+ }
+ None => None,
+ }
+}
diff --git a/third_party/rust/goblin/src/strtab.rs b/third_party/rust/goblin/src/strtab.rs
index dc7b8080f0..487d8d4279 100644
--- a/third_party/rust/goblin/src/strtab.rs
+++ b/third_party/rust/goblin/src/strtab.rs
@@ -34,6 +34,11 @@ impl<'a> Strtab<'a> {
Self::from_slice_unparsed(bytes, 0, bytes.len(), delim)
}
+ /// Returns the length of this `Strtab` in bytes
+ pub fn len(&self) -> usize {
+ self.bytes.len()
+ }
+
/// Creates a `Strtab` directly without bounds check and without parsing it.
///
/// This is potentially unsafe and should only be used if `feature = "alloc"` is disabled.