use rsleigh_api::{Architecture, Decoder};
use rsleigh_decompile::cfg::build_cfg;
use rsleigh_decompile::fold::{fold_with_cc, CallingConv};
use rsleigh_decompile::ir::Stmt;
use rsleigh_decompile::ssa::build_ssa_with_cc;
fn decode_x64(bytes: &[u8], base: u64) -> Vec<(u64, pcode_ir::Instruction)> {
let mut dec = Decoder::new(Architecture::X86_64);
let mut insts = Vec::new();
let mut off = 0usize;
while off < bytes.len() {
let addr = base + off as u64;
match dec.decode(&bytes[off..], addr) {
Ok(inst) => {
let l = inst.len as usize;
insts.push((addr, inst));
off += l;
}
Err(_) => break,
}
}
insts
}
#[test]
fn mid_block_call_out_is_set() {
let handle = std::thread::Builder::new()
.stack_size(32 * 1024 * 1024)
.spawn(|| {
let bytes: [u8; 13] = [
0x48, 0x89, 0xF9, 0xE8, 0x10, 0x00, 0x00, 0x00, 0x48, 0x89, 0x45, 0xF8, 0xC3, ];
let insts = decode_x64(&bytes, 0x1000);
assert!(
insts.len() >= 3,
"expected >=3 instructions, got {}",
insts.len()
);
let cfg = build_cfg(&insts);
let mut ssa = build_ssa_with_cc(&cfg, CallingConv::Win64);
fold_with_cc(&mut ssa, CallingConv::Win64);
let call_stmts: Vec<_> = ssa
.blocks
.iter()
.flat_map(|b| &b.stmts)
.filter(|s| matches!(s, Stmt::Call { .. }))
.collect();
for stmt in &call_stmts {
if let Stmt::Call { out, .. } = stmt {
assert!(
out.is_some(),
"Stmt::Call.out must be Some(_) when return value is used; got None"
);
}
}
let has_any_call = !call_stmts.is_empty()
|| ssa.blocks.iter().any(|b| {
matches!(
&b.terminator,
rsleigh_decompile::ir::SsaTerminator::Call { .. }
)
});
assert!(
has_any_call,
"no Call found in SSA after decoding a CALL instruction"
);
})
.expect("thread spawn failed");
handle.join().expect("test thread panicked");
}
#[test]
fn strcspn_return_is_named() {
let path = "/Users/shane/Downloads/test_bin/cb_baristas_secret_x64.exe";
let data = match std::fs::read(path) {
Ok(d) => d,
Err(_) => {
eprintln!("skipping strcspn_return_is_named: fixture binary not found");
return;
}
};
let pe = match goblin::pe::PE::parse(&data) {
Ok(p) => p,
Err(e) => {
eprintln!("skipping strcspn_return_is_named: PE parse error: {}", e);
return;
}
};
let image_base = pe.image_base as u64;
let func_va: u64 = 0x140001e41;
let rva = func_va - image_base;
let mut file_off = None;
for s in &pe.sections {
let s_va = s.virtual_address as u64;
let s_sz = s.virtual_size as u64;
if rva >= s_va && rva < s_va + s_sz {
file_off = Some((s.pointer_to_raw_data as u64 + (rva - s_va)) as usize);
break;
}
}
let off = match file_off {
Some(o) => o,
None => {
eprintln!("skipping strcspn_return_is_named: func VA not in any section");
return;
}
};
let func_len = 0x300_usize.min(data.len() - off);
let bytes = data[off..off + func_len].to_vec();
let handle = std::thread::Builder::new()
.stack_size(32 * 1024 * 1024)
.spawn(move || {
use pcode_ir::PcodeOp;
let mut dec = Decoder::new(Architecture::X86_64);
let mut insts = Vec::new();
let mut io = 0usize;
while io < bytes.len() {
match dec.decode(&bytes[io..], func_va + io as u64) {
Ok(inst) => {
let is_ret = inst
.ops
.iter()
.any(|op| matches!(op, PcodeOp::Return { .. }));
let l = inst.len as usize;
insts.push((func_va + io as u64, inst));
io += l;
if is_ret {
break;
}
}
Err(_) => break,
}
}
let out = rsleigh_decompile::decompile_with_binary(
Architecture::X86_64,
&insts,
Some(&data),
Some(std::path::Path::new(
"/Users/shane/Downloads/test_bin/cb_baristas_secret_x64.exe",
)),
);
let has_named_binding = out
.lines()
.any(|line| line.contains("= strcspn(") || line.contains("=strcspn("));
assert!(
has_named_binding,
"strcspn return value not bound to a named variable; output:\n{}",
out
);
})
.expect("thread spawn failed");
handle.join().expect("test thread panicked");
}