const WINDOW_BYTES: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Counter {
Rdtsc,
Rdpmc,
Rdtscp,
}
#[derive(Debug, Clone)]
pub struct CounterRead {
pub va: u64,
pub kind: Counter,
}
#[derive(Debug, Clone)]
pub struct Probe {
pub first: CounterRead,
pub second: CounterRead,
pub distance: usize,
}
fn classify(code: &[u8], off: usize) -> Option<(Counter, usize)> {
if code.get(off) == Some(&0x0f) && code.get(off + 1) == Some(&0x31) {
return Some((Counter::Rdtsc, 2));
}
if code.get(off) == Some(&0x0f) && code.get(off + 1) == Some(&0x33) {
return Some((Counter::Rdpmc, 2));
}
if code.get(off) == Some(&0x0f)
&& code.get(off + 1) == Some(&0x01)
&& code.get(off + 2) == Some(&0xf9)
{
return Some((Counter::Rdtscp, 3));
}
None
}
pub fn scan_region(code: &[u8], base_va: u64) -> (Vec<CounterRead>, Vec<Probe>) {
let mut reads: Vec<CounterRead> = Vec::new();
let mut off = 0;
while off < code.len() {
if let Some((kind, len)) = classify(code, off) {
reads.push(CounterRead {
va: base_va + off as u64,
kind,
});
off += len;
} else {
off += 1;
}
}
let mut probes: Vec<Probe> = Vec::new();
for i in 0..reads.len() {
for j in (i + 1)..reads.len() {
let dist = (reads[j].va - reads[i].va) as usize;
if dist > WINDOW_BYTES {
break;
}
if dist < 4 {
continue;
}
probes.push(Probe {
first: reads[i].clone(),
second: reads[j].clone(),
distance: dist,
});
break; }
}
(reads, probes)
}
pub fn render_probe(p: &Probe) -> String {
let name = |c: Counter| match c {
Counter::Rdtsc => "RDTSC",
Counter::Rdpmc => "RDPMC",
Counter::Rdtscp => "RDTSCP",
};
format!(
"anti-debug timing probe: {}@{:#x} → {}@{:#x} ({} bytes apart)",
name(p.first.kind),
p.first.va,
name(p.second.kind),
p.second.va,
p.distance
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_rdtsc_pair() {
let mut code = vec![0x0f, 0x31];
code.extend(std::iter::repeat(0x90).take(16));
code.extend_from_slice(&[0x0f, 0x31]);
let (reads, probes) = scan_region(&code, 0x1000);
assert_eq!(reads.len(), 2);
assert_eq!(probes.len(), 1);
assert_eq!(probes[0].distance, 18);
assert_eq!(probes[0].first.kind, Counter::Rdtsc);
assert_eq!(probes[0].second.kind, Counter::Rdtsc);
}
#[test]
fn detects_rdpmc_pair() {
let mut code = vec![0x0f, 0x33];
code.extend(std::iter::repeat(0x90).take(20));
code.extend_from_slice(&[0x0f, 0x33]);
let (_, probes) = scan_region(&code, 0x2000);
assert_eq!(probes.len(), 1);
assert_eq!(probes[0].first.kind, Counter::Rdpmc);
}
#[test]
fn detects_rdtscp() {
let code = vec![0x0f, 0x01, 0xf9];
let (reads, _) = scan_region(&code, 0x3000);
assert_eq!(reads.len(), 1);
assert_eq!(reads[0].kind, Counter::Rdtscp);
}
#[test]
fn ignores_far_apart() {
let mut code = vec![0x0f, 0x31];
code.extend(std::iter::repeat(0x90).take(500));
code.extend_from_slice(&[0x0f, 0x31]);
let (reads, probes) = scan_region(&code, 0x4000);
assert_eq!(reads.len(), 2);
assert!(probes.is_empty());
}
#[test]
fn no_false_positive_on_quiet_code() {
let code = vec![0x90; 64];
let (reads, probes) = scan_region(&code, 0x5000);
assert!(reads.is_empty());
assert!(probes.is_empty());
}
}