Coverage Report

Created: 2026-11-11 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}