use std::{any::Any, collections::HashSet};
use crate::{
cilassembly::{CilAssembly, GeneratorConfig},
compiler::{EventKind, EventLog},
deobfuscation::{
passes::NativeMethodConversionPass,
techniques::{
Detection, Detections, Evidence, Technique, TechniqueCategory, WorkingAssembly,
},
},
metadata::{tables::ImplMapRaw, token::Token, validation::ValidationConfig},
CilObject, Result,
};
#[derive(Debug)]
pub struct NativeFindings {
pub helpers: Vec<Token>,
}
pub struct ConfuserExNativeHelpers;
impl Technique for ConfuserExNativeHelpers {
fn id(&self) -> &'static str {
"confuserex.natives"
}
fn name(&self) -> &'static str {
"ConfuserEx Native x86 Helper Conversion"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Protection
}
fn requires(&self) -> &[&'static str] {
&["confuserex.tamper"]
}
fn detect(&self, assembly: &CilObject) -> Detection {
let Some(tables) = assembly.tables() else {
return Detection::new_empty();
};
let mut native_tokens = HashSet::new();
for method_entry in assembly.methods() {
let method = method_entry.value();
if method.is_code_native() && method.rva.is_some() {
native_tokens.insert(method.token);
}
}
if native_tokens.is_empty() {
return Detection::new_empty();
}
let mut helpers = Vec::new();
for method_entry in assembly.methods() {
let method = method_entry.value();
for instr in method.instructions() {
if let Some(token) = instr.get_token_operand() {
if native_tokens.contains(&token) && !helpers.contains(&token) {
helpers.push(token);
}
}
}
}
let pinvoke_methods = collect_implmap_methods(tables);
helpers.retain(|token| !pinvoke_methods.contains(token));
if helpers.is_empty() {
return Detection::new_empty();
}
let count = helpers.len();
let mut detection = Detection::new_detected(
vec![Evidence::BytecodePattern(format!(
"{count} native x86 helper methods called from CIL decryptors",
))],
Some(Box::new(NativeFindings {
helpers: helpers.clone(),
}) as Box<dyn Any + Send + Sync>),
);
for token in &helpers {
detection.cleanup_mut().add_method(*token);
}
detection
}
fn byte_transform(
&self,
assembly: &mut WorkingAssembly,
detection: &Detection,
_detections: &Detections,
) -> Option<Result<EventLog>> {
let events = EventLog::new();
let Some(findings) = detection.findings::<NativeFindings>() else {
return Some(Ok(events));
};
if findings.helpers.is_empty() {
return Some(Ok(events));
}
let co = match assembly.cilobject() {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let bytes = co.file().data().to_vec();
let mut cil_assembly =
match CilAssembly::from_bytes_with_validation(bytes, ValidationConfig::analysis()) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut converter = NativeMethodConversionPass::new();
for &token in &findings.helpers {
converter.register_target(token);
}
let pe_file = co.file();
let stats = match converter.run(&mut cil_assembly, pe_file) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
if stats.failed > 0 {
log::warn!(
"Converted {}/{} native x86 methods to CIL (failures: {})",
stats.converted,
stats.converted + stats.failed,
stats.errors.join(", ")
);
}
if stats.converted > 0 {
events.record(EventKind::ArtifactRemoved).message(format!(
"Converted {} native x86 helper method(s) to CIL",
stats.converted,
));
let new_assembly = match cil_assembly
.into_cilobject_with(ValidationConfig::analysis(), GeneratorConfig::default())
{
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
assembly.replace_assembly(new_assembly);
}
Some(Ok(events))
}
fn requires_regeneration(&self) -> bool {
true
}
}
fn collect_implmap_methods(tables: &crate::metadata::streams::TablesHeader<'_>) -> HashSet<Token> {
let mut pinvoke_methods = HashSet::new();
if let Some(implmap_table) = tables.table::<ImplMapRaw>() {
for row in implmap_table {
pinvoke_methods.insert(row.member_forwarded.token);
}
}
pinvoke_methods
}
#[cfg(test)]
mod tests {
use crate::{
deobfuscation::techniques::{
confuserex::natives::{ConfuserExNativeHelpers, NativeFindings},
Technique,
},
test::helpers::load_sample,
};
#[test]
fn test_detect_positive() {
let assembly =
load_sample("tests/samples/packers/confuserex/1.6.0/mkaring_constants_x86.exe");
let technique = ConfuserExNativeHelpers;
let detection = technique.detect(&assembly);
assert!(
detection.is_detected(),
"ConfuserExNativeHelpers should detect native helpers in mkaring_constants_x86.exe"
);
assert!(
!detection.evidence().is_empty(),
"Detection should have evidence"
);
let findings = detection
.findings::<NativeFindings>()
.expect("Should have NativeFindings");
assert!(
!findings.helpers.is_empty(),
"Should have native helper tokens"
);
}
#[test]
fn test_detect_negative() {
let assembly = load_sample("tests/samples/packers/confuserex/1.6.0/original.exe");
let technique = ConfuserExNativeHelpers;
let detection = technique.detect(&assembly);
assert!(
!detection.is_detected(),
"ConfuserExNativeHelpers should not detect native helpers in original.exe"
);
}
}