use crate::{
analysis::{
ConstValue, FieldRef, MethodPurity, MethodRef, ReturnInfo, SsaBlock, SsaFunction,
SsaInstruction, SsaOp, SsaType, SsaVarId,
},
compiler::EventLog,
deobfuscation::{
config::{EngineConfig, IterationConfig, PassConfig},
engine::DeobfuscationEngine,
result::DeobfuscationResult,
},
metadata::token::Token,
};
#[test]
fn test_engine_default() {
let engine = DeobfuscationEngine::default();
assert!(engine.config.passes.constant_propagation);
assert!(engine.config.passes.dead_code_elimination);
}
#[test]
fn test_engine_config() {
let config = EngineConfig {
iterations: IterationConfig {
max_ssa_iterations: 10,
..Default::default()
},
passes: PassConfig {
inline_threshold: 30,
..Default::default()
},
..Default::default()
};
let engine = DeobfuscationEngine::new(config);
assert_eq!(engine.config.iterations.max_ssa_iterations, 10);
assert_eq!(engine.config.passes.inline_threshold, 30);
}
#[test]
fn test_pipeline_passes_default() {
let engine = DeobfuscationEngine::default();
let scheduler = engine.create_scheduler();
assert!(scheduler.pass_count() > 0); assert!(scheduler.normalize_count() > 0); }
#[test]
fn test_pipeline_passes_selective() {
let config = EngineConfig {
passes: PassConfig {
constant_propagation: true,
copy_propagation: false,
opaque_predicate_removal: false,
control_flow_simplification: false,
dead_code_elimination: false,
string_decryption: false,
strength_reduction: false,
..Default::default()
},
..Default::default()
};
let engine = DeobfuscationEngine::new(config);
let scheduler = engine.create_scheduler();
assert_eq!(scheduler.normalize_count(), 4); assert_eq!(scheduler.pass_count(), 0);
}
#[test]
fn test_analyze_return_void() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block);
let result = ssa.return_info();
assert!(matches!(result, ReturnInfo::Void));
}
#[test]
fn test_analyze_return_constant() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let var = SsaVarId::from_index(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: var,
value: ConstValue::I32(42),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return {
value: Some(var),
}));
ssa.add_block(block);
let result = ssa.return_info();
assert!(matches!(result, ReturnInfo::Constant(ConstValue::I32(42))));
}
#[test]
fn test_analyze_return_no_returns_is_void() {
let mut ssa = SsaFunction::new(0, 0);
let block = SsaBlock::new(0);
ssa.add_block(block);
let result = ssa.return_info();
assert!(matches!(result, ReturnInfo::Void));
}
#[test]
fn test_analyze_purity_pure() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let dest = SsaVarId::from_index(0);
let src1 = SsaVarId::from_index(1);
let src2 = SsaVarId::from_index(2);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Add {
dest,
left: src1,
right: src2,
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return {
value: Some(dest),
}));
ssa.add_block(block);
let result = ssa.purity();
assert!(matches!(result, MethodPurity::Pure));
}
#[test]
fn test_analyze_purity_impure_store_field() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let obj = SsaVarId::from_index(0);
let val = SsaVarId::from_index(1);
block.add_instruction(SsaInstruction::synthetic(SsaOp::StoreField {
object: obj,
field: FieldRef::new(Token::new(0x04000001)),
value: val,
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block);
let result = ssa.purity();
assert!(matches!(result, MethodPurity::Impure));
}
#[test]
fn test_analyze_purity_impure_throw() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let exc = SsaVarId::from_index(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Throw { exception: exc }));
ssa.add_block(block);
let result = ssa.purity();
assert!(matches!(result, MethodPurity::Impure));
}
#[test]
fn test_analyze_purity_readonly() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let dest = SsaVarId::from_index(0);
let obj = SsaVarId::from_index(1);
block.add_instruction(
SsaInstruction::synthetic(SsaOp::LoadField {
dest,
object: obj,
field: FieldRef::new(Token::new(0x04000001)),
})
.with_result_type(SsaType::I32),
);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return {
value: Some(dest),
}));
ssa.add_block(block);
let result = ssa.purity();
assert!(matches!(result, MethodPurity::ReadOnly));
}
#[test]
fn test_analyze_purity_unknown_calls() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let dest = SsaVarId::from_index(0);
block.add_instruction(
SsaInstruction::synthetic(SsaOp::Call {
dest: Some(dest),
method: MethodRef::new(Token::new(0x06000001)),
args: vec![],
})
.with_result_type(SsaType::I32),
);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return {
value: Some(dest),
}));
ssa.add_block(block);
let result = ssa.purity();
assert!(matches!(result, MethodPurity::Unknown));
}
#[test]
fn test_detect_string_decryptor_xor() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let dest = SsaVarId::from_index(0);
let left = SsaVarId::from_index(1);
let right = SsaVarId::from_index(2);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Xor { dest, left, right }));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return {
value: Some(dest),
}));
ssa.add_block(block);
let result = DeobfuscationEngine::detect_string_decryptor_pattern(&ssa);
assert!(result);
}
#[test]
fn test_detect_string_decryptor_large_method() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
for _ in 0..250_usize {
let dest = SsaVarId::from_index(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest,
value: ConstValue::I32(42),
}));
}
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block);
let result = DeobfuscationEngine::detect_string_decryptor_pattern(&ssa);
assert!(!result);
}
#[test]
fn test_detect_dispatcher_with_switch() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let value = SsaVarId::from_index(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Switch {
value,
targets: vec![1, 2, 3, 4, 5], default: 6,
}));
ssa.add_block(block);
let result = DeobfuscationEngine::detect_dispatcher_pattern(&ssa);
assert!(result);
}
#[test]
fn test_detect_dispatcher_small_switch() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let value = SsaVarId::from_index(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Switch {
value,
targets: vec![1, 2],
default: 3,
}));
ssa.add_block(block);
let result = DeobfuscationEngine::detect_dispatcher_pattern(&ssa);
assert!(!result);
}
#[test]
fn test_detect_dispatcher_no_switch() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block);
let result = DeobfuscationEngine::detect_dispatcher_pattern(&ssa);
assert!(!result);
}
#[test]
fn test_compute_method_summary() {
let engine = DeobfuscationEngine::default();
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let var = SsaVarId::from_index(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: var,
value: ConstValue::I32(42),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return {
value: Some(var),
}));
ssa.add_block(block);
let token = Token::new(0x06000001);
let summary = engine.compute_method_summary(&ssa, token);
assert_eq!(summary.token, token);
assert!(matches!(summary.return_info, ReturnInfo::Constant(_)));
assert!(matches!(summary.purity, MethodPurity::Pure));
assert!(!summary.is_string_decryptor);
assert!(!summary.is_dispatcher);
}
#[test]
fn test_deobfuscation_result_summary() {
let result = DeobfuscationResult::new_with_techniques(EventLog::new(), Vec::new(), None);
let summary = result.summary();
assert!(!summary.is_empty() || summary == "No changes");
let detailed = result.detailed_summary();
assert!(detailed.contains("Deobfuscation complete"));
}