use std::{collections::HashSet, sync::Arc};
use crate::{
compiler::{PassPhase, SsaPass},
deobfuscation::{
context::AnalysisContext,
passes::{SentinelCondition, SentinelTaintRemovalPass},
techniques::{Detection, Evidence, Technique, TechniqueCategory},
},
metadata::token::Token,
CilObject,
};
#[derive(Debug)]
pub struct BmAntiDebugFindings {
pub method_tokens: HashSet<Token>,
}
pub struct BitMonoAntiDebug;
impl Technique for BitMonoAntiDebug {
fn id(&self) -> &'static str {
"bitmono.debug"
}
fn name(&self) -> &'static str {
"BitMono AntiDebugBreakpoints Removal"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Neutralization
}
fn supersedes(&self) -> &[&'static str] {
&["generic.debug"]
}
fn detect(&self, assembly: &CilObject) -> Detection {
let patterns = &["get_UtcNow", "op_Subtraction", "get_TotalMilliseconds"];
let matches = crate::deobfuscation::utils::find_methods_calling_apis(assembly, patterns);
let method_tokens: HashSet<Token> = matches
.into_iter()
.filter(|(_, idxs)| idxs.contains(&0) && idxs.contains(&1) && idxs.contains(&2))
.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} methods with BitMono AntiDebugBreakpoints timing checks \
(UtcNow + op_Subtraction + TotalMilliseconds)"
))],
None,
);
detection.set_findings(Box::new(BmAntiDebugFindings { method_tokens }));
detection
}
fn ssa_phase(&self) -> Option<PassPhase> {
Some(PassPhase::Simplify)
}
fn create_pass(
&self,
_ctx: &AnalysisContext,
detection: &Detection,
_assembly: &Arc<CilObject>,
) -> Vec<Box<dyn SsaPass>> {
let Some(findings) = detection.findings::<BmAntiDebugFindings>() else {
return Vec::new();
};
vec![Box::new(SentinelTaintRemovalPass::new(
"BitMonoAntiDebug",
"Removes BitMono AntiDebugBreakpoints timing checks via taint analysis",
findings.method_tokens.clone(),
vec!["get_UtcNow", "op_Subtraction", "get_TotalMilliseconds"],
SentinelCondition::All,
))]
}
}
#[cfg(test)]
mod tests {
use crate::{
deobfuscation::techniques::{bitmono::BitMonoAntiDebug, Technique},
test::helpers::load_sample,
};
#[test]
fn test_detect_positive() {
let assembly = load_sample("tests/samples/packers/bitmono/0.39.0/bitmono_antidebug.exe");
let technique = BitMonoAntiDebug;
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 = BitMonoAntiDebug;
let detection = technique.detect(&assembly);
assert!(
!detection.is_detected(),
"BitMonoAntiDebug should not detect timing checks in a non-BitMono assembly"
);
}
}