#[derive(Debug, Clone)]
pub struct Xref {
pub site_va: u64,
pub instr_len: usize,
pub kind: XrefKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XrefKind {
CallRel,
JmpRel,
CallMem,
JmpMem,
DataRef,
DataRefShort,
Other,
}
fn try_match(code: &[u8], off: usize, base_va: u64, target_va: u64) -> Option<Xref> {
const ENCODINGS: &[(&[u8], usize, XrefKind)] = &[
(&[0x48, 0x8b, 0x05], 7, XrefKind::DataRef), (&[0x48, 0x8d, 0x05], 7, XrefKind::DataRef), (&[0x48, 0x89, 0x05], 7, XrefKind::DataRef), (&[0x48, 0x3b, 0x05], 7, XrefKind::DataRef), (&[0x4c, 0x8b, 0x05], 7, XrefKind::DataRef),
(&[0x4c, 0x8d, 0x05], 7, XrefKind::DataRef),
(&[0xff, 0x15], 6, XrefKind::CallMem),
(&[0xff, 0x25], 6, XrefKind::JmpMem),
(&[0x8b, 0x05], 6, XrefKind::DataRefShort),
(&[0x8d, 0x05], 6, XrefKind::DataRefShort),
(&[0xe8], 5, XrefKind::CallRel),
(&[0xe9], 5, XrefKind::JmpRel),
];
for (prefix, total_len, kind) in ENCODINGS.iter() {
if off + *total_len > code.len() {
continue;
}
if &code[off..off + prefix.len()] != *prefix {
continue;
}
let d32_off = off + prefix.len();
if d32_off + 4 > code.len() {
continue;
}
let disp = i32::from_le_bytes([
code[d32_off],
code[d32_off + 1],
code[d32_off + 2],
code[d32_off + 3],
]);
let next_rip = base_va.wrapping_add((off + *total_len) as u64);
let effective = next_rip.wrapping_add(disp as i64 as u64);
if effective == target_va {
return Some(Xref {
site_va: base_va.wrapping_add(off as u64),
instr_len: *total_len,
kind: *kind,
});
}
}
None
}
pub fn scan(code: &[u8], base_va: u64, target_va: u64) -> Vec<Xref> {
let mut hits = Vec::new();
let mut off = 0;
while off < code.len() {
if let Some(xref) = try_match(code, off, base_va, target_va) {
let len = xref.instr_len;
hits.push(xref);
off += len.max(1);
continue;
}
off += 1;
}
hits
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_call_rel32() {
let mut code = vec![0xe8];
code.extend_from_slice(&0x1000_i32.to_le_bytes());
let hits = scan(&code, 0x1000, 0x2005);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].kind, XrefKind::CallRel);
}
#[test]
fn matches_lea_rip() {
let mut code = vec![0x48, 0x8d, 0x05];
code.extend_from_slice(&0x100_i32.to_le_bytes());
let hits = scan(&code, 0x1000, 0x1107);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].kind, XrefKind::DataRef);
assert_eq!(hits[0].instr_len, 7);
}
#[test]
fn matches_call_indirect() {
let mut code = vec![0xff, 0x15];
code.extend_from_slice(&0x10_i32.to_le_bytes());
let hits = scan(&code, 0x1000, 0x1016);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].kind, XrefKind::CallMem);
assert_eq!(hits[0].instr_len, 6);
}
#[test]
fn matches_short_data_ref() {
let mut code = vec![0x8b, 0x05];
code.extend_from_slice(&0x20_i32.to_le_bytes());
let hits = scan(&code, 0x1000, 0x1026);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].kind, XrefKind::DataRefShort);
}
#[test]
fn no_false_positive_on_random_bytes() {
let code = vec![0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90];
let hits = scan(&code, 0x1000, 0x2000);
assert!(hits.is_empty());
}
}