use crate::error::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterKind {
Delta { channels: u8 },
X86Call,
X86CallJmp,
Arm,
}
#[derive(Debug, Clone, Copy)]
pub struct Filter {
pub start: u64,
pub length: u32,
pub kind: FilterKind,
}
pub fn apply(filter: &Filter, buf: &mut [u8]) -> Result<(), Error> {
if (buf.len() as u64) < filter.length as u64 {
return Err(Error::Corrupt);
}
match filter.kind {
FilterKind::X86Call => apply_e8(filter.start, &mut buf[..filter.length as usize], false),
FilterKind::X86CallJmp => apply_e8(filter.start, &mut buf[..filter.length as usize], true),
FilterKind::Delta { .. } => Err(Error::Unsupported),
FilterKind::Arm => Err(Error::Unsupported),
}
}
fn apply_e8(start: u64, buf: &mut [u8], extended: bool) -> Result<(), Error> {
const FILE_SIZE: u32 = 0x0100_0000;
if buf.len() < 5 {
return Ok(());
}
let last = buf.len() - 4;
let mut i = 0;
while i < last {
let b = buf[i];
let matches = b == 0xE8 || (extended && b == 0xE9);
if !matches {
i += 1;
continue;
}
let rel = u32::from_le_bytes([buf[i + 1], buf[i + 2], buf[i + 3], buf[i + 4]]);
let off = ((start + i as u64 + 1) as u32) & (FILE_SIZE - 1);
let new = if (rel & 0x8000_0000) != 0 && (rel.wrapping_add(off) & 0x8000_0000) == 0 {
rel.wrapping_add(FILE_SIZE)
} else if (rel.wrapping_sub(FILE_SIZE) & 0x8000_0000) != 0 {
rel.wrapping_sub(off)
} else {
rel
};
let nb = new.to_le_bytes();
buf[i + 1] = nb[0];
buf[i + 2] = nb[1];
buf[i + 3] = nb[2];
buf[i + 4] = nb[3];
i += 5;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn e8_filter_rewrites_call_target() {
let start: u64 = 0;
let mut buf = alloc::vec![0x00, 0x00, 0xE8, 0x10, 0x00, 0x00, 0x00, 0x90, 0x90];
let f = Filter {
start,
length: buf.len() as u32,
kind: FilterKind::X86Call,
};
apply(&f, &mut buf).unwrap();
let expected = (0x10u32).wrapping_sub(3).to_le_bytes();
assert_eq!(&buf[3..7], &expected);
}
#[test]
fn e8_filter_ignores_non_opcode_bytes() {
let mut buf = alloc::vec![0xC3, 0x90, 0xCC, 0xFF, 0x90, 0x90, 0x90, 0x90, 0x90];
let original = buf.clone();
let f = Filter {
start: 0,
length: buf.len() as u32,
kind: FilterKind::X86Call,
};
apply(&f, &mut buf).unwrap();
assert_eq!(buf, original);
}
#[test]
fn delta_and_arm_return_unsupported() {
let mut buf = alloc::vec![0; 16];
let f = Filter {
start: 0,
length: buf.len() as u32,
kind: FilterKind::Delta { channels: 3 },
};
assert_eq!(apply(&f, &mut buf), Err(Error::Unsupported));
let f = Filter {
start: 0,
length: buf.len() as u32,
kind: FilterKind::Arm,
};
assert_eq!(apply(&f, &mut buf), Err(Error::Unsupported));
}
}