use super::allocator::MappedMemory;
use super::parser::ParsedPe;
use crate::error::{Result, WraithError};
use crate::structures::pe::{BaseRelocation, DataDirectoryType, RelocationEntry, RelocationType};
pub fn apply_relocations(pe: &ParsedPe, memory: &mut MappedMemory, delta: i64) -> Result<()> {
if delta == 0 {
return Ok(());
}
let reloc_dir = pe
.data_directory(DataDirectoryType::Basereloc)
.ok_or_else(|| WraithError::RelocationFailed {
rva: 0,
reason: "no relocation directory".into(),
})?;
if !reloc_dir.is_present() {
return Ok(());
}
let reloc_rva = reloc_dir.virtual_address as usize;
let reloc_size = reloc_dir.size as usize;
let mut offset = 0;
while offset < reloc_size {
let block: BaseRelocation = memory.read_at(reloc_rva + offset)?;
if block.size_of_block == 0 {
break;
}
if block.size_of_block < 8 {
return Err(WraithError::RelocationFailed {
rva: (reloc_rva + offset) as u32,
reason: "invalid relocation block size".into(),
});
}
let entry_count = block.entry_count();
let entries_offset = reloc_rva + offset + 8;
for i in 0..entry_count {
let entry_offset = entries_offset + i * 2;
let entry_raw: u16 = memory.read_at(entry_offset)?;
let entry = RelocationEntry(entry_raw);
apply_single_relocation(memory, &block, &entry, delta)?;
}
offset += block.size_of_block as usize;
}
Ok(())
}
fn apply_single_relocation(
memory: &mut MappedMemory,
block: &BaseRelocation,
entry: &RelocationEntry,
delta: i64,
) -> Result<()> {
let reloc_type = RelocationType::from(entry.reloc_type());
let target_rva = block.virtual_address as usize + entry.offset() as usize;
match reloc_type {
RelocationType::Absolute => {
}
RelocationType::HighLow => {
let value: u32 = memory.read_at(target_rva)?;
let new_value = (value as i64).wrapping_add(delta) as u32;
memory.write_value_at(target_rva, new_value)?;
}
RelocationType::Dir64 => {
let value: u64 = memory.read_at(target_rva)?;
let new_value = (value as i64).wrapping_add(delta) as u64;
memory.write_value_at(target_rva, new_value)?;
}
RelocationType::High => {
let value: u16 = memory.read_at(target_rva)?;
let full_value = (value as u32) << 16;
let new_full = (full_value as i64).wrapping_add(delta) as u32;
let new_value = (new_full >> 16) as u16;
memory.write_value_at(target_rva, new_value)?;
}
RelocationType::Low => {
let value: u16 = memory.read_at(target_rva)?;
let new_value = (value as i64).wrapping_add(delta) as u16;
memory.write_value_at(target_rva, new_value)?;
}
RelocationType::HighAdj => {
return Err(WraithError::RelocationFailed {
rva: target_rva as u32,
reason: "HIGHADJ relocations not supported".into(),
});
}
}
Ok(())
}
pub fn needs_relocations(pe: &ParsedPe, actual_base: usize) -> bool {
pe.preferred_base() != actual_base
}
pub fn calculate_delta(pe: &ParsedPe, actual_base: usize) -> i64 {
actual_base as i64 - pe.preferred_base() as i64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_delta() {
let exe_path = std::env::current_exe().unwrap();
let data = std::fs::read(&exe_path).unwrap();
let pe = ParsedPe::parse(&data).unwrap();
let preferred = pe.preferred_base();
let actual = preferred + 0x1000;
let delta = calculate_delta(&pe, actual);
assert_eq!(delta, 0x1000);
let actual = preferred - 0x1000;
let delta = calculate_delta(&pe, actual);
assert_eq!(delta, -0x1000);
}
#[test]
fn test_needs_relocations() {
let exe_path = std::env::current_exe().unwrap();
let data = std::fs::read(&exe_path).unwrap();
let pe = ParsedPe::parse(&data).unwrap();
let preferred = pe.preferred_base();
assert!(!needs_relocations(&pe, preferred));
assert!(needs_relocations(&pe, preferred + 0x1000));
}
}