use std::path::Path;
use std::process::Command;
fn build_pe_with_truncated_cert_table() -> Vec<u8> {
let mut buf = vec![0u8; 0x400];
buf[0..2].copy_from_slice(b"MZ");
let pe_off: u32 = 0x80;
buf[0x3C..0x40].copy_from_slice(&pe_off.to_le_bytes());
let pe_off = pe_off as usize;
buf[pe_off..pe_off + 4].copy_from_slice(b"PE\0\0");
let coff = pe_off + 4;
buf[coff..coff + 2].copy_from_slice(&0x014Cu16.to_le_bytes()); buf[coff + 2..coff + 4].copy_from_slice(&1u16.to_le_bytes()); let opt_size: u16 = 0xE0; buf[coff + 16..coff + 18].copy_from_slice(&opt_size.to_le_bytes());
buf[coff + 18..coff + 20].copy_from_slice(&0x0102u16.to_le_bytes());
let opt = coff + 20;
buf[opt..opt + 2].copy_from_slice(&0x010Bu16.to_le_bytes()); buf[opt + 2] = 14; buf[opt + 4..opt + 8].copy_from_slice(&0x200u32.to_le_bytes());
buf[opt + 16..opt + 20].copy_from_slice(&0x1000u32.to_le_bytes());
buf[opt + 20..opt + 24].copy_from_slice(&0x1000u32.to_le_bytes());
buf[opt + 24..opt + 28].copy_from_slice(&0x2000u32.to_le_bytes());
buf[opt + 28..opt + 32].copy_from_slice(&0x00400000u32.to_le_bytes());
buf[opt + 32..opt + 36].copy_from_slice(&0x1000u32.to_le_bytes());
buf[opt + 36..opt + 40].copy_from_slice(&0x200u32.to_le_bytes());
buf[opt + 40..opt + 42].copy_from_slice(&5u16.to_le_bytes());
buf[opt + 48..opt + 50].copy_from_slice(&5u16.to_le_bytes());
buf[opt + 56..opt + 60].copy_from_slice(&0x2000u32.to_le_bytes());
buf[opt + 60..opt + 64].copy_from_slice(&0x200u32.to_le_bytes());
buf[opt + 68..opt + 70].copy_from_slice(&3u16.to_le_bytes());
buf[opt + 72..opt + 76].copy_from_slice(&0x100000u32.to_le_bytes());
buf[opt + 76..opt + 80].copy_from_slice(&0x1000u32.to_le_bytes());
buf[opt + 80..opt + 84].copy_from_slice(&0x100000u32.to_le_bytes());
buf[opt + 84..opt + 88].copy_from_slice(&0x1000u32.to_le_bytes());
buf[opt + 92..opt + 96].copy_from_slice(&16u32.to_le_bytes());
let security_dir = opt + 96 + 4 * 8;
buf[security_dir..security_dir + 4].copy_from_slice(&0x0001_0000u32.to_le_bytes());
buf[security_dir + 4..security_dir + 8].copy_from_slice(&0x100u32.to_le_bytes());
let sh = opt + opt_size as usize;
buf[sh..sh + 8].copy_from_slice(b".text\0\0\0");
buf[sh + 8..sh + 12].copy_from_slice(&0x200u32.to_le_bytes()); buf[sh + 12..sh + 16].copy_from_slice(&0x1000u32.to_le_bytes()); buf[sh + 16..sh + 20].copy_from_slice(&0x200u32.to_le_bytes()); buf[sh + 20..sh + 24].copy_from_slice(&0x200u32.to_le_bytes()); buf[sh + 36..sh + 40].copy_from_slice(&0x6000_0020u32.to_le_bytes());
buf[0x200] = 0xC3;
buf
}
fn cli_binary() -> std::path::PathBuf {
let mut path = std::env::current_exe().unwrap();
path.pop(); if path.ends_with("deps") {
path.pop();
}
path.push("rsleigh");
path
}
#[test]
fn truncated_cert_table_does_not_block_discovery() {
let pe = build_pe_with_truncated_cert_table();
let strict = goblin::Object::parse(&pe);
assert!(
strict.is_err(),
"fixture no longer triggers the cert-table bug shape — \
strict parse succeeded, test invariant broken"
);
let tmp_dir = std::env::temp_dir().join("rsleigh_pe_cert_test");
std::fs::create_dir_all(&tmp_dir).unwrap();
let pe_path = tmp_dir.join("truncated_cert.exe");
std::fs::write(&pe_path, &pe).unwrap();
let bin = cli_binary();
assert!(
Path::new(&bin).exists(),
"rsleigh CLI binary not found at {:?} — run `cargo build -p rsleigh-cli` first",
bin
);
let out = Command::new(&bin)
.arg(&pe_path)
.arg("--summary")
.output()
.expect("failed to run rsleigh");
let stderr = String::from_utf8_lossy(&out.stderr);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
out.status.success(),
"rsleigh exited non-zero on carved-PE input.\nstderr: {}\nstdout: {}",
stderr,
stdout
);
let n_funcs_line = stderr
.lines()
.find(|l| l.contains("functions in"))
.unwrap_or("");
let n: usize = n_funcs_line
.split_whitespace()
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
assert!(
n >= 1,
"expected at least 1 discovered function, got {} (stderr: {})",
n,
stderr
);
}