/rust/registry/src/index.crates.io-1949cf8c6b5b557f/goblin-0.10.7/src/pe/authenticode.rs
Line | Count | Source |
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 | | |
11 | | use alloc::collections::VecDeque; |
12 | | use core::ops::Range; |
13 | | use log::debug; |
14 | | |
15 | | use super::{PE, section_table::SectionTable}; |
16 | | |
17 | | static PADDING: [u8; 7] = [0; 7]; |
18 | | |
19 | | impl PE<'_> { |
20 | | /// Returns the various ranges of the binary that are relevant for signature. |
21 | 0 | pub fn authenticode_ranges(&self) -> ExcludedSectionsIter<'_> { |
22 | 0 | ExcludedSectionsIter { |
23 | 0 | pe: self, |
24 | 0 | state: IterState::default(), |
25 | 0 | sections: VecDeque::default(), |
26 | 0 | } |
27 | 0 | } |
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)] |
33 | | pub(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 | | |
40 | | impl ExcludedSections { |
41 | 1.78k | pub(super) fn new( |
42 | 1.78k | checksum: Range<usize>, |
43 | 1.78k | datadir_entry_certtable: Range<usize>, |
44 | 1.78k | certificate_table_size: usize, |
45 | 1.78k | end_image_header: usize, |
46 | 1.78k | ) -> Self { |
47 | 1.78k | Self { |
48 | 1.78k | checksum, |
49 | 1.78k | datadir_entry_certtable, |
50 | 1.78k | certificate_table_size, |
51 | 1.78k | end_image_header, |
52 | 1.78k | } |
53 | 1.78k | } |
54 | | } |
55 | | |
56 | | pub struct ExcludedSectionsIter<'s> { |
57 | | pe: &'s PE<'s>, |
58 | | state: IterState, |
59 | | sections: VecDeque<SectionTable>, |
60 | | } |
61 | | |
62 | | #[derive(Debug, PartialEq)] |
63 | | enum 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 | | |
82 | | impl Default for IterState { |
83 | 0 | fn default() -> Self { |
84 | 0 | Self::Initial |
85 | 0 | } |
86 | | } |
87 | | |
88 | | impl<'s> Iterator for ExcludedSectionsIter<'s> { |
89 | | type Item = &'s [u8]; |
90 | | |
91 | 0 | fn next(&mut self) -> Option<Self::Item> { |
92 | 0 | let bytes = &self.pe.bytes; |
93 | | |
94 | 0 | if let Some(sections) = self.pe.authenticode_excluded_sections.as_ref() { |
95 | | loop { |
96 | 0 | 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 | 0 | let out = Some(&bytes[..sections.checksum.start]); |
101 | 0 | debug!("hashing {:#x} {:#x}", 0, sections.checksum.start); |
102 | | |
103 | | // 4. Skip over the checksum, which is a 4-byte field. |
104 | 0 | debug_assert_eq!(sections.checksum.end - sections.checksum.start, 4); |
105 | 0 | self.state = IterState::ChecksumEnd(sections.checksum.end); |
106 | | |
107 | 0 | return out; |
108 | | } |
109 | 0 | 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 | 0 | let out = |
113 | 0 | Some(&bytes[checksum_end..sections.datadir_entry_certtable.start]); |
114 | 0 | debug!( |
115 | 0 | "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 | 0 | self.state = |
123 | 0 | IterState::CertificateTableEnd(sections.datadir_entry_certtable.end); |
124 | | |
125 | 0 | return out; |
126 | | } |
127 | 0 | 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 | 0 | let end_image_header = sections.end_image_header; |
133 | 0 | let buf = Some(&bytes[start..end_image_header]); |
134 | 0 | 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 | 0 | let sum_of_bytes_hashed = end_image_header; |
140 | | |
141 | 0 | self.state = IterState::HeaderEnd { |
142 | 0 | end_image_header, |
143 | 0 | sum_of_bytes_hashed, |
144 | 0 | }; |
145 | | |
146 | 0 | return buf; |
147 | | } |
148 | | IterState::HeaderEnd { |
149 | 0 | end_image_header, |
150 | 0 | 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 | 0 | let mut sections: VecDeque<SectionTable> = self |
161 | 0 | .pe |
162 | 0 | .sections |
163 | 0 | .iter() |
164 | 0 | .filter(|section| section.size_of_raw_data != 0) |
165 | 0 | .cloned() |
166 | 0 | .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 | 0 | sections |
173 | 0 | .make_contiguous() |
174 | 0 | .sort_by_key(|section| section.pointer_to_raw_data); |
175 | | |
176 | 0 | self.sections = sections; |
177 | | |
178 | 0 | self.state = IterState::Sections { |
179 | 0 | tail: end_image_header, |
180 | 0 | sum_of_bytes_hashed, |
181 | 0 | }; |
182 | | } |
183 | | IterState::Sections { |
184 | | tail: _, |
185 | 0 | 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 | 0 | if let Some(section) = self.sections.pop_front() { |
191 | 0 | let start = section.pointer_to_raw_data as usize; |
192 | 0 | let end = start + section.size_of_raw_data as usize; |
193 | | |
194 | | // 12. Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED. |
195 | 0 | sum_of_bytes_hashed += section.size_of_raw_data as usize; |
196 | | |
197 | 0 | debug!("hashing {start:#x} {:#x}", end - start); |
198 | 0 | let buf = &bytes[start..end]; |
199 | | |
200 | | // 13. Repeat steps 11 and 12 for all of the sections in the sorted table. |
201 | 0 | self.state = IterState::Sections { |
202 | 0 | tail: end, |
203 | 0 | sum_of_bytes_hashed, |
204 | 0 | }; |
205 | | |
206 | 0 | return Some(buf); |
207 | 0 | } else { |
208 | 0 | self.state = IterState::Final { |
209 | 0 | sum_of_bytes_hashed, |
210 | 0 | }; |
211 | 0 | } |
212 | | } |
213 | | IterState::Final { |
214 | 0 | sum_of_bytes_hashed, |
215 | | } => { |
216 | | // 14. Create a value called FILE_SIZE, which is not part of the signature. |
217 | | // Set this value to the image’s file size, acquired from the underlying |
218 | | // file system. If FILE_SIZE is greater than SUM_OF_BYTES_HASHED, the |
219 | | // file contains extra data that must be added to the hash. This data |
220 | | // begins at the SUM_OF_BYTES_HASHED file offset, and its length is: |
221 | | // (File Size) - ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED) |
222 | | // |
223 | | // Note: The size of Attribute Certificate Table is specified in the second |
224 | | // ULONG value in the Certificate Table entry (32 bit: offset 132, |
225 | | // 64 bit: offset 148) in Optional Header Data Directories. |
226 | 0 | let file_size = bytes.len(); |
227 | | |
228 | | // If FILE_SIZE is not a multiple of 8 bytes, the data added to the hash must |
229 | | // be appended with zero padding of length (8 – (FILE_SIZE % 8)) bytes |
230 | 0 | let pad_size = (8 - file_size % 8) % 8; |
231 | 0 | self.state = IterState::Padding(pad_size); |
232 | | |
233 | 0 | if file_size > sum_of_bytes_hashed { |
234 | 0 | let extra_data_start = sum_of_bytes_hashed; |
235 | 0 | let len = |
236 | 0 | file_size - sections.certificate_table_size - sum_of_bytes_hashed; |
237 | | |
238 | 0 | debug!("hashing {extra_data_start:#x} {len:#x}",); |
239 | 0 | let buf = &bytes[extra_data_start..extra_data_start + len]; |
240 | | |
241 | 0 | return Some(buf); |
242 | 0 | } |
243 | | } |
244 | 0 | IterState::Padding(pad_size) => { |
245 | 0 | self.state = IterState::Done; |
246 | | |
247 | 0 | if pad_size != 0 { |
248 | 0 | debug!("hashing {pad_size:#x}"); |
249 | | |
250 | | // NOTE (safety): pad size will be at most 7, and PADDING has a size of 7 |
251 | | // pad_size is computed ~10 lines above. |
252 | 0 | debug_assert!(pad_size <= 7); |
253 | 0 | debug_assert_eq!(PADDING.len(), 7); |
254 | | |
255 | 0 | return Some(&PADDING[..pad_size]); |
256 | 0 | } |
257 | | } |
258 | 0 | IterState::Done => return None, |
259 | | } |
260 | | } |
261 | | } else { |
262 | | loop { |
263 | 0 | match self.state { |
264 | | IterState::Initial => { |
265 | 0 | self.state = IterState::Done; |
266 | 0 | return Some(bytes); |
267 | | } |
268 | 0 | IterState::Done => return None, |
269 | 0 | _ => { |
270 | 0 | self.state = IterState::Done; |
271 | 0 | } |
272 | | } |
273 | | } |
274 | | } |
275 | 0 | } |
276 | | } |