mod error;
mod file;
mod headers;
pub use isr_dl_windows::CodeView; use vmi_core::VmiError;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
pub use self::{
error::PeError,
file::PeFile,
headers::{
Export, ExportTable, ExportTarget, IMAGE_DEBUG_TYPE_CODEVIEW,
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE, IMAGE_DIRECTORY_ENTRY_BASERELOC,
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR,
IMAGE_DIRECTORY_ENTRY_DEBUG, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT,
IMAGE_DIRECTORY_ENTRY_EXCEPTION, IMAGE_DIRECTORY_ENTRY_EXPORT,
IMAGE_DIRECTORY_ENTRY_GLOBALPTR, IMAGE_DIRECTORY_ENTRY_IAT, IMAGE_DIRECTORY_ENTRY_IMPORT,
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, IMAGE_DIRECTORY_ENTRY_RESOURCE,
IMAGE_DIRECTORY_ENTRY_SECURITY, IMAGE_DIRECTORY_ENTRY_TLS, IMAGE_DOS_SIGNATURE,
IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC, IMAGE_NT_SIGNATURE,
IMAGE_NUMBEROF_DIRECTORY_ENTRIES, ImageDataDirectory, ImageDebugDirectory, ImageDosHeader,
ImageFileHeader, ImageNtHeaders, ImageOptionalHeader, ImageOptionalHeader32,
ImageOptionalHeader64, ImageRuntimeFunctionEntry, ImageSectionHeader,
},
};
pub trait PeImage: Sized {
fn read_at_rva(&self, rva: u32, buf: &mut [u8]) -> Result<(), VmiError>;
fn read_struct_at_rva<T>(&self, rva: u32) -> Result<T, VmiError>
where
T: FromBytes + IntoBytes,
{
let mut result = T::new_zeroed();
self.read_at_rva(rva, result.as_mut_bytes())?;
Ok(result)
}
fn dos_header(&self) -> Result<&ImageDosHeader, VmiError>;
fn nt_headers(&self) -> Result<&ImageNtHeaders, VmiError>;
fn export_directory(&self) -> Result<Option<PeExportDirectory<'_, Self>>, VmiError>;
fn exception_directory(&self) -> Result<Option<PeExceptionDirectory<'_, Self>>, VmiError>;
fn debug_directory(&self) -> Result<Option<PeDebugDirectory<'_, Self>>, VmiError>;
}
pub trait PeImageExt: PeImage {
fn codeview(&self) -> Result<Option<CodeView>, VmiError> {
let debug_directory = match self.debug_directory()? {
Some(debug_directory) => debug_directory,
None => return Ok(None),
};
debug_directory.codeview()
}
}
impl<T> PeImageExt for T where T: PeImage {}
pub struct PeExportDirectory<'a, Image: PeImage> {
#[expect(unused)]
image: &'a Image,
entry: ImageDataDirectory,
data: Vec<u8>,
}
impl<'a, Image: PeImage> PeExportDirectory<'a, Image> {
pub(crate) fn new(image: &'a Image, entry: ImageDataDirectory, data: Vec<u8>) -> Self {
Self { image, entry, data }
}
pub fn exports(&self) -> Result<Vec<Export<'_>>, PeError> {
let export_table = ExportTable::parse(&self.data, self.entry.virtual_address)
.map_err(|_| PeError::InvalidExportTable)?;
export_table
.exports()
.map_err(|_| PeError::InvalidExportTable)
}
}
pub struct PeExceptionDirectory<'a, Image: PeImage> {
#[expect(unused)]
image: &'a Image,
data: Vec<u8>,
}
impl<'a, Image: PeImage> PeExceptionDirectory<'a, Image> {
pub(crate) fn new(image: &'a Image, data: Vec<u8>) -> Self {
Self { image, data }
}
pub fn runtime_functions(&self) -> Option<&[ImageRuntimeFunctionEntry]> {
<[ImageRuntimeFunctionEntry]>::ref_from_bytes(&self.data).ok()
}
pub fn find(&self, rva: u32) -> Option<&ImageRuntimeFunctionEntry> {
let entries = self.runtime_functions()?;
let index = entries
.binary_search_by(|entry| {
use std::cmp::Ordering;
let begin = entry.begin_address;
let end = entry.end_address;
if rva < begin {
Ordering::Greater
}
else if rva >= end {
Ordering::Less
}
else {
Ordering::Equal
}
})
.ok()?;
Some(&entries[index])
}
}
pub struct PeDebugDirectory<'a, Image: PeImage> {
image: &'a Image,
data: Vec<u8>,
}
impl<'a, Image: PeImage> PeDebugDirectory<'a, Image> {
pub(crate) fn new(image: &'a Image, data: Vec<u8>) -> Self {
Self { image, data }
}
pub fn debug_directories(&self) -> Option<&[ImageDebugDirectory]> {
<[ImageDebugDirectory]>::ref_from_bytes(&self.data).ok()
}
pub fn find_debug_directory(&self, typ: u32) -> Option<&ImageDebugDirectory> {
self.debug_directories()?
.iter()
.find(|dir| dir.typ == typ && dir.address_of_raw_data != 0 && dir.size_of_data != 0)
}
pub fn codeview(&self) -> Result<Option<CodeView>, VmiError> {
const CV_SIGNATURE_RSDS: u32 = 0x53445352;
#[repr(C)]
#[derive(Debug, FromBytes, Immutable, KnownLayout)]
struct CvInfoPdb70 {
signature: u32,
guid: [u8; 16],
age: u32,
}
let directory = match self.find_debug_directory(IMAGE_DEBUG_TYPE_CODEVIEW) {
Some(directory) => directory,
None => return Ok(None),
};
if directory.size_of_data < size_of::<CvInfoPdb70>() as u32 {
tracing::warn!("invalid CodeView Info size");
return Ok(None);
}
let rva = directory.address_of_raw_data;
let info_size = directory.size_of_data as usize;
let mut info_data = vec![0u8; info_size];
self.image.read_at_rva(rva, &mut info_data)?;
let (info, pdb_file_name) = info_data.split_at(size_of::<CvInfoPdb70>());
let info = match CvInfoPdb70::ref_from_bytes(info) {
Ok(info) => info,
Err(err) => {
tracing::warn!(%err, "invalid CodeView Info address");
return Ok(None);
}
};
if info.signature != CV_SIGNATURE_RSDS {
tracing::warn!("invalid CodeView signature");
return Ok(None);
}
let name = String::from_utf8_lossy(pdb_file_name)
.trim_end_matches('\0')
.to_string();
let guid0 = u32::from_le_bytes(info.guid[0..4].try_into().unwrap());
let guid1 = u16::from_le_bytes(info.guid[4..6].try_into().unwrap());
let guid2 = u16::from_le_bytes(info.guid[6..8].try_into().unwrap());
let guid3 = &info.guid[8..16];
#[rustfmt::skip]
let guid = format!(
concat!(
"{:08x}{:04x}{:04x}",
"{:02x}{:02x}{:02x}{:02x}",
"{:02x}{:02x}{:02x}{:02x}",
),
guid0, guid1, guid2,
guid3[0], guid3[1], guid3[2], guid3[3],
guid3[4], guid3[5], guid3[6], guid3[7],
);
Ok(Some(CodeView {
name,
guid,
age: info.age & 0xf,
}))
}
}
pub struct PeHeader {
dos_header: ImageDosHeader,
nt_headers: ImageNtHeaders,
data_directories: [ImageDataDirectory; IMAGE_NUMBEROF_DIRECTORY_ENTRIES],
section_table_offset: u64,
}
impl PeHeader {
pub fn parse(data: &[u8]) -> Result<Self, PeError> {
let mut offset = 0;
let dos_header = ImageDosHeader::parse(data, &mut offset)?;
let nt_headers = ImageNtHeaders::parse(data, &mut offset)?;
let optional_data_size = u64::from(nt_headers.file_header.size_of_optional_header)
.checked_sub(nt_headers.optional_header.size() as u64)
.ok_or(PeError::OptionalHeaderTooSmall)?;
let optional_data = data
.get(offset as usize..)
.ok_or(PeError::InvalidOptionalHeaderSize)?
.get(..optional_data_size as usize)
.ok_or(PeError::InvalidOptionalHeaderSize)?;
offset += optional_data_size;
let (data_directories, _) = <[ImageDataDirectory]>::ref_from_prefix_with_elems(
optional_data,
nt_headers.optional_header.number_of_rva_and_sizes() as usize,
)
.map_err(|_| PeError::InvalidDataDirectoryCount)?;
Ok(Self {
dos_header,
nt_headers,
data_directories: std::array::from_fn(|i| {
data_directories
.get(i)
.copied()
.unwrap_or(ImageDataDirectory {
virtual_address: 0,
size: 0,
})
}),
section_table_offset: offset,
})
}
pub fn dos_header(&self) -> &ImageDosHeader {
&self.dos_header
}
pub fn nt_headers(&self) -> &ImageNtHeaders {
&self.nt_headers
}
pub fn data_directories(&self) -> &[ImageDataDirectory] {
&self.data_directories
}
pub fn section_table_offset(&self) -> u64 {
self.section_table_offset
}
}