Created
March 6, 2023 03:40
-
-
Save aristotaloss/a51f3eed1799fe569b92a14f37b20880 to your computer and use it in GitHub Desktop.
executable_parser.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! A parser for executable files. | |
//! | |
//! Does not use the standard library (entirely no_std) and also does not require | |
//! the `alloc` crate (but can optionally be enabled to turn on some convenience methods.) | |
// This library works fine without standard library support, but can provide some auto traits if | |
// desired. | |
#![cfg_attr(not(feature = "std"), no_std)] | |
// The `alloc` crate is optional, and not required for basic functionality. | |
#[cfg(feature = "alloc")] | |
extern crate alloc; | |
use core::fmt::Write; | |
// Standard-only imports (for providing blankets & docs) | |
#[cfg(feature = "std")] | |
use std::{ | |
fs::File, | |
io::{BufReader, Cursor, Read, Seek, SeekFrom}, | |
}; | |
use flags::{ImageCharacteristics, ImageDllCharacteristics, SectionCharacteristics}; | |
pub mod flags; | |
/// A type that can be arbitrarily read from at varying (possibly non-sequential) addresses at will. | |
/// | |
/// When the `std` feature is enabled, you get a blanket implementation for free on all types that | |
/// are both [`Seek`] and [`Read`], such as a [`BufReader`] or a | |
/// [`Cursor`] (wrapped around a `Vec<u8>`). | |
/// | |
/// **Warning: when implementing this trait, be aware that there is no implicit buffering done on | |
/// the [`ReadSeek`] object. \ | |
/// If you're using a [`File`] directly, wrap it in a [`BufReader`] to avoid a hit | |
/// in performance.** | |
pub trait ReadSeek { | |
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), IoError>; | |
fn seek_to(&mut self, pos: u64) -> Result<(), IoError>; | |
} | |
#[cfg(feature = "std")] | |
impl<T: Read + Seek> ReadSeek for T { | |
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), IoError> { | |
self.read_exact(buf).map_err(|e| e.into()) | |
} | |
fn seek_to(&mut self, pos: u64) -> Result<(), IoError> { | |
let npos = self | |
.seek(SeekFrom::Start(pos)) | |
.map_err(|e| IoError::from(e))?; | |
if npos == pos { | |
Ok(()) | |
} else { | |
Err(IoError::InvalidSeek) | |
} | |
} | |
} | |
#[non_exhaustive] | |
#[derive(Debug)] | |
pub enum IoError { | |
/// If the `std` feature is enabled, this error contains an error from the standard library. | |
#[cfg(feature = "std")] | |
FromStdIo(std::io::Error), | |
InvalidSeek, | |
/// The image that was attempted to be decoded was invalid, or not of a recognized format. | |
InvalidImage, | |
/// Returned if a crate feature is disabled which is required to proceed. | |
UnsupportedFeature, | |
Other, | |
} | |
#[cfg(feature = "std")] | |
impl From<std::io::Error> for IoError { | |
fn from(value: std::io::Error) -> Self { | |
IoError::FromStdIo(value) | |
} | |
} | |
struct ByteReader<'a, R: ReadSeek> { | |
pos: u64, | |
r: &'a mut R, | |
} | |
impl<'a, R: ReadSeek> ByteReader<'a, R> { | |
fn new(r: &'a mut R) -> Self { | |
Self { pos: 0, r } | |
} | |
fn read_into(&mut self, dest: &mut [u8]) -> Result<(), IoError> { | |
self.r.seek_to(self.pos)?; | |
self.r.read_exact(dest)?; | |
self.pos += dest.len() as u64; | |
Ok(()) | |
} | |
fn read_u8(&mut self) -> Result<u8, IoError> { | |
let mut buf = [0u8; 1]; | |
self.read_into(&mut buf)?; | |
Ok(buf[0]) | |
} | |
fn read_u16(&mut self) -> Result<u16, IoError> { | |
let mut buf = [0u8; 2]; | |
self.read_into(&mut buf)?; | |
Ok(u16::from_le_bytes(buf)) | |
} | |
fn read_u32(&mut self) -> Result<u32, IoError> { | |
let mut buf = [0u8; 4]; | |
self.read_into(&mut buf)?; | |
Ok(u32::from_le_bytes(buf)) | |
} | |
fn read_u64(&mut self) -> Result<u64, IoError> { | |
let mut buf = [0u8; 8]; | |
self.read_into(&mut buf)?; | |
Ok(u64::from_le_bytes(buf)) | |
} | |
fn seek_to(&mut self, pos: u64) -> Result<(), IoError> { | |
self.pos = pos; | |
Ok(()) | |
} | |
} | |
#[derive(Debug)] | |
pub enum Executable { | |
Pe(PeExecutable), | |
Elf, | |
} | |
impl Executable { | |
/// Probes the given data stream for a header we recognize, in which case the type of header | |
/// is returned. | |
pub fn probe<R: ReadSeek>(r: &mut R) -> Result<Option<ImageHeader>, IoError> { | |
let mut r = ByteReader::new(r); | |
// PE begins with a DOS-header, and has a jump at 0x3c to the PE header. | |
let dos_sig = r.read_u16()?; | |
if dos_sig == 0x5A4D { | |
r.seek_to(0x3C)?; | |
let offset = r.read_u16()?; | |
// If the magic matches the PE signature ('PE\0\0'), we assume it's really a PE image. | |
// A call to `parse` will have the final say in validity. | |
r.seek_to(offset as _)?; | |
let magic = r.read_u32()?; | |
if magic == 0x4550 { | |
return Ok(Some(ImageHeader::Pe { | |
header_start: offset as _, | |
})); | |
} | |
} | |
Ok(None) | |
} | |
/// Attempts to parse an executable file using data provided by the read-seek instance passed. | |
/// | |
/// This will first call [`probe`] to determine what type of image we're dealing with, and if | |
/// that image type is supported, proceed with decoding it according to its specification. | |
pub fn parse<R: ReadSeek>(r: &mut R) -> Result<Self, IoError> { | |
let probe = Self::probe(r)?.ok_or(IoError::InvalidImage)?; | |
match probe { | |
ImageHeader::Pe { header_start } => { | |
Ok(Self::Pe(PeExecutable::decode(r, header_start)?)) | |
} | |
ImageHeader::Elf => todo!(), | |
} | |
} | |
/// Turns this enum into a concrete [`PeExecutable`], if it is of that type, otherwise panic. | |
pub fn as_pe(self) -> PeExecutable { | |
match self { | |
Executable::Pe(pe) => pe, | |
Executable::Elf => panic!("image was not a PE file"), | |
} | |
} | |
} | |
pub struct ElfExecutable { | |
// .. stub | |
} | |
#[derive(Debug)] | |
pub struct PeExecutable { | |
pub coff_header: CoffFileHeader, | |
pub data_directories: ImageDataDirectories, | |
/// Offset into the image sections, used when creating the iterator. | |
sections_offset: u64, | |
} | |
impl PeExecutable { | |
pub fn decode<R: ReadSeek>(r: &mut R, pe_start: u64) -> Result<Self, IoError> { | |
let mut r = ByteReader::new(r); | |
r.seek_to(pe_start)?; | |
// Ensure header is present here | |
let magic = r.read_u32()?; | |
if magic != 0x4550 { | |
return Err(IoError::InvalidImage); | |
} | |
let coff_header = CoffFileHeader::decode(&mut r)?; | |
let data_directories = ImageDataDirectories::decode(&mut r)?; | |
let sections_offset = r.pos; | |
Ok(Self { | |
coff_header, | |
data_directories, | |
sections_offset, | |
}) | |
} | |
/// Creates a new iterator over the available sections in this executable. | |
/// | |
/// It takes a [`ReadSeek`] which has the same data at address 0 as the instance did that was | |
/// passed to the executable's [`decode`] call. | |
pub fn sections<'a, 'r, R: ReadSeek>(&'a self, r: &'r mut R) -> SectionIterator<'r, R> { | |
SectionIterator { | |
r, | |
index: 0, | |
count: self.coff_header.number_of_sections as _, | |
start_addr: self.sections_offset, | |
} | |
} | |
} | |
pub struct SectionIterator<'r, R: ReadSeek> { | |
r: &'r mut R, | |
index: usize, | |
count: usize, | |
start_addr: u64, | |
} | |
impl<'r, R: ReadSeek> Iterator for SectionIterator<'r, R> { | |
type Item = Result<ImageSectionHeader, IoError>; | |
fn next(&mut self) -> Option<Self::Item> { | |
if self.index < self.count { | |
// Create a reader, and position it right where it needs to be. | |
let mut r = ByteReader::new(self.r); | |
r.seek_to(self.start_addr + (self.index as u64 * ImageSectionHeader::SIZE_IN_FILE)) | |
.ok()?; | |
self.index += 1; | |
// We had a "next", but we don't guarantee it's valid yet, so we return the decode | |
// result. It can still be an invalid image. | |
Some(ImageSectionHeader::decode(&mut r)) | |
} else { | |
None | |
} | |
} | |
} | |
#[derive(Debug, Clone)] | |
pub enum ImageHeader { | |
/// A Windows-esque "Portable Executable", abbreviated to `PE`. | |
Pe { header_start: u64 }, | |
/// An `Executable and Linkable Format` file. | |
Elf, | |
} | |
#[repr(u16)] | |
#[derive(Debug, Clone)] | |
pub enum ImageArchitecture { | |
Unknown = 0x0, | |
AlphaAXP32 = 0x184, | |
AlphaAXP64 = 0x284, | |
MatsushitaAM33 = 0x1d3, | |
X64 = 0x8664, | |
ARMLittleEndian = 0x1c0, | |
ARM64LittleEndian = 0xaa64, | |
ARMThumb2LittleEndian = 0x1c4, | |
EFIByteCode = 0xebc, | |
Intel386 = 0x14c, | |
IntelItanium = 0x200, | |
LoongArch32 = 0x6232, | |
LoongArch64 = 0x6264, | |
MitsubishiM32RLittleEndian = 0x9041, | |
MIPS16 = 0x266, | |
MIPSwithFPU = 0x366, | |
MIPS16withFPU = 0x466, | |
PowerPCLittleEndian = 0x1f0, | |
PowerPCwithFloatingPointSupport = 0x1f1, | |
MIPSLittleEndian = 0x166, | |
RiscV32bitAddressSpace = 0x5032, | |
RiscV64bitAddressSpace = 0x5064, | |
RiscV128bitAddressSpace = 0x5128, | |
HitachiSH3 = 0x1a2, | |
HitachiSH3DSP = 0x1a3, | |
HitachiSH4 = 0x1a6, | |
HitachiSH5 = 0x1a8, | |
Thumb = 0x1c2, | |
MIPSLittleEndianWCEv2 = 0x169, | |
} | |
impl From<u16> for ImageArchitecture { | |
fn from(value: u16) -> Self { | |
match value { | |
0x0 => ImageArchitecture::Unknown, | |
0x184 => ImageArchitecture::AlphaAXP32, | |
0x284 => ImageArchitecture::AlphaAXP64, | |
0x1d3 => ImageArchitecture::MatsushitaAM33, | |
0x8664 => ImageArchitecture::X64, | |
0x1c0 => ImageArchitecture::ARMLittleEndian, | |
0xaa64 => ImageArchitecture::ARM64LittleEndian, | |
0x1c4 => ImageArchitecture::ARMThumb2LittleEndian, | |
0xebc => ImageArchitecture::EFIByteCode, | |
0x14c => ImageArchitecture::Intel386, | |
0x200 => ImageArchitecture::IntelItanium, | |
0x6232 => ImageArchitecture::LoongArch32, | |
0x6264 => ImageArchitecture::LoongArch64, | |
0x9041 => ImageArchitecture::MitsubishiM32RLittleEndian, | |
0x266 => ImageArchitecture::MIPS16, | |
0x366 => ImageArchitecture::MIPSwithFPU, | |
0x466 => ImageArchitecture::MIPS16withFPU, | |
0x1f0 => ImageArchitecture::PowerPCLittleEndian, | |
0x1f1 => ImageArchitecture::PowerPCwithFloatingPointSupport, | |
0x166 => ImageArchitecture::MIPSLittleEndian, | |
0x5032 => ImageArchitecture::RiscV32bitAddressSpace, | |
0x5064 => ImageArchitecture::RiscV64bitAddressSpace, | |
0x5128 => ImageArchitecture::RiscV128bitAddressSpace, | |
0x1a2 => ImageArchitecture::HitachiSH3, | |
0x1a3 => ImageArchitecture::HitachiSH3DSP, | |
0x1a6 => ImageArchitecture::HitachiSH4, | |
0x1a8 => ImageArchitecture::HitachiSH5, | |
0x1c2 => ImageArchitecture::Thumb, | |
0x169 => ImageArchitecture::MIPSLittleEndianWCEv2, | |
_ => ImageArchitecture::Unknown, | |
} | |
} | |
} | |
/// The COFF file header that precedes the sections and symbol table in a COFF object file or | |
/// executable file. | |
#[derive(Debug)] | |
pub struct CoffFileHeader { | |
/// The [`ImageArchitecture`] that identifies the type of target machine. | |
/// For more information, see [Machine Types][1]. | |
/// | |
/// [1]: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types | |
pub machine: ImageArchitecture, | |
/// The number of sections. This indicates the size of the section table, which immediately follows the headers. | |
pub number_of_sections: u16, | |
/// The low 32 bits of the number of seconds since 00:00 January 1, 1970 (a C run-time time_t value), | |
/// which indicates when the file was created. | |
pub time_date_stamp: u32, | |
/// The file offset of the COFF symbol table, or zero if no COFF symbol table is present. | |
/// This value should be zero for an image because COFF debugging information is deprecated. | |
pub pointer_to_symbol_table: u32, | |
/// The number of entries in the symbol table. This data can be used to locate the string table, | |
/// which immediately follows the symbol table. This value should be zero for an image because COFF debugging information is deprecated. | |
pub number_of_symbols: u32, | |
/// The size of the optional header, which is required for executable files but not for object files. | |
/// This value should be zero for an object file. For a description of the header format, see Optional Header (Image Only). | |
pub size_of_optional_header: u16, | |
/// The flags that indicate the attributes of the file. For specific flag values, see [`ImageCharacteristics`]. | |
pub characteristics: ImageCharacteristics, | |
pub opt_header: Option<OptionalHeader>, | |
} | |
impl CoffFileHeader { | |
/// Decodes the COFF file header from the given reader at the specified PE file offset. | |
fn decode<R: ReadSeek>(r: &mut ByteReader<R>) -> Result<Self, IoError> { | |
// Read the header fields from the binary data | |
let machine = r.read_u16()?.into(); | |
let number_of_sections = r.read_u16()?; | |
let time_date_stamp = r.read_u32()?; | |
let pointer_to_symbol_table = r.read_u32()?; | |
let number_of_symbols = r.read_u32()?; | |
let size_of_optional_header = r.read_u16()?; | |
let characteristics = unsafe { ImageCharacteristics::from_bits_unchecked(r.read_u16()?) }; | |
let opt_header = if size_of_optional_header > 0 { | |
Some(OptionalHeader::decode(r)?) | |
} else { | |
None | |
}; | |
Ok(Self { | |
machine, | |
number_of_sections, | |
time_date_stamp, | |
pointer_to_symbol_table, | |
number_of_symbols, | |
size_of_optional_header, | |
characteristics, | |
opt_header, | |
}) | |
} | |
} | |
#[derive(Debug)] | |
pub struct OptionalHeader { | |
pub standard: OptionalHeaderStandardFields, | |
pub windows_specific: OptionalHeaderWindowsSpecificFields, | |
} | |
impl OptionalHeader { | |
fn decode<R: ReadSeek>(r: &mut ByteReader<R>) -> Result<Self, IoError> { | |
let magic = r.read_u16()?; | |
let pe32 = magic == 0x10B; | |
let standard = OptionalHeaderStandardFields::decode(r, pe32)?; | |
let windows_specific = OptionalHeaderWindowsSpecificFields::decode(r, pe32)?; | |
Ok(Self { | |
standard, | |
windows_specific, | |
}) | |
} | |
} | |
#[derive(Debug)] | |
pub struct OptionalHeaderStandardFields { | |
/// The format version number. | |
pub major_linker_version: u8, | |
pub minor_linker_version: u8, | |
/// The size of the code (text) section, or the sum of all code sections if there are multiple sections. | |
pub size_of_code: u32, | |
/// The size of the initialized data section, or the sum of all such sections if there are multiple data sections. | |
pub size_of_initialized_data: u32, | |
/// The size of the uninitialized data section (BSS), which is always zero for an executable image. | |
pub size_of_uninitialized_data: u32, | |
/// The address of the entry point relative to the image base when the executable file is loaded into memory. | |
/// For program images, this is the starting address. For DLLs, this is the address of the DLL initialization function. | |
/// The entry point is optional for DLLs. When no entry point is present, this field must be zero. | |
pub address_of_entry_point: u32, | |
/// The address that is relative to the image base of the beginning-of-code section when it is loaded into memory. | |
pub base_of_code: u32, | |
/// The address that is relative to the image base of the beginning-of-data section when it is loaded into memory. | |
/// | |
/// This field is only present in `PE32` files, not in `PE32+`. | |
pub base_of_data: Option<u32>, | |
} | |
impl OptionalHeaderStandardFields { | |
/// Decodes the "Optional Header Standard Fields" from the given reader at the specified PE file offset. | |
fn decode<R: ReadSeek>(r: &mut ByteReader<R>, pe32: bool) -> Result<Self, IoError> { | |
let major_linker_version = r.read_u8()?; | |
let minor_linker_version = r.read_u8()?; | |
let size_of_code = r.read_u32()?; | |
let size_of_initialized_data = r.read_u32()?; | |
let size_of_uninitialized_data = r.read_u32()?; | |
let address_of_entry_point = r.read_u32()?; | |
let base_of_code = r.read_u32()?; | |
let base_of_data = if pe32 { Some(r.read_u32()?) } else { None }; | |
Ok(Self { | |
major_linker_version, | |
minor_linker_version, | |
size_of_code, | |
size_of_initialized_data, | |
size_of_uninitialized_data, | |
address_of_entry_point, | |
base_of_code, | |
base_of_data, | |
}) | |
} | |
} | |
#[derive(Debug)] | |
pub struct OptionalHeaderWindowsSpecificFields { | |
/// The preferred address of the first byte of image when loaded into memory; must be a multiple of 64 K. | |
pub image_base: u64, | |
/// The alignment (in bytes) of sections when they are loaded into memory. | |
/// Must be greater than or equal to [`SectionAlignment`]. | |
pub section_alignment: u32, | |
/// The alignment factor (in bytes) that is used to align the raw data of sections in the image file. | |
pub file_alignment: u32, | |
/// The major version number of the required operating system. | |
pub major_os_version: u16, | |
/// The minor version number of the required operating system. | |
pub minor_os_version: u16, | |
/// The major version number of the image. | |
pub major_image_version: u16, | |
/// The minor version number of the image. | |
pub minor_image_version: u16, | |
/// The major version number of the subsystem. | |
pub major_subsystem_version: u16, | |
/// The minor version number of the subsystem. | |
pub minor_subsystem_version: u16, | |
/// Reserved, must be zero. | |
pub win32_version_value: u32, | |
/// The size (in bytes) of the image, including all headers, as loaded into memory. | |
pub size_of_image: u32, | |
/// The combined size of the following items, rounded to a multiple of the value specified in [`FileAlignment`]: | |
/// - the [`CoffFileHeader`], | |
/// - the [`OptionalHeader`], | |
/// - the section headers, and | |
/// - the pad bytes used to align the data. | |
pub size_of_headers: u32, | |
/// The image file checksum. | |
pub checksum: u32, | |
/// The subsystem required to run this image. | |
pub subsystem: WindowsSubsystem, | |
/// The DLL characteristics of the image. | |
pub dll_characteristics: ImageDllCharacteristics, | |
/// The size of the stack to reserve. | |
pub size_of_stack_reserve: u64, | |
/// The size of the stack to commit. | |
pub size_of_stack_commit: u64, | |
/// The size of the local heap space to reserve. | |
pub size_of_heap_reserve: u64, | |
/// The size of the local heap space to commit. | |
pub size_of_heap_commit: u64, | |
/// Reserved, must be zero. | |
pub loader_flags: u32, | |
/// The number of data-directory entries in the remainder of the optional header. | |
pub number_of_rva_and_sizes: u32, | |
} | |
impl OptionalHeaderWindowsSpecificFields { | |
/// Decodes the "Optional Header Windows-Specific Fields" from the given reader at the specified PE file offset. | |
fn decode<R: ReadSeek>(r: &mut ByteReader<R>, pe32: bool) -> Result<Self, IoError> { | |
let image_base = if pe32 { | |
r.read_u32()? as u64 | |
} else { | |
r.read_u64()? | |
}; | |
let section_alignment = r.read_u32()?; | |
let file_alignment = r.read_u32()?; | |
let major_os_version = r.read_u16()?; | |
let minor_os_version = r.read_u16()?; | |
let major_image_version = r.read_u16()?; | |
let minor_image_version = r.read_u16()?; | |
let major_subsystem_version = r.read_u16()?; | |
let minor_subsystem_version = r.read_u16()?; | |
let win32_version_value = r.read_u32()?; | |
let size_of_image = r.read_u32()?; | |
let size_of_headers = r.read_u32()?; | |
let checksum = r.read_u32()?; | |
let subsystem = WindowsSubsystem::from_u16(r.read_u16()?); | |
let dll_characteristics = | |
unsafe { ImageDllCharacteristics::from_bits_unchecked(r.read_u16()?) }; | |
let size_of_stack_reserve = if pe32 { | |
r.read_u32()? as u64 | |
} else { | |
r.read_u64()? | |
}; | |
let size_of_stack_commit = if pe32 { | |
r.read_u32()? as u64 | |
} else { | |
r.read_u64()? | |
}; | |
let size_of_heap_reserve = if pe32 { | |
r.read_u32()? as u64 | |
} else { | |
r.read_u64()? | |
}; | |
let size_of_heap_commit = if pe32 { | |
r.read_u32()? as u64 | |
} else { | |
r.read_u64()? | |
}; | |
let loader_flags = r.read_u32()?; | |
let number_of_rva_and_sizes = r.read_u32()?; | |
Ok(Self { | |
image_base, | |
section_alignment, | |
file_alignment, | |
major_os_version, | |
minor_os_version, | |
major_image_version, | |
minor_image_version, | |
major_subsystem_version, | |
minor_subsystem_version, | |
win32_version_value, | |
size_of_image, | |
size_of_headers, | |
checksum, | |
subsystem, | |
dll_characteristics, | |
size_of_stack_reserve, | |
size_of_stack_commit, | |
size_of_heap_reserve, | |
size_of_heap_commit, | |
loader_flags, | |
number_of_rva_and_sizes, | |
}) | |
} | |
} | |
#[derive(Debug, Copy, Clone, PartialEq, Eq)] | |
#[repr(u16)] | |
pub enum WindowsSubsystem { | |
/// An unknown subsystem. | |
Unknown = 0, | |
/// Device drivers and native Windows processes. | |
Native = 1, | |
/// The Windows graphical user interface (GUI) subsystem. | |
WindowsGui = 2, | |
/// The Windows character subsystem. | |
WindowsCui = 3, | |
/// The OS/2 character subsystem. | |
Os2Cui = 5, | |
/// The Posix character subsystem. | |
PosixCui = 7, | |
/// Native Win9x driver. | |
NativeWindows = 8, | |
/// Windows CE. | |
WindowsCeGui = 9, | |
/// An Extensible Firmware Interface (EFI) application. | |
EfiApplication = 10, | |
/// An EFI driver with boot services. | |
EfiBootServiceDriver = 11, | |
/// An EFI driver with run-time services. | |
EfiRuntimeDriver = 12, | |
/// An EFI ROM image. | |
EfiRom = 13, | |
/// Xbox. | |
Xbox = 14, | |
/// Windows boot application. | |
WindowsBootApplication = 16, | |
} | |
impl WindowsSubsystem { | |
/// Attempts to create a WindowsSubsystem variant from a u16 value. | |
/// Unknown values will return the `Unknown` variant. | |
pub fn from_u16(value: u16) -> Self { | |
match value { | |
0 => Self::Unknown, | |
1 => Self::Native, | |
2 => Self::WindowsGui, | |
3 => Self::WindowsCui, | |
5 => Self::Os2Cui, | |
7 => Self::PosixCui, | |
8 => Self::NativeWindows, | |
9 => Self::WindowsCeGui, | |
10 => Self::EfiApplication, | |
11 => Self::EfiBootServiceDriver, | |
12 => Self::EfiRuntimeDriver, | |
13 => Self::EfiRom, | |
14 => Self::Xbox, | |
16 => Self::WindowsBootApplication, | |
_ => Self::Unknown, | |
} | |
} | |
} | |
#[derive(Debug)] | |
pub struct ImageDataDirectories { | |
/// The export table address and size. | |
pub export_table: Option<ImageDataDirectory>, | |
/// The import table address and size. | |
pub import_table: Option<ImageDataDirectory>, | |
/// The resource table address and size. | |
pub resource_table: Option<ImageDataDirectory>, | |
/// The exception table address and size. | |
pub exception_table: Option<ImageDataDirectory>, | |
/// The attribute certificate table address and size. | |
pub certificate_table: Option<ImageDataDirectory>, | |
/// The base relocation table address and size. | |
pub base_relocation_table: Option<ImageDataDirectory>, | |
/// The debug data starting address and size. | |
pub debug: Option<ImageDataDirectory>, | |
/// Reserved. | |
pub architecture: Option<ImageDataDirectory>, | |
/// The RVA of the value to be stored in the global pointer register. | |
pub global_ptr: Option<ImageDataDirectory>, | |
/// The thread local storage (TLS) table address and size. | |
pub tls_table: Option<ImageDataDirectory>, | |
/// The load configuration table address and size. | |
pub load_config_table: Option<ImageDataDirectory>, | |
/// The bound import table address and size. | |
pub bound_import: Option<ImageDataDirectory>, | |
/// The import address table address and size. | |
pub iat: Option<ImageDataDirectory>, | |
/// The delay import descriptor address and size. | |
pub delay_import_descriptor: Option<ImageDataDirectory>, | |
/// The CLR runtime header address and size. | |
pub clr_runtime_header: Option<ImageDataDirectory>, | |
} | |
impl ImageDataDirectories { | |
/// Decodes the image data directories from the given reader at the specified PE file offset. | |
fn decode<R: ReadSeek>(r: &mut ByteReader<R>) -> Result<Self, IoError> { | |
let export_table = ImageDataDirectory::decode(r)?; | |
let import_table = ImageDataDirectory::decode(r)?; | |
let resource_table = ImageDataDirectory::decode(r)?; | |
let exception_table = ImageDataDirectory::decode(r)?; | |
let certificate_table = ImageDataDirectory::decode(r)?; | |
let base_relocation_table = ImageDataDirectory::decode(r)?; | |
let debug = ImageDataDirectory::decode(r)?; | |
let architecture = ImageDataDirectory::decode(r)?; | |
let global_ptr = ImageDataDirectory::decode(r)?; | |
let tls_table = ImageDataDirectory::decode(r)?; | |
let load_config_table = ImageDataDirectory::decode(r)?; | |
let bound_import = ImageDataDirectory::decode(r)?; | |
let iat = ImageDataDirectory::decode(r)?; | |
let delay_import_descriptor = ImageDataDirectory::decode(r)?; | |
let clr_runtime_header = ImageDataDirectory::decode(r)?; | |
let _reserved = ImageDataDirectory::decode(r)?; | |
Ok(Self { | |
export_table, | |
import_table, | |
resource_table, | |
exception_table, | |
certificate_table, | |
base_relocation_table, | |
debug, | |
architecture, | |
global_ptr, | |
tls_table, | |
load_config_table, | |
bound_import, | |
iat, | |
delay_import_descriptor, | |
clr_runtime_header, | |
}) | |
} | |
} | |
/// Describes an image data directory, which contains the address and size of a specific block of data within the image file. | |
#[derive(Debug, Clone, Copy)] | |
pub struct ImageDataDirectory { | |
/// The RVA (relative virtual address) of the data. | |
pub virtual_address: u32, | |
/// The size of the data in bytes. | |
pub size: u32, | |
} | |
impl ImageDataDirectory { | |
/// Decodes an image data directory from the given reader. | |
fn decode<R: ReadSeek>(r: &mut ByteReader<R>) -> Result<Option<Self>, IoError> { | |
let virtual_address = r.read_u32()?; | |
let size = r.read_u32()?; | |
if virtual_address == 0 && size == 0 { | |
return Ok(None); | |
} | |
Ok(Some(Self { | |
virtual_address, | |
size, | |
})) | |
} | |
} | |
pub struct AsciiByteStr<const S: usize>([u8; S]); | |
impl<const S: usize> core::fmt::Display for AsciiByteStr<S> { | |
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | |
for b in self.0 { | |
if b == 0 { | |
break; | |
} | |
f.write_char(b as _)?; | |
} | |
Ok(()) | |
} | |
} | |
impl<const S: usize> core::fmt::Debug for AsciiByteStr<S> { | |
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | |
for b in self.0 { | |
if b == 0 { | |
break; | |
} | |
f.write_char(b as _)?; | |
} | |
Ok(()) | |
} | |
} | |
#[derive(Debug)] | |
pub struct ImageSectionHeader { | |
/// An 8-byte, null-padded UTF-8 encoded string. | |
/// If the string is exactly 8 characters long, there is no terminating null. | |
/// For longer names, this field contains a slash (/) that is followed by an ASCII representation of a decimal number | |
/// that is an offset into the string table. | |
pub name: AsciiByteStr<8>, | |
/// The total size of the section when loaded into memory. | |
/// If this value is greater than SizeOfRawData, the section is zero-padded. | |
/// This field is valid only for executable images and should be set to zero for object files. | |
pub virtual_size: u32, | |
/// For executable images, the address of the first byte of the section relative to the image base | |
/// when the section is loaded into memory. | |
/// For object files, this field is the address of the first byte before relocation is applied; | |
/// for simplicity, compilers should set this to zero. Otherwise, it is an arbitrary value | |
/// that is subtracted from offsets during relocation. | |
pub virtual_address: u32, | |
/// The size of the section (for object files) or the size of the initialized data on disk (for image files). | |
/// For executable images, this must be a multiple of FileAlignment from the optional header. | |
/// If this is less than VirtualSize, the remainder of the section is zero-filled. | |
/// Because the SizeOfRawData field is rounded but the VirtualSize field is not, | |
/// it is possible for SizeOfRawData to be greater than VirtualSize as well. | |
/// When a section contains only uninitialized data, this field should be zero. | |
pub size_of_raw_data: u32, | |
/// The file pointer to the first page of the section within the COFF file. | |
/// For executable images, this must be a multiple of FileAlignment from the optional header. | |
/// For object files, the value should be aligned on a 4-byte boundary for best performance. | |
/// When a section contains only uninitialized data, this field should be zero. | |
pub pointer_to_raw_data: u32, | |
/// The file pointer to the beginning of relocation entries for the section. | |
/// This is set to zero for executable images or if there are no relocations. | |
pub pointer_to_relocations: u32, | |
/// The file pointer to the beginning of line-number entries for the section. | |
/// This is set to zero if there are no COFF line numbers. | |
/// This value should be zero for an image because COFF debugging information is deprecated. | |
pub pointer_to_linenumbers: u32, | |
/// The number of relocation entries for the section. | |
/// This is set to zero for executable images. | |
pub number_of_relocations: u16, | |
/// The number of line-number entries for the section. | |
/// This value should be zero for an image because COFF debugging information is deprecated. | |
pub number_of_linenumbers: u16, | |
/// The flags that describe the characteristics of the section. | |
/// For more information, see Section Flags. | |
pub characteristics: SectionCharacteristics, | |
} | |
impl ImageSectionHeader { | |
const IMAGE_SIZEOF_SHORT_NAME: usize = 8; | |
const SIZE_IN_FILE: u64 = 40; | |
/// Decodes an `ImageSectionHeader` from the given reader at the specified PE file offset. | |
fn decode<R: ReadSeek>(r: &mut ByteReader<R>) -> Result<Self, IoError> { | |
let mut name = [0u8; Self::IMAGE_SIZEOF_SHORT_NAME]; | |
r.read_into(&mut name)?; | |
let name = AsciiByteStr(name); | |
let virtual_size = r.read_u32()?; | |
let virtual_address = r.read_u32()?; | |
let size_of_raw_data = r.read_u32()?; | |
let pointer_to_raw_data = r.read_u32()?; | |
let pointer_to_relocations = r.read_u32()?; | |
let pointer_to_linenumbers = r.read_u32()?; | |
let number_of_relocations = r.read_u16()?; | |
let number_of_linenumbers = r.read_u16()?; | |
let characteristics = unsafe { SectionCharacteristics::from_bits_unchecked(r.read_u32()?) }; | |
Ok(Self { | |
name, | |
virtual_size, | |
virtual_address, | |
size_of_raw_data, | |
pointer_to_raw_data, | |
pointer_to_relocations, | |
pointer_to_linenumbers, | |
number_of_relocations, | |
number_of_linenumbers, | |
characteristics, | |
}) | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use std::fs::File; | |
#[test] | |
fn read_pe32() { | |
let f = File::open(r"bitos_fp_on.efi").unwrap(); | |
let mut rd = BufReader::new(f); | |
let exe = Executable::parse(&mut rd).expect("invalid PE"); | |
println!("{:#X?}", exe); | |
println!(); | |
println!("sections:"); | |
for sect in exe.as_pe().sections(&mut rd).flatten() { | |
println!("{sect:#X?}"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment