use log::debug;
use std::collections::BTreeSet;
use anyhow::Result;
use crate::{analysis::dis, aspace::AddressSpace, loader::pe::PE, module::Permissions, util, VA};
pub fn find_pe_call_targets(pe: &PE) -> Result<BTreeSet<VA>> {
let mut ret = BTreeSet::default();
let decoder = dis::get_disassembler(&pe.module)?;
let mut call_count = 0usize;
for section in pe.executable_sections() {
let name = §ion.name;
let vstart: VA = section.virtual_range.start;
let vsize = (section.virtual_range.end - section.virtual_range.start) as usize;
let sec_buf = pe.module.address_space.read_bytes(vstart, vsize)?;
for (insn_offset, insn) in dis::linear_disassemble(&decoder, &sec_buf) {
if let Ok(Some(insn)) = insn {
if insn.meta.category != zydis::InstructionCategory::CALL {
continue;
}
let insn_va: VA = vstart + insn_offset as u64;
let op0 = &insn.operands()[0];
match &op0.kind {
dis::DecodedOperandKind::Ptr(ptr) => {
let target = ptr.offset as u64;
if pe.module.probe_va(target, Permissions::X) {
ret.insert(target);
}
}
dis::DecodedOperandKind::Imm(imm) => {
if imm.is_relative {
let imm = if imm.is_signed {
util::u64_i64(imm.value)
} else {
imm.value as i64
};
if imm == 0 {
debug!("call targets: {insn_va:#x}: call $+5 skipped");
continue;
}
let target = ((insn_va + insn.length as u64) as i64 + imm) as u64;
if pe.module.probe_va(target, Permissions::X) {
ret.insert(target);
}
} else {
debug!("CALL-IMM-ABS: {:#x}", insn_va);
panic!("call immediate absolute")
}
}
_ => continue,
}
call_count += 1;
}
}
let count = ret.len();
debug!("call targets: {name}, call count: {call_count}, targets: {count}");
}
Ok(ret)
}
#[cfg(test)]
mod tests {
use crate::rsrc::*;
use anyhow::Result;
#[test]
fn k32() -> Result<()> {
let buf = get_buf(Rsrc::K32);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::call_targets::find_pe_call_targets(&pe)?;
assert_eq!(891, fns.len());
Ok(())
}
#[test]
fn tiny() -> Result<()> {
let buf = get_buf(Rsrc::TINY);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::call_targets::find_pe_call_targets(&pe)?;
assert_eq!(0, fns.len());
Ok(())
}
#[test]
fn cpp1() -> Result<()> {
let buf = get_buf(Rsrc::CPP1);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::call_targets::find_pe_call_targets(&pe)?;
assert_eq!(144, fns.len());
Ok(())
}
#[test]
fn nop() -> Result<()> {
let buf = get_buf(Rsrc::NOP);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::call_targets::find_pe_call_targets(&pe)?;
assert_eq!(94, fns.len());
Ok(())
}
#[test]
fn mimi() -> Result<()> {
let buf = get_buf(Rsrc::MIMI);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::call_targets::find_pe_call_targets(&pe)?;
assert_eq!(1772, fns.len());
Ok(())
}
}