pub const IMAGE_REL_BASED_ABSOLUTE: u16 = 0;
pub const IMAGE_REL_BASED_HIGHLOW: u16 = 3;
#[allow(dead_code)]
pub const IMAGE_REL_BASED_DIR64: u16 = 10;
#[derive(Debug, Clone)]
pub struct RelocationConfig {
pub is_dll: bool,
pub is_pe32_plus: bool,
pub is_il_only: bool,
pub entry_stub_rva: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct RelocationResult {
pub data: Vec<u8>,
pub strip_relocations: bool,
}
pub fn generate_relocations(
config: &RelocationConfig,
existing_reloc_data: Option<&[u8]>,
text_rva_range: (u32, u32),
) -> RelocationResult {
if config.is_dll {
return generate_dll_relocations(config, existing_reloc_data, text_rva_range);
}
if config.is_pe32_plus && config.is_il_only {
return RelocationResult {
data: Vec::new(),
strip_relocations: true,
};
}
if !config.is_pe32_plus && config.is_il_only {
if let Some(stub_rva) = config.entry_stub_rva {
let reloc_rva = stub_rva.saturating_add(2);
return RelocationResult {
data: generate_single_reloc_block(reloc_rva, IMAGE_REL_BASED_HIGHLOW),
strip_relocations: false,
};
}
}
if let Some(data) = existing_reloc_data {
let filtered = filter_relocation_blocks(data, text_rva_range);
if filtered.is_empty() {
return RelocationResult {
data: Vec::new(),
strip_relocations: true,
};
}
return RelocationResult {
data: filtered,
strip_relocations: false,
};
}
RelocationResult {
data: Vec::new(),
strip_relocations: true,
}
}
fn generate_dll_relocations(
config: &RelocationConfig,
existing_reloc_data: Option<&[u8]>,
text_rva_range: (u32, u32),
) -> RelocationResult {
if let Some(data) = existing_reloc_data {
let filtered = filter_relocation_blocks(data, text_rva_range);
if !filtered.is_empty() {
return RelocationResult {
data: filtered,
strip_relocations: false,
};
}
}
if !config.is_pe32_plus {
if let Some(stub_rva) = config.entry_stub_rva {
let reloc_rva = stub_rva.saturating_add(2);
return RelocationResult {
data: generate_single_reloc_block(reloc_rva, IMAGE_REL_BASED_HIGHLOW),
strip_relocations: false,
};
}
}
RelocationResult {
data: generate_minimal_reloc_block(),
strip_relocations: false,
}
}
fn generate_minimal_reloc_block() -> Vec<u8> {
let mut data = Vec::with_capacity(12);
data.extend_from_slice(&0x1000u32.to_le_bytes());
data.extend_from_slice(&12u32.to_le_bytes());
data.extend_from_slice(&0u16.to_le_bytes());
data.extend_from_slice(&0u16.to_le_bytes());
data
}
fn generate_single_reloc_block(rva: u32, reloc_type: u16) -> Vec<u8> {
let mut data = Vec::with_capacity(12);
let page_rva = rva & !0xFFF; let offset_in_page = (rva & 0xFFF) as u16;
data.extend_from_slice(&page_rva.to_le_bytes());
data.extend_from_slice(&12u32.to_le_bytes());
let entry = (reloc_type << 12) | offset_in_page;
data.extend_from_slice(&entry.to_le_bytes());
data.extend_from_slice(&0u16.to_le_bytes());
data
}
fn filter_relocation_blocks(reloc_data: &[u8], text_rva_range: (u32, u32)) -> Vec<u8> {
let mut result = Vec::new();
let mut offset = 0;
let (text_start, text_end) = text_rva_range;
while offset + 8 <= reloc_data.len() {
let block_va = u32::from_le_bytes([
reloc_data[offset],
reloc_data[offset + 1],
reloc_data[offset + 2],
reloc_data[offset + 3],
]);
let block_size = u32::from_le_bytes([
reloc_data[offset + 4],
reloc_data[offset + 5],
reloc_data[offset + 6],
reloc_data[offset + 7],
]) as usize;
if block_size < 8 || offset + block_size > reloc_data.len() {
break;
}
let points_to_text = block_va >= text_start && block_va < text_end;
if !points_to_text {
result.extend_from_slice(&reloc_data[offset..offset + block_size]);
}
offset += block_size;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_x64_il_only_exe_strips_relocations() {
let config = RelocationConfig {
is_dll: false,
is_pe32_plus: true,
is_il_only: true,
entry_stub_rva: Some(0x2050),
};
let result = generate_relocations(&config, None, (0x2000, 0x4000));
assert!(result.strip_relocations);
assert!(result.data.is_empty());
}
#[test]
fn test_x86_il_only_exe_has_entry_reloc() {
let config = RelocationConfig {
is_dll: false,
is_pe32_plus: false,
is_il_only: true,
entry_stub_rva: Some(0x2050),
};
let result = generate_relocations(&config, None, (0x2000, 0x4000));
assert!(!result.strip_relocations);
assert_eq!(result.data.len(), 12);
let page_rva = u32::from_le_bytes([
result.data[0],
result.data[1],
result.data[2],
result.data[3],
]);
let block_size = u32::from_le_bytes([
result.data[4],
result.data[5],
result.data[6],
result.data[7],
]);
let entry = u16::from_le_bytes([result.data[8], result.data[9]]);
assert_eq!(page_rva, 0x2000);
assert_eq!(block_size, 12);
assert_eq!(entry, 0x3052);
}
#[test]
fn test_x64_dll_always_has_relocations() {
let config = RelocationConfig {
is_dll: true,
is_pe32_plus: true,
is_il_only: true,
entry_stub_rva: Some(0x2050),
};
let result = generate_relocations(&config, None, (0x2000, 0x4000));
assert!(!result.strip_relocations);
assert!(!result.data.is_empty());
assert_eq!(result.data.len(), 12); }
#[test]
fn test_x86_dll_has_entry_reloc() {
let config = RelocationConfig {
is_dll: true,
is_pe32_plus: false,
is_il_only: true,
entry_stub_rva: Some(0x2050),
};
let result = generate_relocations(&config, None, (0x2000, 0x4000));
assert!(!result.strip_relocations);
assert_eq!(result.data.len(), 12);
let entry = u16::from_le_bytes([result.data[8], result.data[9]]);
assert_eq!(entry, 0x3052); }
#[test]
fn test_minimal_reloc_block_structure() {
let block = generate_minimal_reloc_block();
assert_eq!(block.len(), 12);
let page_rva = u32::from_le_bytes([block[0], block[1], block[2], block[3]]);
let block_size = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
let entry1 = u16::from_le_bytes([block[8], block[9]]);
let entry2 = u16::from_le_bytes([block[10], block[11]]);
assert_eq!(page_rva, 0x1000);
assert_eq!(block_size, 12);
assert_eq!(entry1, 0); assert_eq!(entry2, 0); }
#[test]
fn test_filter_removes_text_blocks() {
let mut reloc_data = Vec::new();
reloc_data.extend_from_slice(&0x2000u32.to_le_bytes()); reloc_data.extend_from_slice(&12u32.to_le_bytes()); reloc_data.extend_from_slice(&0x3050u16.to_le_bytes()); reloc_data.extend_from_slice(&0u16.to_le_bytes());
reloc_data.extend_from_slice(&0x6000u32.to_le_bytes()); reloc_data.extend_from_slice(&12u32.to_le_bytes()); reloc_data.extend_from_slice(&0x3100u16.to_le_bytes()); reloc_data.extend_from_slice(&0u16.to_le_bytes());
let filtered = filter_relocation_blocks(&reloc_data, (0x2000, 0x4000));
assert_eq!(filtered.len(), 12);
let page_rva = u32::from_le_bytes([filtered[0], filtered[1], filtered[2], filtered[3]]);
assert_eq!(page_rva, 0x6000);
}
#[test]
fn test_mixed_mode_preserves_non_text_relocs() {
let mut reloc_data = Vec::new();
reloc_data.extend_from_slice(&0x8000u32.to_le_bytes()); reloc_data.extend_from_slice(&12u32.to_le_bytes()); reloc_data.extend_from_slice(&0x3050u16.to_le_bytes()); reloc_data.extend_from_slice(&0u16.to_le_bytes());
let config = RelocationConfig {
is_dll: false,
is_pe32_plus: false,
is_il_only: false, entry_stub_rva: Some(0x2050),
};
let result = generate_relocations(&config, Some(&reloc_data), (0x2000, 0x4000));
assert!(!result.strip_relocations);
assert_eq!(result.data.len(), 12);
}
}