Skip to content

Instantly share code, notes, and snippets.

@aristotaloss
Created March 6, 2023 03:40
Show Gist options
  • Save aristotaloss/a51f3eed1799fe569b92a14f37b20880 to your computer and use it in GitHub Desktop.
Save aristotaloss/a51f3eed1799fe569b92a14f37b20880 to your computer and use it in GitHub Desktop.
executable_parser.rs
//! 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