use std::any::Any;
use crate::{
assembly::Instruction,
cilassembly::CleanupRequest,
deobfuscation::techniques::{Detection, Evidence, Technique, TechniqueCategory},
metadata::token::Token,
prelude::FlowType,
CilObject,
};
const MAX_PROXY_INSTRUCTIONS: usize = 10;
#[derive(Debug)]
pub struct ProxyFindings {
pub proxy_methods: Vec<Token>,
pub strong_proxy_methods: Vec<Token>,
}
pub struct ConfuserExReferenceProxy;
impl Technique for ConfuserExReferenceProxy {
fn id(&self) -> &'static str {
"confuserex.proxy"
}
fn name(&self) -> &'static str {
"ConfuserEx Reference Proxy Inlining"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Call
}
fn detect(&self, assembly: &CilObject) -> Detection {
let entry_point_token = assembly.cor20header().entry_point_token;
let entry_token = if entry_point_token != 0 {
Some(Token::new(entry_point_token))
} else {
None
};
let mut proxy_methods = Vec::new();
let mut strong_proxy_methods = Vec::new();
for method_entry in assembly.methods() {
let method = method_entry.value();
if method.is_ctor() || method.is_cctor() {
continue;
}
if entry_token.is_some_and(|t| t == method.token) {
continue;
}
let instructions: Vec<&Instruction> = method.instructions().collect();
if instructions.is_empty() || instructions.len() > MAX_PROXY_INSTRUCTIONS {
continue;
}
if is_strong_proxy(&instructions, assembly) {
proxy_methods.push(method.token);
strong_proxy_methods.push(method.token);
} else if is_mild_proxy(&instructions) {
proxy_methods.push(method.token);
}
}
if proxy_methods.is_empty() {
return Detection::new_empty();
}
let count = proxy_methods.len();
let findings = ProxyFindings {
proxy_methods,
strong_proxy_methods,
};
Detection::new_detected(
vec![Evidence::BytecodePattern(format!(
"{count} reference proxy forwarding stubs",
))],
Some(Box::new(findings) as Box<dyn Any + Send + Sync>),
)
}
fn cleanup(&self, detection: &Detection) -> Option<CleanupRequest> {
let findings = detection.findings::<ProxyFindings>()?;
if findings.strong_proxy_methods.is_empty() {
return None;
}
let mut request = CleanupRequest::new();
for token in &findings.strong_proxy_methods {
request.add_method(*token);
}
Some(request)
}
}
fn is_mild_proxy(instructions: &[&Instruction]) -> bool {
if instructions.len() < 2 {
return false;
}
let last = instructions.last().unwrap();
if last.mnemonic != "ret" {
return false;
}
let call_instr = instructions[instructions.len() - 2];
if call_instr.mnemonic != "call" || call_instr.flow_type != FlowType::Call {
return false;
}
if call_instr.get_token_operand().is_none() {
return false;
}
for instr in &instructions[..instructions.len() - 2] {
if !instr.mnemonic.starts_with("ldarg") {
return false;
}
}
true
}
fn is_strong_proxy(instructions: &[&Instruction], _assembly: &CilObject) -> bool {
if instructions.len() < 3 {
return false;
}
if instructions[0].mnemonic != "ldsfld" {
return false;
}
let last = instructions.last().unwrap();
if last.mnemonic != "ret" {
return false;
}
let call_instr = instructions[instructions.len() - 2];
if call_instr.mnemonic != "callvirt" || call_instr.flow_type != FlowType::Call {
return false;
}
if call_instr.get_token_operand().is_none() {
return false;
}
for instr in &instructions[1..instructions.len() - 2] {
if !instr.mnemonic.starts_with("ldarg") {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use crate::{
deobfuscation::techniques::{
confuserex::proxy::{ConfuserExReferenceProxy, ProxyFindings},
Technique,
},
test::helpers::load_sample,
};
#[test]
fn test_detect_positive() {
let assembly = load_sample("tests/samples/packers/confuserex/1.6.0/mkaring_normal.exe");
let technique = ConfuserExReferenceProxy;
let detection = technique.detect(&assembly);
assert!(
detection.is_detected(),
"ConfuserExReferenceProxy should detect proxy stubs in mkaring_normal.exe"
);
assert!(
!detection.evidence().is_empty(),
"Detection should have evidence"
);
let findings = detection
.findings::<ProxyFindings>()
.expect("Should have ProxyFindings");
assert!(
!findings.proxy_methods.is_empty(),
"Should have proxy method tokens"
);
}
#[test]
fn test_detect_negative() {
let assembly = load_sample("tests/samples/packers/confuserex/1.6.0/original.exe");
let technique = ConfuserExReferenceProxy;
let detection = technique.detect(&assembly);
assert!(
!detection.is_detected(),
"ConfuserExReferenceProxy should not detect proxy stubs in original.exe"
);
}
}