Revision control
Copy as Markdown
Other Tools
// Reference:
// Authenticode works by omiting sections of the PE binary from the digest
// those sections are:
// - checksum
// - data directory entry for certtable
// - certtable
use alloc::collections::VecDeque;
use core::ops::Range;
use log::debug;
use super::{section_table::SectionTable, PE};
static PADDING: [u8; 7] = [0; 7];
impl PE<'_> {
/// Returns the various ranges of the binary that are relevant for signature.
pub fn authenticode_ranges(&self) -> ExcludedSectionsIter<'_> {
ExcludedSectionsIter {
pe: self,
state: IterState::default(),
sections: VecDeque::default(),
}
}
}
/// [`ExcludedSections`] holds the various ranges of the binary that are expected to be
/// excluded from the authenticode computation.
#[derive(Debug, Clone, Default)]
pub(super) struct ExcludedSections {
checksum: Range<usize>,
datadir_entry_certtable: Range<usize>,
certificate_table_size: usize,
end_image_header: usize,
}
impl ExcludedSections {
pub(super) fn new(
checksum: Range<usize>,
datadir_entry_certtable: Range<usize>,
certificate_table_size: usize,
end_image_header: usize,
) -> Self {
Self {
checksum,
datadir_entry_certtable,
certificate_table_size,
end_image_header,
}
}
}
pub struct ExcludedSectionsIter<'s> {
pe: &'s PE<'s>,
state: IterState,
sections: VecDeque<SectionTable>,
}
#[derive(Debug, PartialEq)]
enum IterState {
Initial,
ChecksumEnd(usize),
CertificateTableEnd(usize),
HeaderEnd {
end_image_header: usize,
sum_of_bytes_hashed: usize,
},
Sections {
tail: usize,
sum_of_bytes_hashed: usize,
},
Final {
sum_of_bytes_hashed: usize,
},
Padding(usize),
Done,
}
impl Default for IterState {
fn default() -> Self {
Self::Initial
}
}
impl<'s> Iterator for ExcludedSectionsIter<'s> {
type Item = &'s [u8];
fn next(&mut self) -> Option<Self::Item> {
let bytes = &self.pe.bytes;
if let Some(sections) = self.pe.authenticode_excluded_sections.as_ref() {
loop {
match self.state {
IterState::Initial => {
// 3. Hash the image header from its base to immediately before the start of the
// checksum address, as specified in Optional Header Windows-Specific Fields.
let out = Some(&bytes[..sections.checksum.start]);
debug!("hashing {:#x} {:#x}", 0, sections.checksum.start);
// 4. Skip over the checksum, which is a 4-byte field.
debug_assert_eq!(sections.checksum.end - sections.checksum.start, 4);
self.state = IterState::ChecksumEnd(sections.checksum.end);
return out;
}
IterState::ChecksumEnd(checksum_end) => {
// 5. Hash everything from the end of the checksum field to immediately before the start
// of the Certificate Table entry, as specified in Optional Header Data Directories.
let out =
Some(&bytes[checksum_end..sections.datadir_entry_certtable.start]);
debug!(
"hashing {checksum_end:#x} {:#x}",
sections.datadir_entry_certtable.start
);
// 6. Get the Attribute Certificate Table address and size from the Certificate Table entry.
// For details, see section 5.7 of the PE/COFF specification.
// 7. Exclude the Certificate Table entry from the calculation
self.state =
IterState::CertificateTableEnd(sections.datadir_entry_certtable.end);
return out;
}
IterState::CertificateTableEnd(start) => {
// 7. Exclude the Certificate Table entry from the calculation and hash everything from
// the end of the Certificate Table entry to the end of image header, including
// Section Table (headers). The Certificate Table entry is 8 bytes long, as specified
// in Optional Header Data Directories.
let end_image_header = sections.end_image_header;
let buf = Some(&bytes[start..end_image_header]);
debug!("hashing {start:#x} {:#x}", end_image_header - start);
// 8. Create a counter called SUM_OF_BYTES_HASHED, which is not part of the signature.
// Set this counter to the SizeOfHeaders field, as specified in
// Optional Header Windows-Specific Field.
let sum_of_bytes_hashed = end_image_header;
self.state = IterState::HeaderEnd {
end_image_header,
sum_of_bytes_hashed,
};
return buf;
}
IterState::HeaderEnd {
end_image_header,
sum_of_bytes_hashed,
} => {
// 9. Build a temporary table of pointers to all of the section headers in the
// image. The NumberOfSections field of COFF File Header indicates how big
// the table should be. Do not include any section headers in the table whose
// SizeOfRawData field is zero.
// Implementation detail:
// We require allocation here because the section table has a variable size and
// needs to be sorted.
let mut sections: VecDeque<SectionTable> = self
.pe
.sections
.iter()
.filter(|section| section.size_of_raw_data != 0)
.cloned()
.collect();
// 10. Using the PointerToRawData field (offset 20) in the referenced SectionHeader
// structure as a key, arrange the table's elements in ascending order. In
// other words, sort the section headers in ascending order according to the
// disk-file offset of the sections.
sections
.make_contiguous()
.sort_by_key(|section| section.pointer_to_raw_data);
self.sections = sections;
self.state = IterState::Sections {
tail: end_image_header,
sum_of_bytes_hashed,
};
}
IterState::Sections {
mut tail,
mut sum_of_bytes_hashed,
} => {
// 11. Walk through the sorted table, load the corresponding section into memory,
// and hash the entire section. Use the SizeOfRawData field in the SectionHeader
// structure to determine the amount of data to hash.
if let Some(section) = self.sections.pop_front() {
let start = section.pointer_to_raw_data as usize;
let end = start + section.size_of_raw_data as usize;
tail = end;
// 12. Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED.
sum_of_bytes_hashed += section.size_of_raw_data as usize;
debug!("hashing {start:#x} {:#x}", end - start);
let buf = &bytes[start..end];
// 13. Repeat steps 11 and 12 for all of the sections in the sorted table.
self.state = IterState::Sections {
tail,
sum_of_bytes_hashed,
};
return Some(buf);
} else {
self.state = IterState::Final {
sum_of_bytes_hashed,
};
}
}
IterState::Final {
sum_of_bytes_hashed,
} => {
// 14. Create a value called FILE_SIZE, which is not part of the signature.
// Set this value to the image’s file size, acquired from the underlying
// file system. If FILE_SIZE is greater than SUM_OF_BYTES_HASHED, the
// file contains extra data that must be added to the hash. This data
// begins at the SUM_OF_BYTES_HASHED file offset, and its length is:
// (File Size) - ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED)
//
// Note: The size of Attribute Certificate Table is specified in the second
// ULONG value in the Certificate Table entry (32 bit: offset 132,
// 64 bit: offset 148) in Optional Header Data Directories.
let file_size = bytes.len();
// If FILE_SIZE is not a multiple of 8 bytes, the data added to the hash must
// be appended with zero padding of length (8 – (FILE_SIZE % 8)) bytes
let pad_size = (8 - file_size % 8) % 8;
self.state = IterState::Padding(pad_size);
if file_size > sum_of_bytes_hashed {
let extra_data_start = sum_of_bytes_hashed;
let len =
file_size - sections.certificate_table_size - sum_of_bytes_hashed;
debug!("hashing {extra_data_start:#x} {len:#x}",);
let buf = &bytes[extra_data_start..extra_data_start + len];
return Some(buf);
}
}
IterState::Padding(pad_size) => {
self.state = IterState::Done;
if pad_size != 0 {
debug!("hashing {pad_size:#x}");
// NOTE (safety): pad size will be at most 7, and PADDING has a size of 7
// pad_size is computed ~10 lines above.
debug_assert!(pad_size <= 7);
debug_assert_eq!(PADDING.len(), 7);
return Some(&PADDING[..pad_size]);
}
}
IterState::Done => return None,
}
}
} else {
loop {
match self.state {
IterState::Initial => {
self.state = IterState::Done;
return Some(bytes);
}
IterState::Done => return None,
_ => {
self.state = IterState::Done;
}
}
}
}
}
}