#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{format, string::String, vec::Vec};
#[cfg(feature = "std")]
use std::{format, string::String, vec::Vec};
use crate::error::{Result, WraithError};
use crate::structures::pe::{
DataDirectory, DataDirectoryType, DosHeader, FileHeader, OptionalHeader32, OptionalHeader64,
SectionHeader,
};
use crate::structures::pe::nt_headers::{NT_SIGNATURE, PE32_MAGIC, PE32PLUS_MAGIC};
pub struct ParsedPe {
data: Vec<u8>,
is_64bit: bool,
nt_offset: usize,
}
impl ParsedPe {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < core::mem::size_of::<DosHeader>() {
return Err(WraithError::InvalidPeFormat {
reason: "file too small for DOS header".into(),
});
}
let dos = unsafe { &*(data.as_ptr() as *const DosHeader) };
if !dos.is_valid() {
return Err(WraithError::InvalidPeFormat {
reason: "invalid DOS signature".into(),
});
}
let nt_offset = dos.nt_headers_offset();
if nt_offset + 4 > data.len() {
return Err(WraithError::InvalidPeFormat {
reason: "NT headers offset out of bounds".into(),
});
}
let signature = unsafe { *(data.as_ptr().add(nt_offset) as *const u32) };
if signature != NT_SIGNATURE {
return Err(WraithError::InvalidPeFormat {
reason: "invalid NT signature".into(),
});
}
let magic_offset = nt_offset + 4 + core::mem::size_of::<FileHeader>();
if magic_offset + 2 > data.len() {
return Err(WraithError::InvalidPeFormat {
reason: "file too small for optional header".into(),
});
}
let magic = unsafe { *(data.as_ptr().add(magic_offset) as *const u16) };
let is_64bit = match magic {
PE32_MAGIC => false,
PE32PLUS_MAGIC => true,
_ => {
return Err(WraithError::InvalidPeFormat {
reason: format!("unknown PE magic: {magic:#x}"),
});
}
};
let optional_size = if is_64bit {
core::mem::size_of::<OptionalHeader64>()
} else {
core::mem::size_of::<OptionalHeader32>()
};
let headers_end = nt_offset + 4 + core::mem::size_of::<FileHeader>() + optional_size;
if headers_end > data.len() {
return Err(WraithError::InvalidPeFormat {
reason: "file too small for full headers".into(),
});
}
Ok(Self {
data: data.to_vec(),
is_64bit,
nt_offset,
})
}
pub fn is_64bit(&self) -> bool {
self.is_64bit
}
pub fn dos_header(&self) -> &DosHeader {
unsafe { &*(self.data.as_ptr() as *const DosHeader) }
}
pub fn file_header(&self) -> &FileHeader {
let offset = self.nt_offset + 4;
unsafe { &*(self.data.as_ptr().add(offset) as *const FileHeader) }
}
pub fn optional_header_32(&self) -> Option<&OptionalHeader32> {
if self.is_64bit {
None
} else {
let offset = self.nt_offset + 4 + core::mem::size_of::<FileHeader>();
Some(unsafe { &*(self.data.as_ptr().add(offset) as *const OptionalHeader32) })
}
}
pub fn optional_header_64(&self) -> Option<&OptionalHeader64> {
if !self.is_64bit {
None
} else {
let offset = self.nt_offset + 4 + core::mem::size_of::<FileHeader>();
Some(unsafe { &*(self.data.as_ptr().add(offset) as *const OptionalHeader64) })
}
}
pub fn size_of_image(&self) -> usize {
if self.is_64bit {
self.optional_header_64().unwrap().size_of_image as usize
} else {
self.optional_header_32().unwrap().size_of_image as usize
}
}
pub fn preferred_base(&self) -> usize {
if self.is_64bit {
self.optional_header_64().unwrap().image_base as usize
} else {
self.optional_header_32().unwrap().image_base as usize
}
}
pub fn entry_point_rva(&self) -> u32 {
if self.is_64bit {
self.optional_header_64().unwrap().address_of_entry_point
} else {
self.optional_header_32().unwrap().address_of_entry_point
}
}
pub fn size_of_headers(&self) -> usize {
if self.is_64bit {
self.optional_header_64().unwrap().size_of_headers as usize
} else {
self.optional_header_32().unwrap().size_of_headers as usize
}
}
pub fn section_alignment(&self) -> u32 {
if self.is_64bit {
self.optional_header_64().unwrap().section_alignment
} else {
self.optional_header_32().unwrap().section_alignment
}
}
pub fn file_alignment(&self) -> u32 {
if self.is_64bit {
self.optional_header_64().unwrap().file_alignment
} else {
self.optional_header_32().unwrap().file_alignment
}
}
pub fn number_of_sections(&self) -> u16 {
self.file_header().number_of_sections
}
pub fn data_directory(&self, dir_type: DataDirectoryType) -> Option<&DataDirectory> {
let index = dir_type.index();
if self.is_64bit {
self.optional_header_64()?.data_directory.get(index)
} else {
self.optional_header_32()?.data_directory.get(index)
}
}
pub fn sections(&self) -> &[SectionHeader] {
let optional_size = if self.is_64bit {
core::mem::size_of::<OptionalHeader64>()
} else {
core::mem::size_of::<OptionalHeader32>()
};
let sections_offset =
self.nt_offset + 4 + core::mem::size_of::<FileHeader>() + optional_size;
let num_sections = self.number_of_sections() as usize;
unsafe {
core::slice::from_raw_parts(
self.data.as_ptr().add(sections_offset) as *const SectionHeader,
num_sections,
)
}
}
pub fn raw_data(&self) -> &[u8] {
&self.data
}
pub fn section_from_rva(&self, rva: u32) -> Option<&SectionHeader> {
self.sections()
.iter()
.find(|s| rva >= s.virtual_address && rva < s.virtual_address + s.virtual_size)
}
pub fn rva_to_offset(&self, rva: u32) -> Option<usize> {
if (rva as usize) < self.size_of_headers() {
return Some(rva as usize);
}
let section = self.section_from_rva(rva)?;
let offset_in_section = rva - section.virtual_address;
Some(section.pointer_to_raw_data as usize + offset_in_section as usize)
}
pub fn read_at_rva<T: Copy>(&self, rva: u32) -> Option<T> {
let offset = self.rva_to_offset(rva)?;
if offset + core::mem::size_of::<T>() > self.data.len() {
return None;
}
Some(unsafe { (self.data.as_ptr().add(offset) as *const T).read_unaligned() })
}
pub fn read_string_at_rva(&self, rva: u32) -> Option<String> {
let offset = self.rva_to_offset(rva)?;
let mut end = offset;
while end < self.data.len() && self.data[end] != 0 {
end += 1;
}
String::from_utf8(self.data[offset..end].to_vec()).ok()
}
pub fn has_relocations(&self) -> bool {
self.data_directory(DataDirectoryType::Basereloc)
.map(|d| d.is_present())
.unwrap_or(false)
}
pub fn has_tls(&self) -> bool {
self.data_directory(DataDirectoryType::Tls)
.map(|d| d.is_present())
.unwrap_or(false)
}
pub fn is_dll(&self) -> bool {
const IMAGE_FILE_DLL: u16 = 0x2000;
self.file_header().characteristics & IMAGE_FILE_DLL != 0
}
pub fn supports_aslr(&self) -> bool {
const IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE: u16 = 0x0040;
let dll_characteristics = if self.is_64bit {
self.optional_header_64().unwrap().dll_characteristics
} else {
self.optional_header_32().unwrap().dll_characteristics
};
dll_characteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE != 0
}
pub fn dll_characteristics(&self) -> u16 {
if self.is_64bit {
self.optional_header_64().unwrap().dll_characteristics
} else {
self.optional_header_32().unwrap().dll_characteristics
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_self() {
let exe_path = std::env::current_exe().unwrap();
let data = std::fs::read(&exe_path).unwrap();
let pe = ParsedPe::parse(&data).expect("should parse");
assert!(pe.size_of_image() > 0);
assert!(pe.number_of_sections() > 0);
}
#[test]
fn test_parse_ntdll() {
let ntdll_path = r"C:\Windows\System32\ntdll.dll";
if let Ok(data) = std::fs::read(ntdll_path) {
let pe = ParsedPe::parse(&data).expect("should parse ntdll");
#[cfg(target_arch = "x86_64")]
assert!(pe.is_64bit());
assert!(pe.has_relocations());
assert!(pe.number_of_sections() > 0);
}
}
#[test]
fn test_section_access() {
let exe_path = std::env::current_exe().unwrap();
let data = std::fs::read(&exe_path).unwrap();
let pe = ParsedPe::parse(&data).unwrap();
let has_text = pe.sections().iter().any(|s| s.name_str() == ".text");
assert!(has_text);
}
}