From a3a63373a4c8879dfcbf7fbfccd0fc78d26c66af Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Fri, 24 Feb 2023 07:36:46 +0100 Subject: [PATCH] pe: support basic certificates enumeration --- src/pe/certificate_table.rs | 156 ++++++++++++++++++++++++++++++++++++ src/pe/mod.rs | 15 ++++ 2 files changed, 171 insertions(+) create mode 100644 src/pe/certificate_table.rs diff --git a/src/pe/certificate_table.rs b/src/pe/certificate_table.rs new file mode 100644 index 000000000..d873738be --- /dev/null +++ b/src/pe/certificate_table.rs @@ -0,0 +1,156 @@ +/// Implements parsing of pe32's Attribute Certificate Table +/// See reference: +/// 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 alloc::string::ToString; +use alloc::vec::Vec; + +#[repr(u16)] +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum AttributeCertificateRevision { + /// WIN_CERT_REVISION_1_0 + Revision1_0 = 0x0100, + /// WIN_CERT_REVISION_2_0 + Revision2_0 = 0x0200, +} + +impl TryFrom for AttributeCertificateRevision { + type Error = error::Error; + + fn try_from(value: u16) -> Result { + Ok(match value { + x if x == AttributeCertificateRevision::Revision1_0 as u16 => { + AttributeCertificateRevision::Revision1_0 + } + x if x == AttributeCertificateRevision::Revision2_0 as u16 => { + AttributeCertificateRevision::Revision2_0 + } + _ => { + return Err(error::Error::Malformed( + "Invalid certificate attribute revision".to_string(), + )) + } + }) + } +} + +#[repr(u16)] +#[derive(Debug)] +pub enum AttributeCertificateType { + /// WIN_CERT_TYPE_X509 + X509 = 0x0001, + /// WIN_CERT_TYPE_PKCS_SIGNED_DATA + PkcsSignedData = 0x0002, + /// WIN_CERT_TYPE_RESERVED_1 + Reserved1 = 0x0003, + /// WIN_CERT_TYPE_TS_STACK_SIGNED + TsStackSigned = 0x0004, +} + +impl TryFrom for AttributeCertificateType { + type Error = error::Error; + + fn try_from(value: u16) -> Result { + Ok(match value { + x if x == AttributeCertificateType::X509 as u16 => AttributeCertificateType::X509, + x if x == AttributeCertificateType::PkcsSignedData as u16 => { + AttributeCertificateType::PkcsSignedData + } + x if x == AttributeCertificateType::Reserved1 as u16 => { + AttributeCertificateType::Reserved1 + } + x if x == AttributeCertificateType::TsStackSigned as u16 => { + AttributeCertificateType::TsStackSigned + } + _ => { + return Err(error::Error::Malformed( + "Invalid attribute certificate type".to_string(), + )) + } + }) + } +} + +#[derive(Clone, Pread)] +struct AttributeCertificateHeader { + /// dwLength + length: u32, + revision: u16, + certificate_type: u16, +} + +const CERTIFICATE_DATA_OFFSET: u32 = 8; +#[derive(Debug)] +pub struct AttributeCertificate<'a> { + pub length: u32, + pub revision: AttributeCertificateRevision, + pub certificate_type: AttributeCertificateType, + pub certificate: &'a [u8], +} + +impl<'a> AttributeCertificate<'a> { + pub fn parse( + bytes: &'a [u8], + current_offset: &mut usize, + ) -> Result, error::Error> { + // `current_offset` is moved sizeof(AttributeCertificateHeader) = 8 bytes further. + let header: AttributeCertificateHeader = bytes.gread_with(current_offset, scroll::LE)?; + let cert_size = usize::try_from(header.length.saturating_sub(CERTIFICATE_DATA_OFFSET)) + .map_err(|_err| { + error::Error::Malformed( + "Attribute certificate size do not fit in usize".to_string(), + ) + })?; + let attr = Self { + length: header.length, + revision: header.revision.try_into()?, + certificate_type: header.certificate_type.try_into()?, + certificate: &bytes[*current_offset..(*current_offset + cert_size)], + }; + // Moving past the certificate data. + // Prevent the current_offset to wrap and ensure current_offset is strictly increasing. + *current_offset = current_offset.saturating_add(cert_size); + // Round to the next 8-bytes. + *current_offset = (*current_offset + 7) & !7; + Ok(attr) + } +} + +pub type CertificateDirectoryTable<'a> = Vec>; +pub(crate) fn enumerate_certificates( + bytes: &[u8], + table_virtual_address: u32, + table_size: u32, +) -> Result { + let table_start_offset = usize::try_from(table_virtual_address).map_err(|_err| { + error::Error::Malformed("Certificate table RVA do not fit in a usize".to_string()) + })?; + // Here, we do not want wrapping semantics as it means that a too big table size or table start + // offset will provide table_end_offset such that table_end_offset < table_start_offset, which + // is not desirable at all. + let table_end_offset = + table_start_offset.saturating_add(usize::try_from(table_size).map_err(|_err| { + error::Error::Malformed("Certificate table size do not fit in a usize".to_string()) + })?); + let mut current_offset = table_start_offset; + let mut attrs = vec![]; + + // End offset cannot be further than the binary we have at hand. + if table_end_offset >= bytes.len() { + return Err(error::Error::Malformed( + "End of attribute certificates table is after the end of the PE binary".to_string(), + )); + } + + // This is guaranteed to terminate, either by a malformed error being returned + // or because current_offset >= table_end_offset by virtue of current_offset being strictly + // increasing through `AttributeCertificate::parse`. + while current_offset < table_end_offset { + attrs.push(AttributeCertificate::parse(bytes, &mut current_offset)?); + } + + Ok(attrs) +} diff --git a/src/pe/mod.rs b/src/pe/mod.rs index 72a5c8e53..f0df3c7ef 100644 --- a/src/pe/mod.rs +++ b/src/pe/mod.rs @@ -5,6 +5,7 @@ use alloc::vec::Vec; +pub mod certificate_table; pub mod characteristic; pub mod data_directories; pub mod debug; @@ -58,6 +59,8 @@ pub struct PE<'a> { 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> { @@ -86,6 +89,7 @@ impl<'a> PE<'a> { 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 { entry = optional_header.standard_fields.address_of_entry_point as usize; @@ -177,6 +181,16 @@ impl<'a> PE<'a> { )?); } } + + 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, + )?; + } } Ok(PE { header, @@ -194,6 +208,7 @@ impl<'a> PE<'a> { libraries, debug_data, exception_data, + certificates, }) } }