use std::any::Any;
use crate::{
deobfuscation::techniques::{
netreactor::helpers, Detection, Evidence, Technique, TechniqueCategory,
},
metadata::token::Token,
CilObject,
};
#[derive(Debug)]
pub struct AntiTrialFindings {
pub trial_method_tokens: Vec<Token>,
}
pub struct NetReactorAntiTrial;
impl Technique for NetReactorAntiTrial {
fn id(&self) -> &'static str {
"netreactor.antitrial"
}
fn name(&self) -> &'static str {
".NET Reactor Trial Guard Removal"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Neutralization
}
fn detect(&self, assembly: &CilObject) -> Detection {
let trial_check_tokens: Vec<Token> = helpers::find_trial_checks(assembly)
.into_iter()
.filter(|t| t.is_on_module_type)
.map(|t| t.method_token)
.collect();
if trial_check_tokens.is_empty() {
return Detection::new_empty();
}
let count = trial_check_tokens.len();
let mut detection = Detection::new_detected(
vec![Evidence::BytecodePattern(format!(
"{count} <Module> trial-guard method(s) (DateTime + TimeSpan.get_Days + throw)"
))],
Some(Box::new(AntiTrialFindings {
trial_method_tokens: trial_check_tokens.clone(),
}) as Box<dyn Any + Send + Sync>),
);
for token in &trial_check_tokens {
detection.cleanup_mut().add_method(*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 = NetReactorAntiTrial.detect(&assembly);
assert!(
detection.is_detected(),
"Should detect <Module> trial guard in reactor_obfuscation.exe"
);
let findings = detection
.findings::<AntiTrialFindings>()
.expect("Should attach findings");
assert!(
!findings.trial_method_tokens.is_empty(),
"Should record at least one trial method token"
);
assert_eq!(
detection.cleanup().methods_len(),
findings.trial_method_tokens.len(),
"Each detected trial method should be marked for cleanup"
);
}
#[test]
fn test_detect_negative_baseline() {
let Some(assembly) = try_load_sample("original.exe") else {
return;
};
let detection = NetReactorAntiTrial.detect(&assembly);
assert!(
!detection.is_detected(),
"Should not detect a trial guard in unprotected original.exe"
);
}
}