use crate::errors::FileParseError;
use crate::field::Field;
use crate::pe::header::PEType;
use crate::pe::import;
use crate::pe::section::PeSection;
use crate::utils::{extract_u16, extract_u32};
pub const IMAGE_REL_BASED_ABSOLUTE: u16 = 0;
pub const IMAGE_REL_BASED_HIGH: u16 = 1;
pub const IMAGE_REL_BASED_LOW: u16 = 2;
pub const IMAGE_REL_BASED_HIGHLOW: u16 = 3;
pub const IMAGE_REL_BASED_HIGHADJ: u16 = 4;
pub const IMAGE_REL_BASED_DIR64: u16 = 10;
#[derive(Debug, Clone)]
pub struct BaseRelocationEntry {
pub raw: Field<u16>,
}
impl BaseRelocationEntry {
pub fn relocation_type(&self) -> u16 {
self.raw.value >> 12
}
pub fn offset(&self) -> u16 {
self.raw.value & 0x0fff
}
pub fn rva(&self, page_rva: u32) -> u32 {
page_rva + self.offset() as u32
}
}
pub struct BaseRelocationBlock {
pub page_rva: Field<u32>,
pub block_size: Field<u32>,
pub entries: Vec<BaseRelocationEntry>,
}
impl BaseRelocationBlock {
pub fn parse(buffer: &[u8], offset: usize) -> Result<Self, FileParseError> {
let page_rva = extract_u32(buffer, offset)?;
let block_size = extract_u32(buffer, offset + 4)?;
if block_size < 8 || block_size % 2 != 0 {
return Err(FileParseError::InvalidFileFormat);
}
let end = offset
.checked_add(block_size as usize)
.ok_or(FileParseError::BufferOverflow)?;
if buffer.len() < end {
return Err(FileParseError::BufferOverflow);
}
let entry_count = (block_size as usize - 8) / 2;
let mut entries = Vec::with_capacity(entry_count);
let mut entry_offset = offset + 8;
for _ in 0..entry_count {
entries.push(BaseRelocationEntry {
raw: Field::new(extract_u16(buffer, entry_offset)?, entry_offset, 2),
});
entry_offset += 2;
}
Ok(BaseRelocationBlock {
page_rva: Field::new(page_rva, offset, 4),
block_size: Field::new(block_size, offset + 4, 4),
entries,
})
}
}
pub fn parse_base_relocations(
buffer: &[u8],
offset: usize,
size: usize,
) -> Result<Vec<BaseRelocationBlock>, FileParseError> {
let end = offset
.checked_add(size)
.ok_or(FileParseError::BufferOverflow)?;
if buffer.len() < end {
return Err(FileParseError::BufferOverflow);
}
let mut blocks = Vec::new();
let mut current = offset;
while current < end {
if end - current < 8 {
return Err(FileParseError::InvalidFileFormat);
}
let block = BaseRelocationBlock::parse(buffer, current)?;
let next = current
.checked_add(block.block_size.value as usize)
.ok_or(FileParseError::BufferOverflow)?;
if next > end {
return Err(FileParseError::BufferOverflow);
}
blocks.push(block);
current = next;
}
Ok(blocks)
}
pub fn apply_base_relocations(
buffer: &mut [u8],
blocks: &[BaseRelocationBlock],
sections: &[PeSection],
old_base: u64,
new_base: u64,
pe_type: PEType,
) -> Result<(), FileParseError> {
let delta = i64::try_from(new_base)
.and_then(|new| i64::try_from(old_base).map(|old| new - old))
.map_err(|_| FileParseError::ValueTooLarge)?;
if delta == 0 {
return Ok(());
}
for block in blocks {
for entry in &block.entries {
let reloc_type = entry.relocation_type();
if reloc_type == IMAGE_REL_BASED_ABSOLUTE {
continue;
}
let rva = entry.rva(block.page_rva.value);
let offset = import::rva_to_offset(buffer, sections, rva)?;
match (pe_type, reloc_type) {
(PEType::PE32, IMAGE_REL_BASED_HIGHLOW) => {
if buffer.len() < offset + 4 {
return Err(FileParseError::BufferOverflow);
}
let current = u32::from_le_bytes([
buffer[offset],
buffer[offset + 1],
buffer[offset + 2],
buffer[offset + 3],
]);
let patched = (current as i64 + delta) as u32;
buffer[offset..offset + 4].copy_from_slice(&patched.to_le_bytes());
}
(PEType::PE32Plus, IMAGE_REL_BASED_DIR64) => {
if buffer.len() < offset + 8 {
return Err(FileParseError::BufferOverflow);
}
let current = u64::from_le_bytes([
buffer[offset],
buffer[offset + 1],
buffer[offset + 2],
buffer[offset + 3],
buffer[offset + 4],
buffer[offset + 5],
buffer[offset + 6],
buffer[offset + 7],
]);
let patched = (current as i64 + delta) as u64;
buffer[offset..offset + 8].copy_from_slice(&patched.to_le_bytes());
}
_ => {}
}
}
}
Ok(())
}