Skip to content

Commit

Permalink
pe: support basic certificates enumeration
Browse files Browse the repository at this point in the history
  • Loading branch information
RaitoBezarius authored and m4b committed Mar 5, 2023
1 parent 4c99ae8 commit 170395f
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 0 deletions.
156 changes: 156 additions & 0 deletions src/pe/certificate_table.rs
Original file line number Diff line number Diff line change
@@ -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<u16> for AttributeCertificateRevision {
type Error = error::Error;

fn try_from(value: u16) -> Result<Self, Self::Error> {
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<u16> for AttributeCertificateType {
type Error = error::Error;

fn try_from(value: u16) -> Result<Self, Self::Error> {
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<AttributeCertificate<'a>, 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<AttributeCertificate<'a>>;
pub(crate) fn enumerate_certificates(
bytes: &[u8],
table_virtual_address: u32,
table_size: u32,
) -> Result<CertificateDirectoryTable, error::Error> {
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)
}
15 changes: 15 additions & 0 deletions src/pe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use alloc::vec::Vec;

pub mod certificate_table;
pub mod characteristic;
pub mod data_directories;
pub mod debug;
Expand Down Expand Up @@ -58,6 +59,8 @@ pub struct PE<'a> {
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> {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -194,6 +208,7 @@ impl<'a> PE<'a> {
libraries,
debug_data,
exception_data,
certificates,
})
}
}
Expand Down

0 comments on commit 170395f

Please sign in to comment.