use std::io::{self, Read, Seek, SeekFrom};
pub fn overlay_end<R: Read + Seek>(r: &mut R) -> io::Result<u64> {
let len = r.seek(SeekFrom::End(0))?;
Ok(security_dir_offset(r, len)?.unwrap_or(len))
}
fn security_dir_offset<R: Read + Seek>(r: &mut R, len: u64) -> io::Result<Option<u64>> {
let mut b2 = [0u8; 2];
let mut b4 = [0u8; 4];
if !read_at(r, 0, &mut b2, len)? || &b2 != b"MZ" {
return Ok(None);
}
if !read_at(r, 0x3C, &mut b4, len)? {
return Ok(None);
}
let e_lfanew = u32::from_le_bytes(b4) as u64;
if !read_at(r, e_lfanew, &mut b4, len)? || &b4 != b"PE\0\0" {
return Ok(None);
}
let opt = e_lfanew + 4 + 20;
if !read_at(r, opt, &mut b2, len)? {
return Ok(None);
}
let (num_rvas_off, cert_dir_off) = match u16::from_le_bytes(b2) {
0x10b => (92u64, 128u64), 0x20b => (108u64, 144u64), _ => return Ok(None),
};
if !read_at(r, opt + num_rvas_off, &mut b4, len)? {
return Ok(None);
}
if u32::from_le_bytes(b4) <= 4 {
return Ok(None);
}
if !read_at(r, opt + cert_dir_off, &mut b4, len)? {
return Ok(None);
}
match u32::from_le_bytes(b4) as u64 {
0 => Ok(None),
off if off > len => Ok(None),
off => Ok(Some(off)),
}
}
fn read_at<R: Read + Seek>(r: &mut R, off: u64, buf: &mut [u8], len: u64) -> io::Result<bool> {
match off.checked_add(buf.len() as u64) {
Some(end) if end <= len => {
r.seek(SeekFrom::Start(off))?;
r.read_exact(buf)?;
Ok(true)
}
_ => Ok(false),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn pe(magic: u16, num_rvas: u32, cert_off: u32) -> Vec<u8> {
let e_lfanew = 0x40usize;
let opt = e_lfanew + 4 + 20;
let (num_rvas_off, cert_dir_off) = if magic == 0x10b {
(92usize, 128usize)
} else {
(108usize, 144usize)
};
let mut v = vec![0u8; opt + cert_dir_off + 8];
v[0..2].copy_from_slice(b"MZ");
v[0x3C..0x40].copy_from_slice(&(e_lfanew as u32).to_le_bytes());
v[e_lfanew..e_lfanew + 4].copy_from_slice(b"PE\0\0");
v[opt..opt + 2].copy_from_slice(&magic.to_le_bytes());
v[opt + num_rvas_off..opt + num_rvas_off + 4].copy_from_slice(&num_rvas.to_le_bytes());
v[opt + cert_dir_off..opt + cert_dir_off + 4].copy_from_slice(&cert_off.to_le_bytes());
v
}
fn end(bytes: Vec<u8>) -> u64 {
overlay_end(&mut Cursor::new(bytes)).unwrap()
}
#[test]
fn unsigned_pe_returns_stream_length() {
let bytes = pe(0x20b, 16, 0);
let len = bytes.len() as u64;
assert_eq!(end(bytes), len);
}
#[test]
fn missing_cert_directory_returns_length() {
let bytes = pe(0x20b, 4, 0);
let len = bytes.len() as u64;
assert_eq!(end(bytes), len);
}
#[test]
fn signed_pe32plus_returns_cert_offset() {
let mut bytes = pe(0x20b, 16, 0x1000);
bytes.resize(0x1000 + 200, 0); assert_eq!(end(bytes), 0x1000);
}
#[test]
fn signed_pe32_returns_cert_offset() {
let mut bytes = pe(0x10b, 16, 0x800);
bytes.resize(0x800 + 200, 0); assert_eq!(end(bytes), 0x800);
}
#[test]
fn cert_offset_beyond_eof_returns_length() {
let bytes = pe(0x20b, 16, 0x1000); let len = bytes.len() as u64;
assert!(len < 0x1000);
assert_eq!(end(bytes), len);
}
#[test]
fn garbage_returns_stream_length() {
let bytes = b"this is not a PE file, just some bytes".to_vec();
let len = bytes.len() as u64;
assert_eq!(end(bytes), len);
}
#[test]
fn truncated_after_mz_returns_length() {
assert_eq!(end(b"MZ".to_vec()), 2);
}
}