#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{format, string::String};
#[cfg(feature = "std")]
use std::{format, string::String};
mod allocator;
mod entry;
mod mapper;
mod parser;
mod relocator;
mod resolver;
mod tls;
pub use allocator::MappedMemory;
pub use entry::reason;
pub use parser::ParsedPe;
use crate::error::{Result, WraithError};
use core::marker::PhantomData;
pub mod state {
pub struct Parsed;
pub struct Allocated;
pub struct SectionsMapped;
pub struct Relocated;
pub struct ImportsResolved;
pub struct TlsProcessed;
pub struct Ready;
}
pub struct ManualMapper<S> {
pe: ParsedPe,
memory: Option<MappedMemory>,
_state: PhantomData<S>,
}
impl ManualMapper<state::Parsed> {
pub fn parse(data: &[u8]) -> Result<Self> {
let pe = ParsedPe::parse(data)?;
Ok(Self {
pe,
memory: None,
_state: PhantomData,
})
}
#[cfg(feature = "std")]
pub fn from_file(path: &str) -> Result<Self> {
let data = std::fs::read(path).map_err(|e| WraithError::InvalidPeFormat {
reason: format!("failed to read file: {e}"),
})?;
Self::parse(&data)
}
#[cfg(not(feature = "std"))]
pub fn from_file(_path: &str) -> Result<Self> {
Err(WraithError::InvalidPeFormat {
reason: "file operations not available in no_std".into(),
})
}
pub fn pe(&self) -> &ParsedPe {
&self.pe
}
pub fn allocate(self) -> Result<ManualMapper<state::Allocated>> {
let size = self.pe.size_of_image();
let preferred_base = self.pe.preferred_base();
let memory = allocator::allocate_image(size, preferred_base)?;
Ok(ManualMapper {
pe: self.pe,
memory: Some(memory),
_state: PhantomData,
})
}
pub fn allocate_at(self, base: usize) -> Result<ManualMapper<state::Allocated>> {
let size = self.pe.size_of_image();
let memory = allocator::allocate_at(base, size)?;
Ok(ManualMapper {
pe: self.pe,
memory: Some(memory),
_state: PhantomData,
})
}
pub fn allocate_anywhere(self) -> Result<ManualMapper<state::Allocated>> {
let size = self.pe.size_of_image();
let memory = allocator::allocate_anywhere(size)?;
Ok(ManualMapper {
pe: self.pe,
memory: Some(memory),
_state: PhantomData,
})
}
}
impl ManualMapper<state::Allocated> {
pub fn base(&self) -> usize {
self.memory.as_ref().unwrap().base()
}
pub fn pe(&self) -> &ParsedPe {
&self.pe
}
pub fn map_sections(mut self) -> Result<ManualMapper<state::SectionsMapped>> {
let memory = self.memory.as_mut().unwrap();
mapper::map_sections(&self.pe, memory)?;
Ok(ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
})
}
}
impl ManualMapper<state::SectionsMapped> {
pub fn base(&self) -> usize {
self.memory.as_ref().unwrap().base()
}
pub fn pe(&self) -> &ParsedPe {
&self.pe
}
pub fn relocate(mut self) -> Result<ManualMapper<state::Relocated>> {
let memory = self.memory.as_mut().unwrap();
let delta = memory.base() as i64 - self.pe.preferred_base() as i64;
if delta != 0 {
relocator::apply_relocations(&self.pe, memory, delta)?;
}
Ok(ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
})
}
pub fn skip_relocations(self) -> ManualMapper<state::Relocated> {
ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
}
}
}
impl ManualMapper<state::Relocated> {
pub fn base(&self) -> usize {
self.memory.as_ref().unwrap().base()
}
pub fn pe(&self) -> &ParsedPe {
&self.pe
}
pub fn resolve_imports(mut self) -> Result<ManualMapper<state::ImportsResolved>> {
let memory = self.memory.as_mut().unwrap();
resolver::resolve_imports(&self.pe, memory)?;
Ok(ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
})
}
pub fn resolve_imports_with<F>(
mut self,
resolver_fn: F,
) -> Result<ManualMapper<state::ImportsResolved>>
where
F: Fn(&str, &str) -> Option<usize>,
{
let memory = self.memory.as_mut().unwrap();
resolver::resolve_imports_custom(&self.pe, memory, resolver_fn)?;
Ok(ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
})
}
pub fn skip_imports(self) -> ManualMapper<state::ImportsResolved> {
ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
}
}
}
impl ManualMapper<state::ImportsResolved> {
pub fn base(&self) -> usize {
self.memory.as_ref().unwrap().base()
}
pub fn pe(&self) -> &ParsedPe {
&self.pe
}
pub fn process_tls(mut self) -> Result<ManualMapper<state::TlsProcessed>> {
let memory = self.memory.as_mut().unwrap();
tls::process_tls(&self.pe, memory)?;
Ok(ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
})
}
pub fn skip_tls(self) -> ManualMapper<state::TlsProcessed> {
ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
}
}
}
impl ManualMapper<state::TlsProcessed> {
pub fn base(&self) -> usize {
self.memory.as_ref().unwrap().base()
}
pub fn pe(&self) -> &ParsedPe {
&self.pe
}
pub fn finalize(mut self) -> Result<ManualMapper<state::Ready>> {
let memory = self.memory.as_mut().unwrap();
mapper::set_section_protections(&self.pe, memory)?;
Ok(ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
})
}
pub fn finalize_without_protections(self) -> ManualMapper<state::Ready> {
ManualMapper {
pe: self.pe,
memory: self.memory,
_state: PhantomData,
}
}
}
impl ManualMapper<state::Ready> {
pub fn call_entry_point(&self) -> Result<bool> {
let memory = self.memory.as_ref().unwrap();
entry::call_dll_attach(&self.pe, memory)
}
pub fn call_entry_point_with_reason(&self, call_reason: u32) -> Result<bool> {
let memory = self.memory.as_ref().unwrap();
entry::call_entry_point(&self.pe, memory, call_reason)
}
pub fn get_export(&self, name: &str) -> Result<usize> {
let memory = self.memory.as_ref().unwrap();
resolver::get_mapped_export(&self.pe, memory, name)
}
pub fn get_export_by_ordinal(&self, ordinal: u16) -> Result<usize> {
let memory = self.memory.as_ref().unwrap();
resolver::get_mapped_export_by_ordinal(&self.pe, memory, ordinal)
}
pub fn base(&self) -> usize {
self.memory.as_ref().unwrap().base()
}
pub fn size(&self) -> usize {
self.memory.as_ref().unwrap().size()
}
pub fn pe(&self) -> &ParsedPe {
&self.pe
}
pub fn into_memory(mut self) -> MappedMemory {
self.memory.take().unwrap()
}
pub fn ptr_at(&self, offset: usize) -> *mut u8 {
self.memory.as_ref().unwrap().ptr_at(offset)
}
pub fn unmap(mut self) -> Result<()> {
if let Some(memory) = self.memory.take() {
let _ = entry::call_dll_detach(&self.pe, &memory);
memory.free()?;
}
Ok(())
}
}
pub fn map_pe(data: &[u8]) -> Result<ManualMapper<state::Ready>> {
ManualMapper::parse(data)?
.allocate()?
.map_sections()?
.relocate()?
.resolve_imports()?
.process_tls()?
.finalize()
}
pub fn map_file(path: &str) -> Result<ManualMapper<state::Ready>> {
ManualMapper::from_file(path)?
.allocate()?
.map_sections()?
.relocate()?
.resolve_imports()?
.process_tls()?
.finalize()
}
pub fn map_and_call(data: &[u8]) -> Result<ManualMapper<state::Ready>> {
let mapper = map_pe(data)?;
mapper.call_entry_point()?;
Ok(mapper)
}
pub fn map_file_and_call(path: &str) -> Result<ManualMapper<state::Ready>> {
let mapper = map_file(path)?;
mapper.call_entry_point()?;
Ok(mapper)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_and_allocate() {
let exe_path = std::env::current_exe().unwrap();
let data = std::fs::read(&exe_path).unwrap();
let mapper = ManualMapper::parse(&data).unwrap();
assert!(mapper.pe().size_of_image() > 0);
let mapper = mapper.allocate().unwrap();
assert!(mapper.base() != 0);
}
#[test]
fn test_map_sections() {
let exe_path = std::env::current_exe().unwrap();
let data = std::fs::read(&exe_path).unwrap();
let mapper = ManualMapper::parse(&data)
.unwrap()
.allocate()
.unwrap()
.map_sections()
.unwrap();
assert!(mapper.base() != 0);
}
}