use core::fmt;
use crate::errors::FileParseError;
use crate::field::Field;
use crate::utils::{extract_u16, extract_u32, extract_u64};
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum PEType {
PE32,
PE32Plus,
}
#[derive(Clone, Copy)]
pub enum ImageBase {
Base32(u32),
Base64(u64),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SizedU64 {
U32(u32),
U64(u64),
}
pub struct DataDirectoryEntry {
pub virtual_address: Field<u32>,
pub size: Field<u32>,
}
pub const EXPORT: usize = 0;
pub const IMPORT: usize = 1;
pub const RESOURCE: usize = 2;
pub const EXCEPTION: usize = 3;
pub const SECURITY: usize = 4;
pub const BASERELOC: usize = 5;
pub const DEBUG: usize = 6;
pub const ARCHITECTURE: usize = 7;
pub const GLOBAL_PTR: usize = 8;
pub const TLS: usize = 9;
pub const LOAD_CONFIG: usize = 10;
pub const BOUND_IMPORT: usize = 11;
pub const IAT: usize = 12;
pub const DELAY_IMPORT: usize = 13;
pub const COM_DESCRIPTOR: usize = 14;
pub enum Architecture {
X86,
X64,
Armnt,
Arm64,
Arm64x,
Unknown,
}
impl fmt::Display for Architecture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match *self {
Architecture::X86 => "x86",
Architecture::X64 => "x64",
Architecture::Armnt => "armnt",
Architecture::Arm64 => "arm64",
Architecture::Arm64x => "arm64x",
Architecture::Unknown => "Unknown",
};
write!(f, "{s}")
}
}
impl Architecture {
pub fn from_u16(value: u16) -> Self {
match value {
0x014c => Architecture::X86,
0x8664 => Architecture::X64,
0x01c4 => Architecture::Armnt,
0xAA64 => Architecture::Arm64,
0xA64E => Architecture::Arm64x,
_ => Architecture::Unknown,
}
}
}
pub struct OptionalHeader {
pub magic: Field<u16>,
pub major_linker_version: Field<u8>,
pub minor_linker_version: Field<u8>,
pub size_of_code: Field<u32>,
pub size_of_initialized_data: Field<u32>,
pub size_of_uninitialized_data: Field<u32>,
pub entry_point: Field<u32>,
pub base_of_code: Field<u32>,
pub base_of_data: Option<Field<u32>>,
pub image_base: Field<ImageBase>,
pub section_alignment: Field<u32>,
pub file_alignment: Field<u32>,
pub major_operating_system_version: Field<u16>,
pub minor_operating_system_version: Field<u16>,
pub major_image_version: Field<u16>,
pub minor_image_version: Field<u16>,
pub major_subsystem_version: Field<u16>,
pub minor_subsystem_version: Field<u16>,
pub win32_version_value: Field<u32>,
pub size_of_image: Field<u32>,
pub size_of_headers: Field<u32>,
pub checksum: Field<u32>,
pub subsystem: Field<u16>,
pub dll_characteristics: Field<u16>,
pub size_of_stack_reserve: Field<SizedU64>,
pub size_of_stack_commit: Field<SizedU64>,
pub size_of_heap_reserve: Field<SizedU64>,
pub size_of_heap_commit: Field<SizedU64>,
pub loader_flags: Field<u32>,
pub number_of_rva_and_sizes: Field<u32>,
pub data_directories: [DataDirectoryEntry; 16],
}
impl DataDirectoryEntry {
pub fn parse(buffer: &[u8], offset: usize) -> Result<Self, FileParseError> {
Ok(DataDirectoryEntry {
virtual_address: Field::new(extract_u32(buffer, offset)?, offset, 4),
size: Field::new(extract_u32(buffer, offset + 4)?, offset + 4, 4),
})
}
}
impl OptionalHeader {
pub fn parse(buffer: &[u8], offset: usize) -> Result<Self, FileParseError> {
let magic = extract_u16(buffer, offset)?;
let pe_type = match magic {
0x10B => PEType::PE32,
0x20B => PEType::PE32Plus,
_ => return Err(FileParseError::InvalidFileFormat),
};
let extract_u8 = |buf: &[u8], off: usize| -> Result<u8, FileParseError> {
buf.get(off).copied().ok_or(FileParseError::BufferOverflow)
};
let parse_sized_u64 =
|buf: &[u8], off: usize, size: usize| -> Result<SizedU64, FileParseError> {
match size {
4 => Ok(SizedU64::U32(extract_u32(buf, off)?)),
8 => Ok(SizedU64::U64(extract_u64(buf, off)?)),
_ => Err(FileParseError::InvalidFileFormat),
}
};
let (
image_base,
base_of_data,
stack_size,
heap_size,
loader_flags_off,
number_of_rva_off,
data_dirs_off,
) = match pe_type {
PEType::PE32 => (
Field::new(
ImageBase::Base32(extract_u32(buffer, offset + 28)?),
offset + 28,
4,
),
Some(Field::new(
extract_u32(buffer, offset + 24)?,
offset + 24,
4,
)),
4usize,
4usize,
offset + 88,
offset + 92,
offset + 96,
),
PEType::PE32Plus => (
Field::new(
ImageBase::Base64(extract_u64(buffer, offset + 24)?),
offset + 24,
8,
),
None,
8usize,
8usize,
offset + 104,
offset + 108,
offset + 112,
),
};
let stack_reserve_off = offset + 72;
let stack_commit_off = stack_reserve_off + stack_size;
let heap_reserve_off = stack_commit_off + stack_size;
let heap_commit_off = heap_reserve_off + heap_size;
let number_of_rva_and_sizes = extract_u32(buffer, number_of_rva_off)?;
let active_directories = (number_of_rva_and_sizes as usize).min(16);
let min_end = data_dirs_off + active_directories * 8;
if buffer.len() < min_end {
return Err(FileParseError::BufferOverflow);
}
let mut data_directories: [DataDirectoryEntry; 16] =
std::array::from_fn(|i| DataDirectoryEntry {
virtual_address: Field::new(0, data_dirs_off + i * 8, 4),
size: Field::new(0, data_dirs_off + i * 8 + 4, 4),
});
for (i, dir) in data_directories
.iter_mut()
.enumerate()
.take(active_directories)
{
*dir = DataDirectoryEntry::parse(buffer, data_dirs_off + i * 8)?;
}
Ok(OptionalHeader {
magic: Field::new(magic, offset, 2),
major_linker_version: Field::new(extract_u8(buffer, offset + 2)?, offset + 2, 1),
minor_linker_version: Field::new(extract_u8(buffer, offset + 3)?, offset + 3, 1),
size_of_code: Field::new(extract_u32(buffer, offset + 4)?, offset + 4, 4),
size_of_initialized_data: Field::new(extract_u32(buffer, offset + 8)?, offset + 8, 4),
size_of_uninitialized_data: Field::new(
extract_u32(buffer, offset + 12)?,
offset + 12,
4,
),
entry_point: Field::new(extract_u32(buffer, offset + 16)?, offset + 16, 4),
base_of_code: Field::new(extract_u32(buffer, offset + 20)?, offset + 20, 4),
base_of_data,
image_base,
section_alignment: Field::new(extract_u32(buffer, offset + 32)?, offset + 32, 4),
file_alignment: Field::new(extract_u32(buffer, offset + 36)?, offset + 36, 4),
major_operating_system_version: Field::new(
extract_u16(buffer, offset + 40)?,
offset + 40,
2,
),
minor_operating_system_version: Field::new(
extract_u16(buffer, offset + 42)?,
offset + 42,
2,
),
major_image_version: Field::new(extract_u16(buffer, offset + 44)?, offset + 44, 2),
minor_image_version: Field::new(extract_u16(buffer, offset + 46)?, offset + 46, 2),
major_subsystem_version: Field::new(extract_u16(buffer, offset + 48)?, offset + 48, 2),
minor_subsystem_version: Field::new(extract_u16(buffer, offset + 50)?, offset + 50, 2),
win32_version_value: Field::new(extract_u32(buffer, offset + 52)?, offset + 52, 4),
size_of_image: Field::new(extract_u32(buffer, offset + 56)?, offset + 56, 4),
size_of_headers: Field::new(extract_u32(buffer, offset + 60)?, offset + 60, 4),
checksum: Field::new(extract_u32(buffer, offset + 64)?, offset + 64, 4),
subsystem: Field::new(extract_u16(buffer, offset + 68)?, offset + 68, 2),
dll_characteristics: Field::new(extract_u16(buffer, offset + 70)?, offset + 70, 2),
size_of_stack_reserve: Field::new(
parse_sized_u64(buffer, stack_reserve_off, stack_size)?,
stack_reserve_off,
stack_size,
),
size_of_stack_commit: Field::new(
parse_sized_u64(buffer, stack_commit_off, stack_size)?,
stack_commit_off,
stack_size,
),
size_of_heap_reserve: Field::new(
parse_sized_u64(buffer, heap_reserve_off, heap_size)?,
heap_reserve_off,
heap_size,
),
size_of_heap_commit: Field::new(
parse_sized_u64(buffer, heap_commit_off, heap_size)?,
heap_commit_off,
heap_size,
),
loader_flags: Field::new(extract_u32(buffer, loader_flags_off)?, loader_flags_off, 4),
number_of_rva_and_sizes: Field::new(number_of_rva_and_sizes, number_of_rva_off, 4),
data_directories,
})
}
pub fn active_data_directory_count(&self) -> usize {
(self.number_of_rva_and_sizes.value as usize).min(16)
}
pub fn has_data_directory(&self, index: usize) -> bool {
index < self.active_data_directory_count()
}
pub fn pe_type(&self) -> Result<PEType, FileParseError> {
match self.magic.value {
0x10B => Ok(PEType::PE32),
0x20B => Ok(PEType::PE32Plus),
_ => Err(FileParseError::InvalidFileFormat),
}
}
}
impl Field<ImageBase> {
pub fn update(
&mut self,
buffer: &mut [u8],
new_value: ImageBase,
) -> Result<(), FileParseError> {
match (new_value, self.size) {
(ImageBase::Base32(value), 4) => {
buffer[self.offset..self.offset + 4].copy_from_slice(&value.to_le_bytes());
self.value = new_value;
Ok(())
}
(ImageBase::Base64(value), 8) => {
buffer[self.offset..self.offset + 8].copy_from_slice(&value.to_le_bytes());
self.value = new_value;
Ok(())
}
_ => Err(FileParseError::InvalidFileFormat),
}
}
}
impl Field<SizedU64> {
pub fn update(&mut self, buffer: &mut [u8], new_value: SizedU64) -> Result<(), FileParseError> {
match (new_value, self.size) {
(SizedU64::U32(value), 4) => {
buffer[self.offset..self.offset + 4].copy_from_slice(&value.to_le_bytes());
self.value = new_value;
Ok(())
}
(SizedU64::U64(value), 8) => {
buffer[self.offset..self.offset + 8].copy_from_slice(&value.to_le_bytes());
self.value = new_value;
Ok(())
}
_ => Err(FileParseError::InvalidFileFormat),
}
}
}