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}