pub mod analysis;
pub mod cfg;
pub mod antidebug_timing;
pub mod api_resolver;
#[cfg(feature = "experimental")]
pub mod cpp_class;
pub mod crypto_constants;
pub mod dominators;
pub mod dwarf;
pub mod eh_frame;
pub mod eqsat;
pub mod fold;
pub mod go_pclntab;
pub mod handler_summary;
pub mod imports;
pub mod ir;
pub mod jmp_rax_trampoline;
pub mod pdb_info;
pub mod peb_walk;
pub mod peb_walk_detect;
pub mod printer;
pub mod rip_xref;
pub mod scratch_leak;
#[cfg(feature = "experimental")]
pub mod seh_static;
pub mod sha256_func_detect;
pub mod signatures;
mod signatures_crypto;
mod signatures_cxxabi;
mod signatures_libc;
mod signatures_msvcrt;
mod signatures_python;
mod signatures_win32;
pub mod ssa;
pub mod structure;
pub mod syscall_table;
pub mod tag_dispatch;
pub mod vm_bytecode_disasm;
pub mod vm_dispatch_extract;
pub mod vm_fingerprint;
pub mod vm_handler_classify;
pub mod xor_vtable;
use pcode_ir::Instruction;
use rsleigh_api::Architecture;
use std::path::Path;
fn detect_cc(arch: Architecture, binary: Option<&[u8]>) -> fold::CallingConv {
if let Some(binary) = binary {
if arch == Architecture::X86_64 && is_go_binary(binary) {
return fold::CallingConv::GoAmd64;
}
if let Ok(goblin::Object::PE(pe)) = goblin::Object::parse(binary) {
return if pe.is_64 {
fold::CallingConv::Win64
} else {
fold::CallingConv::Cdecl32
};
}
}
match arch {
Architecture::X86_32 | Architecture::MIPS32 => fold::CallingConv::Cdecl32,
Architecture::ARM32 => fold::CallingConv::Arm32,
Architecture::AArch64 => fold::CallingConv::AArch64,
_ => fold::CallingConv::SysV,
}
}
fn is_go_binary(binary: &[u8]) -> bool {
let Ok(obj) = goblin::Object::parse(binary) else {
return false;
};
match &obj {
goblin::Object::Elf(elf) => elf
.section_headers
.iter()
.any(|sh| elf.shdr_strtab.get_at(sh.sh_name) == Some(".gopclntab")),
goblin::Object::PE(pe) => pe
.sections
.iter()
.any(|s| s.name().ok() == Some(".gopclntab")),
_ => false,
}
}
pub fn decompile(arch: Architecture, instructions: &[(u64, Instruction)]) -> String {
decompile_with_binary(arch, instructions, None, None)
}
pub fn decompile_with_binary(
arch: Architecture,
instructions: &[(u64, Instruction)],
binary: Option<&[u8]>,
binary_path: Option<&Path>,
) -> String {
if instructions.is_empty() {
return "// empty function\n".to_string();
}
let expanded: Vec<(u64, pcode_ir::Instruction)> = instructions.to_vec();
let cfg = cfg::build_cfg(&expanded);
if cfg.blocks.is_empty() {
return "// no blocks\n".to_string();
}
let import_map = binary
.map(|b| imports::resolve_imports(b))
.unwrap_or_default();
let cc = detect_cc(arch, binary);
let mut ssa = ssa::build_ssa_with_cc(&cfg, cc);
fold::fold_with_cc(&mut ssa, cc);
fold::apply_signature_names(&mut ssa, &import_map);
fold::propagate_signature_return_types(&mut ssa, &import_map);
fold::rewrite_conditional_phi_to_ternary(&mut ssa, &cfg);
let debug_info = if let Some(path) = binary_path {
let info = dwarf::parse_dwarf_from_path(path);
if !info.is_empty() {
Some(info)
} else {
None
}
} else if let Some(binary) = binary {
let info = dwarf::parse_dwarf(binary);
if !info.is_empty() {
Some(info)
} else {
None
}
} else {
None
};
let (pdb_debug_info, pdb_struct_fields) = if debug_info.is_none() {
if let Some(path) = binary_path {
pdb_info::parse_pdb_from_path(path)
} else {
(
std::collections::HashMap::new(),
std::collections::HashMap::new(),
)
}
} else {
(
std::collections::HashMap::new(),
std::collections::HashMap::new(),
)
};
let effective_debug_info = if debug_info.is_some() {
debug_info.clone()
} else if !pdb_debug_info.is_empty() {
Some(pdb_debug_info)
} else {
None
};
let mut local_var_names = std::collections::HashMap::new();
if let Some(ref debug_info) = effective_debug_info {
let func_addr = instructions[0].0;
if let Some(info) = debug_info.get(&func_addr) {
for v in &mut ssa.vars {
if let Some(ref param_name) = v.param_name {
if let Some(idx) = param_name
.strip_prefix("param_")
.and_then(|s| s.parse::<usize>().ok())
{
if let Some(dwarf_name) = info.param_names.get(idx) {
v.param_name = Some(dwarf_name.clone());
}
}
}
}
for (offset, name) in &info.local_names {
if *offset < 0 {
let positive = (-offset) as u64;
let var_name = format!("var_{:x}", positive);
local_var_names.insert(var_name, name.clone());
let adjusted = positive + 8;
let adj_name = format!("var_{:x}", adjusted);
local_var_names
.entry(adj_name)
.or_insert_with(|| name.clone());
} else if *offset > 0 {
let var_name = format!("var_{:x}", *offset as u64);
local_var_names.insert(var_name, name.clone());
}
}
}
}
let mut struct_fields = if let Some(path) = binary_path {
dwarf::parse_struct_fields_from_path(path)
} else if let Some(binary) = binary {
dwarf::parse_struct_fields(binary)
} else {
std::collections::HashMap::new()
};
for (offset, name) in pdb_struct_fields {
struct_fields.entry(offset).or_insert(name);
}
let func_addr = instructions[0].0;
let func_name = import_map
.get(&func_addr)
.cloned()
.or_else(|| {
debug_info
.as_ref()
.and_then(|di| {
di.get(&func_addr)
.and_then(|f| Some(f.param_names.first()?.clone()))
})
.and(None)
}) .unwrap_or_else(|| format!("func_{:x}", func_addr));
let try_regions_map = if let Some(bin) = binary {
eh_frame::parse_eh_frame(bin)
} else {
std::collections::HashMap::new()
};
let empty_regions: Vec<eh_frame::TryRegion> = Vec::new();
let try_regions = try_regions_map.get(&func_addr).unwrap_or(&empty_regions);
let structured = structure::recover_structure(&ssa, &cfg);
printer::print_c_with_try(
&structured,
&ssa,
arch,
binary,
&import_map,
&local_var_names,
&struct_fields,
&func_name,
try_regions,
)
}
#[derive(Debug, Clone)]
pub struct LearnedFuncType {
pub addr: u64,
pub param_types: Vec<Option<&'static str>>, pub return_type: Option<&'static str>, }
pub fn extract_learned_types(
arch: Architecture,
instructions: &[(u64, Instruction)],
binary: Option<&[u8]>,
) -> Option<LearnedFuncType> {
if instructions.is_empty() {
return None;
}
let mut expanded = Vec::new();
for (addr, inst) in instructions {
expanded.push((*addr, inst.clone()));
}
let cfg = cfg::build_cfg(&expanded);
if cfg.blocks.is_empty() {
return None;
}
let import_map = binary
.map(|b| imports::resolve_imports(b))
.unwrap_or_default();
let cc = detect_cc(arch, binary);
let mut ssa = ssa::build_ssa_with_cc(&cfg, cc);
fold::fold_with_cc(&mut ssa, cc);
fold::apply_signature_names(&mut ssa, &import_map);
fold::propagate_signature_return_types(&mut ssa, &import_map);
let func_addr = instructions[0].0;
let mut params: Vec<(u32, Option<&'static str>)> = Vec::new();
for v in &ssa.vars {
if let Some(ref name) = v.param_name {
if let Some(idx) = name
.strip_prefix("param_")
.and_then(|s| s.parse::<u32>().ok())
{
params.push((idx, v.display_type));
}
}
}
params.sort_by_key(|(idx, _)| *idx);
params.dedup_by_key(|(idx, _)| *idx);
let param_types: Vec<Option<&'static str>> = params.into_iter().map(|(_, dt)| dt).collect();
let mut return_type = None;
for block in &ssa.blocks {
if let ir::SsaTerminator::Return(Some(v)) = &block.terminator {
let vdef = ssa.var(*v);
if let Some(dt) = vdef.display_type {
return_type = Some(dt);
}
break;
}
}
let has_return_val = ssa
.blocks
.iter()
.any(|b| matches!(&b.terminator, ir::SsaTerminator::Return(Some(_))));
if has_return_val && return_type.is_none() {
return_type = Some("int");
}
if param_types.iter().any(|t| t.is_some()) || return_type.is_some() {
Some(LearnedFuncType {
addr: func_addr,
param_types,
return_type,
})
} else {
None
}
}
#[derive(Debug, Clone)]
pub struct LearnedStructParam {
pub func_addr: u64,
pub param_index: u32,
pub struct_name: String,
}
pub fn extract_learned_structs(func_addr: u64, output: &str) -> Vec<LearnedStructParam> {
let mut results = Vec::new();
for line in output.lines() {
let t = line.trim();
if let Some(rest) = t.strip_prefix("// param_") {
if let Some(is_pos) = rest.find(" is ") {
if let Ok(idx) = rest[..is_pos].parse::<u32>() {
let struct_part = &rest[is_pos + 4..];
let struct_name = struct_part.trim().trim_end_matches('*').trim();
if !struct_name.is_empty() {
results.push(LearnedStructParam {
func_addr,
param_index: idx,
struct_name: struct_name.to_string(),
});
}
}
}
}
}
results
}
pub fn infer_returns_from_callsites(
arch: Architecture,
instructions: &[(u64, Instruction)],
binary: Option<&[u8]>,
) -> Vec<(u64, &'static str)> {
if instructions.is_empty() {
return Vec::new();
}
let mut expanded = Vec::new();
for (addr, inst) in instructions {
expanded.push((*addr, inst.clone()));
}
let cfg_result = cfg::build_cfg(&expanded);
if cfg_result.blocks.is_empty() {
return Vec::new();
}
let import_map = binary
.map(|b| imports::resolve_imports(b))
.unwrap_or_default();
let cc = detect_cc(arch, binary);
let mut ssa = ssa::build_ssa_with_cc(&cfg_result, cc);
fold::fold_with_cc(&mut ssa, cc);
let mut results = Vec::new();
for bi in 0..ssa.blocks.len() {
let (target_addr, ft) = match &ssa.blocks[bi].terminator {
ir::SsaTerminator::Call {
target: ir::CallTarget::Direct(addr),
fallthrough,
..
} => (*addr, fallthrough.0),
_ => continue,
};
if import_map.contains_key(&target_addr) {
continue;
}
if ft < ssa.blocks.len() {
for stmt in &ssa.blocks[ft].stmts {
if let ir::Stmt::Assign(var_id) = stmt {
let vdef = &ssa.vars[var_id.0 as usize];
if vdef.call_return && vdef.use_count > 0 {
results.push((target_addr, "int"));
break;
}
}
}
}
}
for block in &ssa.blocks {
for stmt in &block.stmts {
if let ir::Stmt::Call {
target: ir::CallTarget::Direct(addr),
out: Some(out_var),
..
} = stmt
{
if import_map.contains_key(addr) {
continue;
}
let vdef = &ssa.vars[out_var.0 as usize];
if vdef.use_count > 0 {
results.push((*addr, "int"));
}
}
}
}
results.sort_by_key(|(addr, _)| *addr);
results.dedup_by_key(|(addr, _)| *addr);
results
}