use std::any::Any;
use crate::{
deobfuscation::techniques::{
netreactor::helpers, Detection, Evidence, Technique, TechniqueCategory,
},
metadata::token::Token,
CilObject,
};
#[derive(Debug)]
pub struct PrivateImplFindings {
pub container_tokens: Vec<Token>,
}
pub struct NetReactorPrivateImpl;
impl Technique for NetReactorPrivateImpl {
fn id(&self) -> &'static str {
"netreactor.privateimpl"
}
fn name(&self) -> &'static str {
".NET Reactor Data Container Cleanup"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Neutralization
}
fn requires(&self) -> &[&'static str] {
&["netreactor.antitrial"]
}
fn detect(&self, assembly: &CilObject) -> Detection {
let has_module_trial = helpers::find_trial_checks(assembly)
.iter()
.any(|t| t.is_on_module_type);
if !has_module_trial {
return Detection::new_empty();
}
let containers = helpers::find_nr_private_impl_containers(assembly);
if containers.is_empty() {
return Detection::new_empty();
}
let container_tokens: Vec<Token> = containers.iter().map(|c| c.container_token).collect();
let count = container_tokens.len();
let mut detection = Detection::new_detected(
vec![Evidence::Structural(format!(
"{count} NR-injected <PrivateImplementationDetails>{{GUID}} container(s)"
))],
Some(Box::new(PrivateImplFindings {
container_tokens: container_tokens.clone(),
}) as Box<dyn Any + Send + Sync>),
);
for token in &container_tokens {
detection.cleanup_mut().add_type(*token);
}
detection
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::validation::ValidationConfig;
fn try_load_sample(name: &str) -> Option<CilObject> {
let path = format!("tests/samples/packers/netreactor/7.5.0/{name}");
if !std::path::Path::new(&path).exists() {
eprintln!("Skipping test: sample not found at {path}");
return None;
}
Some(
CilObject::from_path_with_validation(&path, ValidationConfig::analysis())
.unwrap_or_else(|e| panic!("Failed to load {name}: {e}")),
)
}
#[test]
fn test_detect_positive_obfuscation() {
let Some(assembly) = try_load_sample("reactor_obfuscation.exe") else {
return;
};
let detection = NetReactorPrivateImpl.detect(&assembly);
assert!(
detection.is_detected(),
"Should detect NR GUID PrivateImpl container in reactor_obfuscation.exe"
);
let findings = detection
.findings::<PrivateImplFindings>()
.expect("Should attach findings");
assert!(
!findings.container_tokens.is_empty(),
"Should record at least one container token"
);
}
#[test]
fn test_detect_negative_baseline() {
let Some(assembly) = try_load_sample("original.exe") else {
return;
};
let detection = NetReactorPrivateImpl.detect(&assembly);
assert!(
!detection.is_detected(),
"Should not detect a GUID container in unprotected original.exe \
(baseline has only the compiler-generated naked <PrivateImplementationDetails>)"
);
}
}