use crate::{
compiler::PassPhase,
deobfuscation::techniques::{Detection, Evidence, Technique, TechniqueCategory},
CilObject,
};
const MIN_SPACE_NAMES: usize = 5;
pub struct BitMonoRenamer;
impl Technique for BitMonoRenamer {
fn id(&self) -> &'static str {
"bitmono.renamer"
}
fn name(&self) -> &'static str {
"BitMono FullRenamer Detection"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Metadata
}
fn detect(&self, assembly: &CilObject) -> Detection {
let mut space_name_count = 0usize;
for type_entry in assembly.types().iter() {
let cil_type = type_entry.value();
if cil_type.name.contains(' ') && !cil_type.name.starts_with('<') {
space_name_count += 1;
}
for i in 0..cil_type.methods.count() {
let Some(method_ref) = cil_type.methods.get(i) else {
continue;
};
let Some(method) = method_ref.upgrade() else {
continue;
};
if method.name.contains(' ') && !method.name.starts_with('<') {
space_name_count += 1;
}
}
}
if space_name_count < MIN_SPACE_NAMES {
return Detection::new_empty();
}
Detection::new_detected(
vec![Evidence::Structural(format!(
"{space_name_count} space-containing names (BitMono FullRenamer)"
))],
None,
)
}
fn ssa_phase(&self) -> Option<PassPhase> {
Some(PassPhase::Normalize)
}
}
#[cfg(test)]
mod tests {
use crate::deobfuscation::techniques::{bitmono::BitMonoRenamer, Technique};
use crate::test::helpers::load_sample;
#[test]
fn test_detect_positive() {
let assembly = load_sample("tests/samples/packers/bitmono/0.39.0/bitmono_renamer.exe");
let technique = BitMonoRenamer;
let detection = technique.detect(&assembly);
if detection.is_detected() {
assert!(
!detection.evidence().is_empty(),
"Positive detection should include evidence"
);
}
}
#[test]
fn test_detect_negative() {
let assembly = load_sample("tests/samples/packers/confuserex/1.6.0/original.exe");
let technique = BitMonoRenamer;
let detection = technique.detect(&assembly);
assert!(
!detection.is_detected(),
"BitMonoRenamer should not detect space-containing names in a non-BitMono assembly"
);
}
}