pub mod exports;
pub mod header;
pub mod imports;
pub mod reloc;
pub mod sections;
use std::collections::BTreeMap;
use crate::emulator::{mmu::Mmu, Trap};
use crate::win32::{HostState, Registry};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PeError {
TooSmall { got: usize, need: usize },
NotMz,
BadELfanew { offset: u32, file_len: usize },
NotPe,
Pe32PlusUnsupported,
BadOptionalHeaderMagic { magic: u16 },
UnsupportedMachine { machine: u16 },
DirectoryOutOfRange {
name: &'static str,
rva: u32,
size: u32,
},
ManagedPe,
UnknownImportDll { dll: String },
UnknownImportFunction { dll: String, name: String },
SectionOutOfRange {
name: String,
raw_off: u32,
raw_size: u32,
},
BadRelocBlock { rva: u32, reason: &'static str },
Trap(Trap),
}
impl core::fmt::Display for PeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PeError::TooSmall { got, need } => {
write!(f, "PE file too small: {got} bytes, need ≥ {need}")
}
PeError::NotMz => f.write_str("missing 'MZ' DOS signature"),
PeError::BadELfanew { offset, file_len } => {
write!(f, "e_lfanew {offset:#x} outside file (len {file_len})")
}
PeError::NotPe => f.write_str("missing 'PE\\0\\0' signature"),
PeError::Pe32PlusUnsupported => f.write_str("PE32+ (64-bit) not supported"),
PeError::BadOptionalHeaderMagic { magic } => {
write!(f, "bad optional-header magic {magic:#x}")
}
PeError::UnsupportedMachine { machine } => {
write!(f, "machine {machine:#x} is not IMAGE_FILE_MACHINE_I386")
}
PeError::DirectoryOutOfRange { name, rva, size } => {
write!(
f,
"directory {name} (rva {rva:#x}, size {size}) out of image range"
)
}
PeError::ManagedPe => f.write_str("managed (.NET) PE not supported"),
PeError::UnknownImportDll { dll } => {
write!(f, "no Round-1 stub registry entry for DLL '{dll}'")
}
PeError::UnknownImportFunction { dll, name } => {
write!(f, "no stub for {dll}!{name}")
}
PeError::SectionOutOfRange {
name,
raw_off,
raw_size,
} => {
write!(
f,
"section '{name}' raw bytes [{raw_off:#x}..+{raw_size}] out of file"
)
}
PeError::BadRelocBlock { rva, reason } => {
write!(f, "malformed reloc block at rva {rva:#x}: {reason}")
}
PeError::Trap(t) => write!(f, "MMU trap during load: {t}"),
}
}
}
impl From<Trap> for PeError {
fn from(t: Trap) -> Self {
PeError::Trap(t)
}
}
#[derive(Debug, Clone)]
pub struct Image {
pub name: String,
pub image_base: u32,
pub entry_point: u32,
pub size_of_image: u32,
pub sections: Vec<sections::Section>,
pub exports: BTreeMap<String, u32>,
}
impl Image {
pub fn export(&self, name: &str) -> Option<u32> {
self.exports
.get(name)
.map(|rva| self.image_base.wrapping_add(*rva))
}
}
#[derive(Debug, Default, Clone)]
pub struct LoadOptions {
pub imports: imports::ResolveMode,
pub fail_soft_log: Option<Vec<(String, String)>>,
pub target_image_base: Option<u32>,
}
impl Default for imports::ResolveMode {
fn default() -> Self {
Self::Strict
}
}
pub struct Loader<'a> {
mmu: &'a mut Mmu,
registry: &'a mut Registry,
host: &'a mut HostState,
}
impl<'a> Loader<'a> {
pub fn new(mmu: &'a mut Mmu, registry: &'a mut Registry, host: &'a mut HostState) -> Self {
Loader {
mmu,
registry,
host,
}
}
pub fn load(&mut self, name: &str, bytes: &[u8]) -> Result<Image, PeError> {
self.load_with_options(name, bytes, &mut LoadOptions::default())
}
pub fn load_with_options(
&mut self,
name: &str,
bytes: &[u8],
options: &mut LoadOptions,
) -> Result<Image, PeError> {
let parsed = header::parse(bytes)?;
let preferred_base = parsed.optional.image_base;
let load_base = options.target_image_base.unwrap_or(preferred_base);
let delta = load_base.wrapping_sub(preferred_base);
let secs = sections::map_sections_at(self.mmu, &parsed, bytes, load_base)?;
if delta != 0 {
reloc::apply(self.mmu, &parsed, load_base, delta)?;
}
imports::resolve_with(
self.mmu,
&parsed,
load_base,
self.registry,
options.imports,
options.fail_soft_log.as_mut(),
)?;
let exports = exports::parse_exports(&parsed, bytes, load_base)?;
for s in &secs {
sections::apply_section_permissions(self.mmu, s);
}
let image = Image {
name: name.to_string(),
image_base: load_base,
entry_point: load_base.wrapping_add(parsed.optional.address_of_entry_point),
size_of_image: parsed.optional.size_of_image,
sections: secs,
exports,
};
self.host
.modules
.insert(name.to_ascii_lowercase(), load_base);
let rsrc = parsed.optional.data_directories[2];
if rsrc.virtual_address != 0 && rsrc.size != 0 {
self.host
.module_resource_dirs
.insert(load_base, load_base.wrapping_add(rsrc.virtual_address));
}
Ok(image)
}
}
pub mod test_image;
#[cfg(test)]
mod tests {
use super::test_image::build_minimal_dll;
use super::*;
use crate::win32::HostState;
#[test]
fn load_minimal_synthesised_dll_succeeds() {
let bytes = build_minimal_dll();
let mut mmu = Mmu::new();
let mut registry = Registry::new();
registry.register_kernel32();
let mut host = HostState::new(0x6000_0000, 0x7000_0000);
let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
let img = loader.load("synth.dll", &bytes).unwrap();
assert_eq!(img.image_base, 0x1000_0000);
assert!(mmu.fetch_x8(img.entry_point).is_ok());
}
#[test]
fn rejects_non_mz() {
let bytes = vec![0u8; 1024];
let mut mmu = Mmu::new();
let mut registry = Registry::new();
let mut host = HostState::new(0, 0);
let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
match loader.load("bad.dll", &bytes) {
Err(PeError::NotMz) => (),
other => panic!("expected NotMz, got {other:?}"),
}
}
#[test]
fn rejects_pe32_plus() {
let mut bytes = build_minimal_dll();
let pe_off =
u32::from_le_bytes([bytes[0x3C], bytes[0x3D], bytes[0x3E], bytes[0x3F]]) as usize;
let opt_magic_off = pe_off + 4 + 20; bytes[opt_magic_off] = 0x0B;
bytes[opt_magic_off + 1] = 0x02;
let mut mmu = Mmu::new();
let mut registry = Registry::new();
let mut host = HostState::new(0, 0);
let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
match loader.load("bad.dll", &bytes) {
Err(PeError::Pe32PlusUnsupported) => (),
other => panic!("expected Pe32PlusUnsupported, got {other:?}"),
}
}
#[test]
fn rejects_managed_pe() {
let mut bytes = build_minimal_dll();
let pe_off =
u32::from_le_bytes([bytes[0x3C], bytes[0x3D], bytes[0x3E], bytes[0x3F]]) as usize;
let dirs_off = pe_off + 4 + 20 + 96;
let comheader_off = dirs_off + 14 * 8;
bytes[comheader_off..comheader_off + 4].copy_from_slice(&1u32.to_le_bytes());
bytes[comheader_off + 4..comheader_off + 8].copy_from_slice(&8u32.to_le_bytes());
let mut mmu = Mmu::new();
let mut registry = Registry::new();
let mut host = HostState::new(0, 0);
let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
match loader.load("bad.dll", &bytes) {
Err(PeError::ManagedPe) => (),
other => panic!("expected ManagedPe, got {other:?}"),
}
}
#[test]
fn export_by_name_resolves_to_va() {
let bytes = build_minimal_dll();
let mut mmu = Mmu::new();
let mut registry = Registry::new();
registry.register_kernel32();
let mut host = HostState::new(0x6000_0000, 0x7000_0000);
let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
let img = loader.load("synth.dll", &bytes).unwrap();
let p = img.export("DllMain").expect("DllMain export");
assert_eq!(p, img.entry_point);
}
#[test]
fn iat_is_populated_with_thunks() {
let bytes = build_minimal_dll();
let mut mmu = Mmu::new();
let mut registry = Registry::new();
registry.register_kernel32();
let mut host = HostState::new(0x6000_0000, 0x7000_0000);
let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
let img = loader.load("synth.dll", &bytes).unwrap();
let expected = registry.resolve("kernel32.dll", "GetProcessHeap").unwrap();
let iat = mmu
.load32(img.image_base + super::test_image::IAT_RVA)
.unwrap();
assert_eq!(iat, expected);
}
}