use std::collections::HashSet;
use crate::{
cilassembly::CleanupRequest,
compiler::PassPhase,
deobfuscation::{
techniques::{Detection, Evidence, Technique, TechniqueCategory},
utils::find_methods_calling_apis,
},
metadata::token::Token,
CilObject,
};
const PAT_VIRTUAL_PROTECT: usize = 0;
const PAT_GET_HINSTANCE: usize = 1;
const PAT_GET_MODULE: usize = 2;
const PAT_MARSHAL_COPY: usize = 3;
const API_PATTERNS: &[&str] = &[
"VirtualProtect",
"GetHINSTANCE",
"get_Module",
"Marshal.Copy",
];
#[derive(Debug)]
pub struct AntiDumpFindings {
pub method_tokens: HashSet<Token>,
pub include_module_cctor: bool,
}
pub struct ConfuserExAntiDump;
impl Technique for ConfuserExAntiDump {
fn id(&self) -> &'static str {
"confuserex.dump"
}
fn name(&self) -> &'static str {
"ConfuserEx Anti-Dump Neutralisation"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Neutralization
}
fn supersedes(&self) -> &[&'static str] {
&["generic.dump"]
}
fn detect(&self, assembly: &CilObject) -> Detection {
let api_hits = find_methods_calling_apis(assembly, API_PATTERNS);
let pinvoke_vp_tokens = super::helpers::resolve_pinvoke_tokens(assembly, "VirtualProtect");
let pinvoke_callers =
super::helpers::find_methods_calling_tokens(assembly, &pinvoke_vp_tokens);
let method_tokens: HashSet<Token> = api_hits
.into_iter()
.filter(|(token, indices)| {
let has_virtualprotect =
indices.contains(&PAT_VIRTUAL_PROTECT) || pinvoke_callers.contains(token);
let has_gethinstance = indices.contains(&PAT_GET_HINSTANCE);
let has_get_module = indices.contains(&PAT_GET_MODULE);
let has_marshal_copy = indices.contains(&PAT_MARSHAL_COPY);
has_virtualprotect && has_gethinstance && has_get_module && has_marshal_copy
})
.map(|(token, _)| token)
.collect();
if method_tokens.is_empty() {
return Detection::new_empty();
}
let count = method_tokens.len();
let mut detection = Detection::new_detected(
vec![Evidence::BytecodePattern(format!(
"{count} anti-dump methods (VirtualProtect + GetHINSTANCE + get_Module + Marshal.Copy)",
))],
None,
);
for token in &method_tokens {
detection.cleanup_mut().add_method(*token);
}
detection.set_findings(Box::new(AntiDumpFindings {
method_tokens,
include_module_cctor: true,
}));
detection
}
fn ssa_phase(&self) -> Option<PassPhase> {
Some(PassPhase::Simplify)
}
fn cleanup(&self, detection: &Detection) -> Option<CleanupRequest> {
let findings = detection.findings::<AntiDumpFindings>()?;
if findings.method_tokens.is_empty() {
return None;
}
let mut request = CleanupRequest::new();
for token in &findings.method_tokens {
request.add_method(*token);
}
Some(request)
}
}
#[cfg(test)]
mod tests {
use crate::{
deobfuscation::techniques::{
confuserex::dump::{AntiDumpFindings, ConfuserExAntiDump},
Technique,
},
test::helpers::load_sample,
};
#[test]
fn test_detect_positive() {
let assembly = load_sample("tests/samples/packers/confuserex/1.6.0/mkaring_maximum.exe");
let technique = ConfuserExAntiDump;
let detection = technique.detect(&assembly);
if detection.is_detected() {
assert!(
!detection.evidence().is_empty(),
"Detection should have evidence"
);
let findings = detection
.findings::<AntiDumpFindings>()
.expect("Should have AntiDumpFindings");
assert!(
!findings.method_tokens.is_empty(),
"Should have anti-dump method tokens"
);
}
}
#[test]
fn test_no_false_positive_on_antidebug() {
let assembly = load_sample("tests/samples/packers/confuserex/1.6.0/mkaring_minimal.exe");
let technique = ConfuserExAntiDump;
let detection = technique.detect(&assembly);
assert!(
!detection.is_detected(),
"ConfuserExAntiDump should not false-positive on anti-debug-only sample"
);
}
#[test]
fn test_detect_negative() {
let assembly = load_sample("tests/samples/packers/confuserex/1.6.0/original.exe");
let technique = ConfuserExAntiDump;
let detection = technique.detect(&assembly);
assert!(
!detection.is_detected(),
"ConfuserExAntiDump should not detect anti-dump in original.exe"
);
}
}