use crate::{addressmap::AddressMap, error::Error};
const MIN_ENTRY_BYTES: usize = 5;
const PUSH_IMM32: u8 = 0x68;
const VB5_MAGIC: &[u8; 4] = b"VB5!";
pub fn extract_vb_header_va(map: &AddressMap<'_>, entry_point_rva: u32) -> Result<u32, Error> {
if let Ok(code) = map.slice_from_rva(entry_point_rva, MIN_ENTRY_BYTES)
&& let Some(&[PUSH_IMM32, b0, b1, b2, b3]) = code.first_chunk::<5>()
{
return Ok(u32::from_le_bytes([b0, b1, b2, b3]));
}
let byte = map
.slice_from_rva(entry_point_rva, 1)
.ok()
.and_then(|c| c.first().copied())
.unwrap_or(0);
Err(Error::EntryPointNotPush { byte })
}
pub fn extract_vb_header_va_from_exports(
map: &AddressMap<'_>,
exports: &[goblin::pe::export::Export<'_>],
) -> Option<u32> {
for export in exports {
let rva = u32::try_from(export.rva).ok()?;
let Ok(code) = map.slice_from_rva(rva, 6) else {
continue;
};
let Some(&[0x58, PUSH_IMM32, b0, b1, b2, b3]) = code.first_chunk::<6>() else {
continue;
};
let candidate = u32::from_le_bytes([b0, b1, b2, b3]);
if let Ok(magic) = map.slice_from_va(candidate, 4)
&& magic.starts_with(VB5_MAGIC)
{
return Some(candidate);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::addressmap::SectionEntry;
fn make_test_map(file: &[u8]) -> AddressMap<'_> {
AddressMap::from_parts(
file,
0x00400000,
vec![SectionEntry {
virtual_address: 0x1000,
virtual_size: 0x1000,
raw_data_offset: 0x200,
raw_data_size: 0x1000,
}],
)
}
#[test]
fn test_extract_vb_header_va_valid() {
let mut file = vec![0u8; 0x2000];
file[0x200] = PUSH_IMM32;
file[0x201] = 0x34;
file[0x202] = 0x12;
file[0x203] = 0x40;
file[0x204] = 0x00;
file[0x205] = 0xE8;
let map = make_test_map(&file);
let va = extract_vb_header_va(&map, 0x1000).unwrap();
assert_eq!(va, 0x00401234);
}
#[test]
fn test_extract_vb_header_va_not_push() {
let mut file = vec![0u8; 0x2000];
file[0x200] = 0xCC;
let map = make_test_map(&file);
assert_eq!(
extract_vb_header_va(&map, 0x1000),
Err(Error::EntryPointNotPush { byte: 0xCC })
);
}
#[test]
fn test_extract_vb_header_va_too_short() {
let file = vec![0u8; 0x203];
let map = make_test_map(&file);
assert!(extract_vb_header_va(&map, 0x1000).is_err());
}
#[test]
fn test_extract_vb_header_va_rva_not_mapped() {
let file = vec![0u8; 0x2000];
let map = make_test_map(&file);
assert!(extract_vb_header_va(&map, 0x5000).is_err());
}
}