use std::collections::BTreeSet;
use rayon::prelude::*;
use crate::{
analysis::{
ConstValue, PhiTaintMode, SsaBlock, SsaEvaluator, SsaFunction, SsaOp, SsaVarId,
TaintAnalysis, TaintConfig,
},
deobfuscation::passes::unflattening::tracer::{
context::TreeTraceContext,
engine::trace_from_block,
types::{HandlerTrace, TraceNode, TraceStats, TraceTerminator},
},
metadata::{token::Token, typesystem::PointerSize},
utils::BitSet,
CilObject,
};
pub fn trace_exception_handlers(ctx: &mut TreeTraceContext<'_>) -> Vec<HandlerTrace> {
let handler_blocks = ctx.unvisited_handler_blocks();
if handler_blocks.is_empty() {
return Vec::new();
}
let id_base = ctx.next_id();
let id_stride = ctx.max_block_visits();
let handler_traces: Vec<HandlerTrace> = handler_blocks
.par_iter()
.enumerate()
.filter_map(|(i, &handler_start)| {
let mut handler_ctx = ctx.fork_for_handler(id_base + i * id_stride);
let root = trace_from_block(&mut handler_ctx, handler_start, 0);
Some(HandlerTrace {
handler_start_block: handler_start,
root,
})
})
.collect();
ctx.advance_node_id(id_base + handler_blocks.len() * id_stride);
handler_traces
}
pub fn propagate_taint_forward(ssa: &SsaFunction, tainted: &mut BitSet) {
let config = TaintConfig {
forward: true,
backward: false,
phi_mode: PhiTaintMode::NoPropagation,
max_iterations: 100,
};
let mut taint = TaintAnalysis::new(config);
taint.add_tainted_vars(tainted.iter().map(SsaVarId::from_index));
taint.propagate(ssa);
tainted.clear();
for var in taint.tainted_variables() {
tainted.insert(var.index());
}
let mut changed = true;
while changed {
changed = false;
for block in ssa.blocks() {
for phi in block.phi_nodes() {
if !tainted.contains(phi.result().index())
&& !phi.operands().is_empty()
&& phi
.operands()
.iter()
.all(|op| tainted.contains(op.value().index()))
{
tainted.insert(phi.result().index());
changed = true;
}
}
}
if changed {
let config = TaintConfig {
forward: true,
backward: false,
phi_mode: PhiTaintMode::NoPropagation,
max_iterations: 100,
};
let mut taint = TaintAnalysis::new(config);
taint.add_tainted_vars(tainted.iter().map(SsaVarId::from_index));
taint.propagate(ssa);
tainted.clear();
for var in taint.tainted_variables() {
tainted.insert(var.index());
}
}
}
}
pub fn resolve_call_result(
assembly: &CilObject,
method_token: Token,
concrete_args: &[ConstValue],
pointer_size: PointerSize,
) -> Option<ConstValue> {
let method = assembly.method(&method_token)?;
let callee_ssa = method.ssa(assembly).ok()?;
let mut eval = SsaEvaluator::new(&callee_ssa, pointer_size);
for (var, value) in callee_ssa.argument_variables().zip(concrete_args) {
eval.set_concrete(var.id(), value.clone());
}
let trace = eval.execute(0, None, 50);
if !trace.is_complete() {
return None;
}
let last_block_idx = trace.last_block()?;
let last_block = callee_ssa.block(last_block_idx)?;
for instr in last_block.instructions() {
if let SsaOp::Return {
value: Some(ret_var),
} = instr.op()
{
return eval.get_concrete(*ret_var).cloned();
}
}
None
}
pub fn detect_expression_switch(
ssa: &SsaFunction,
true_target: usize,
false_target: usize,
tainted: &BitSet,
) -> Option<usize> {
let (Some(tb), Some(fb)) = (ssa.block(true_target), ssa.block(false_target)) else {
return None;
};
let true_merge = const_producer_target(tb)?;
let false_merge = const_producer_target(fb)?;
if true_merge != false_merge {
return None;
}
let merge = ssa.block(true_merge)?;
if merge.phi_nodes().is_empty() {
return None;
}
let phi_results: BTreeSet<SsaVarId> =
merge.phi_nodes().iter().map(|phi| phi.result()).collect();
let feeds_tainted = merge.instructions().iter().any(|instr| match instr.op() {
SsaOp::Xor { left, right, .. }
| SsaOp::Add { left, right, .. }
| SsaOp::Sub { left, right, .. }
| SsaOp::Mul { left, right, .. } => {
let one_is_phi = phi_results.contains(left) || phi_results.contains(right);
let one_is_tainted = tainted.contains(left.index()) || tainted.contains(right.index());
one_is_phi && one_is_tainted
}
_ => false,
});
feeds_tainted.then_some(true_merge)
}
pub fn const_producer_target(block: &SsaBlock) -> Option<usize> {
let instrs = block.instructions();
if instrs.is_empty() {
return None;
}
let target = match instrs.last()?.op() {
SsaOp::Jump { target } => *target,
_ => return None,
};
let non_term: Vec<_> = instrs.iter().filter(|i| !i.is_terminator()).collect();
if non_term.len() > 2 {
return None;
}
if !non_term.iter().all(|i| {
matches!(
i.op(),
SsaOp::Const { .. } | SsaOp::Copy { .. } | SsaOp::Conv { .. }
)
}) {
return None;
}
Some(target)
}
pub fn compute_tree_stats(node: &TraceNode, stats: &mut TraceStats, depth: usize) {
stats.node_count += 1;
stats.max_depth = stats.max_depth.max(depth);
match &node.terminator {
TraceTerminator::Exit { .. } => {
stats.exit_count += 1;
}
TraceTerminator::StateTransition { continues, .. } => {
stats.state_transition_count += 1;
compute_tree_stats(continues, stats, depth + 1);
}
TraceTerminator::UserBranch {
true_branch,
false_branch,
..
} => {
stats.user_branch_count += 1;
compute_tree_stats(true_branch, stats, depth + 1);
compute_tree_stats(false_branch, stats, depth + 1);
}
TraceTerminator::UserSwitch { cases, default, .. } => {
stats.user_branch_count += 1;
for (_, case_node) in cases {
compute_tree_stats(case_node, stats, depth + 1);
}
compute_tree_stats(default, stats, depth + 1);
}
TraceTerminator::Stopped { .. } | TraceTerminator::LoopBack { .. } => {}
TraceTerminator::PendingStateTransition { .. } => {
}
}
}