1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/// 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::{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
    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, PartialEq, Copy, Clone)]
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(),
                )
            })?;

        if let Some(bytes) = bytes.get(*current_offset..(*current_offset + cert_size)) {
            let attr = Self {
                length: header.length,
                revision: header.revision.try_into()?,
                certificate_type: header.certificate_type.try_into()?,
                certificate: bytes,
            };
            // 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)
        } else {
            Err(error::Error::Malformed(format!(
                "Unable to extract certificate. Probably cert_size:{} is malformed",
                cert_size
            )))
        }
    }
}

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,
    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)
}