Skip to main content

goblin_experimental/pe/
authenticode.rs

1// Reference:
2//   https://learn.microsoft.com/en-us/windows-hardware/drivers/install/authenticode
3//   https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx
4
5// Authenticode works by omiting sections of the PE binary from the digest
6// those sections are:
7//   - checksum
8//   - data directory entry for certtable
9//   - certtable
10
11use alloc::collections::VecDeque;
12use core::ops::Range;
13use log::debug;
14
15use super::{section_table::SectionTable, PE};
16
17static PADDING: [u8; 7] = [0; 7];
18
19impl PE<'_> {
20    /// Returns the various ranges of the binary that are relevant for signature.
21    pub fn authenticode_ranges(&self) -> ExcludedSectionsIter<'_> {
22        ExcludedSectionsIter {
23            pe: self,
24            state: IterState::default(),
25            sections: VecDeque::default(),
26        }
27    }
28}
29
30/// [`ExcludedSections`] holds the various ranges of the binary that are expected to be
31/// excluded from the authenticode computation.
32#[derive(Debug, Clone, Default)]
33pub(super) struct ExcludedSections {
34    checksum: Range<usize>,
35    datadir_entry_certtable: Range<usize>,
36    certificate_table_size: usize,
37    end_image_header: usize,
38}
39
40impl ExcludedSections {
41    pub(super) fn new(
42        checksum: Range<usize>,
43        datadir_entry_certtable: Range<usize>,
44        certificate_table_size: usize,
45        end_image_header: usize,
46    ) -> Self {
47        Self {
48            checksum,
49            datadir_entry_certtable,
50            certificate_table_size,
51            end_image_header,
52        }
53    }
54}
55
56pub struct ExcludedSectionsIter<'s> {
57    pe: &'s PE<'s>,
58    state: IterState,
59    sections: VecDeque<SectionTable>,
60}
61
62#[derive(Debug, PartialEq)]
63enum IterState {
64    Initial,
65    ChecksumEnd(usize),
66    CertificateTableEnd(usize),
67    HeaderEnd {
68        end_image_header: usize,
69        sum_of_bytes_hashed: usize,
70    },
71    Sections {
72        tail: usize,
73        sum_of_bytes_hashed: usize,
74    },
75    Final {
76        sum_of_bytes_hashed: usize,
77    },
78    Padding(usize),
79    Done,
80}
81
82impl Default for IterState {
83    fn default() -> Self {
84        Self::Initial
85    }
86}
87
88impl<'s> Iterator for ExcludedSectionsIter<'s> {
89    type Item = &'s [u8];
90
91    fn next(&mut self) -> Option<Self::Item> {
92        let bytes = &self.pe.bytes;
93
94        if let Some(sections) = self.pe.authenticode_excluded_sections.as_ref() {
95            loop {
96                match self.state {
97                    IterState::Initial => {
98                        // 3. Hash the image header from its base to immediately before the start of the
99                        //    checksum address, as specified in Optional Header Windows-Specific Fields.
100                        let out = Some(&bytes[..sections.checksum.start]);
101                        debug!("hashing {:#x} {:#x}", 0, sections.checksum.start);
102
103                        // 4. Skip over the checksum, which is a 4-byte field.
104                        debug_assert_eq!(sections.checksum.end - sections.checksum.start, 4);
105                        self.state = IterState::ChecksumEnd(sections.checksum.end);
106
107                        return out;
108                    }
109                    IterState::ChecksumEnd(checksum_end) => {
110                        // 5. Hash everything from the end of the checksum field to immediately before the start
111                        //    of the Certificate Table entry, as specified in Optional Header Data Directories.
112                        let out =
113                            Some(&bytes[checksum_end..sections.datadir_entry_certtable.start]);
114                        debug!(
115                            "hashing {checksum_end:#x} {:#x}",
116                            sections.datadir_entry_certtable.start
117                        );
118
119                        // 6. Get the Attribute Certificate Table address and size from the Certificate Table entry.
120                        //    For details, see section 5.7 of the PE/COFF specification.
121                        // 7. Exclude the Certificate Table entry from the calculation
122                        self.state =
123                            IterState::CertificateTableEnd(sections.datadir_entry_certtable.end);
124
125                        return out;
126                    }
127                    IterState::CertificateTableEnd(start) => {
128                        // 7. Exclude the Certificate Table entry from the calculation and hash everything from
129                        //    the end of the Certificate Table entry to the end of image header, including
130                        //    Section Table (headers). The Certificate Table entry is 8 bytes long, as specified
131                        //    in Optional Header Data Directories.
132                        let end_image_header = sections.end_image_header;
133                        let buf = Some(&bytes[start..end_image_header]);
134                        debug!("hashing {start:#x} {:#x}", end_image_header - start);
135
136                        // 8. Create a counter called SUM_OF_BYTES_HASHED, which is not part of the signature.
137                        //    Set this counter to the SizeOfHeaders field, as specified in
138                        //    Optional Header Windows-Specific Field.
139                        let sum_of_bytes_hashed = end_image_header;
140
141                        self.state = IterState::HeaderEnd {
142                            end_image_header,
143                            sum_of_bytes_hashed,
144                        };
145
146                        return buf;
147                    }
148                    IterState::HeaderEnd {
149                        end_image_header,
150                        sum_of_bytes_hashed,
151                    } => {
152                        // 9. Build a temporary table of pointers to all of the section headers in the
153                        //    image. The NumberOfSections field of COFF File Header indicates how big
154                        //    the table should be. Do not include any section headers in the table whose
155                        //    SizeOfRawData field is zero.
156
157                        // Implementation detail:
158                        // We require allocation here because the section table has a variable size and
159                        // needs to be sorted.
160                        let mut sections: VecDeque<SectionTable> = self
161                            .pe
162                            .sections
163                            .iter()
164                            .filter(|section| section.size_of_raw_data != 0)
165                            .cloned()
166                            .collect();
167
168                        // 10. Using the PointerToRawData field (offset 20) in the referenced SectionHeader
169                        //     structure as a key, arrange the table's elements in ascending order. In
170                        //     other words, sort the section headers in ascending order according to the
171                        //     disk-file offset of the sections.
172                        sections
173                            .make_contiguous()
174                            .sort_by_key(|section| section.pointer_to_raw_data);
175
176                        self.sections = sections;
177
178                        self.state = IterState::Sections {
179                            tail: end_image_header,
180                            sum_of_bytes_hashed,
181                        };
182                    }
183                    IterState::Sections {
184                        mut tail,
185                        mut sum_of_bytes_hashed,
186                    } => {
187                        // 11. Walk through the sorted table, load the corresponding section into memory,
188                        //     and hash the entire section. Use the SizeOfRawData field in the SectionHeader
189                        //     structure to determine the amount of data to hash.
190                        if let Some(section) = self.sections.pop_front() {
191                            let start = section.pointer_to_raw_data as usize;
192                            let end = start + section.size_of_raw_data as usize;
193                            tail = end;
194
195                            // 12. Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED.
196                            sum_of_bytes_hashed += section.size_of_raw_data as usize;
197
198                            debug!("hashing {start:#x} {:#x}", end - start);
199                            let buf = &bytes[start..end];
200
201                            // 13. Repeat steps 11 and 12 for all of the sections in the sorted table.
202                            self.state = IterState::Sections {
203                                tail,
204                                sum_of_bytes_hashed,
205                            };
206
207                            return Some(buf);
208                        } else {
209                            self.state = IterState::Final {
210                                sum_of_bytes_hashed,
211                            };
212                        }
213                    }
214                    IterState::Final {
215                        sum_of_bytes_hashed,
216                    } => {
217                        // 14. Create a value called FILE_SIZE, which is not part of the signature.
218                        //     Set this value to the image’s file size, acquired from the underlying
219                        //     file system. If FILE_SIZE is greater than SUM_OF_BYTES_HASHED, the
220                        //     file contains extra data that must be added to the hash. This data
221                        //     begins at the SUM_OF_BYTES_HASHED file offset, and its length is:
222                        //       (File Size) - ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED)
223                        //
224                        // Note: The size of Attribute Certificate Table is specified in the second
225                        //       ULONG value in the Certificate Table entry (32 bit: offset 132,
226                        //       64 bit: offset 148) in Optional Header Data Directories.
227                        let file_size = bytes.len();
228
229                        // If FILE_SIZE is not a multiple of 8 bytes, the data added to the hash must
230                        // be appended with zero padding of length (8 – (FILE_SIZE % 8)) bytes
231                        let pad_size = (8 - file_size % 8) % 8;
232                        self.state = IterState::Padding(pad_size);
233
234                        if file_size > sum_of_bytes_hashed {
235                            let extra_data_start = sum_of_bytes_hashed;
236                            let len =
237                                file_size - sections.certificate_table_size - sum_of_bytes_hashed;
238
239                            debug!("hashing {extra_data_start:#x} {len:#x}",);
240                            let buf = &bytes[extra_data_start..extra_data_start + len];
241
242                            return Some(buf);
243                        }
244                    }
245                    IterState::Padding(pad_size) => {
246                        self.state = IterState::Done;
247
248                        if pad_size != 0 {
249                            debug!("hashing {pad_size:#x}");
250
251                            // NOTE (safety): pad size will be at most 7, and PADDING has a size of 7
252                            //                pad_size is computed ~10 lines above.
253                            debug_assert!(pad_size <= 7);
254                            debug_assert_eq!(PADDING.len(), 7);
255
256                            return Some(&PADDING[..pad_size]);
257                        }
258                    }
259                    IterState::Done => return None,
260                }
261            }
262        } else {
263            loop {
264                match self.state {
265                    IterState::Initial => {
266                        self.state = IterState::Done;
267                        return Some(bytes);
268                    }
269                    IterState::Done => return None,
270                    _ => {
271                        self.state = IterState::Done;
272                    }
273                }
274            }
275        }
276    }
277}