-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pe: support basic certificates enumeration
- Loading branch information
1 parent
4c99ae8
commit 170395f
Showing
2 changed files
with
171 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters