Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pe: support basic certificates enumeration #354

Merged
merged 1 commit into from
Mar 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
RaitoBezarius marked this conversation as resolved.
Show resolved Hide resolved
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(),
RaitoBezarius marked this conversation as resolved.
Show resolved Hide resolved
))
}
})
}
}

#[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
RaitoBezarius marked this conversation as resolved.
Show resolved Hide resolved
// 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