use std::{collections::HashMap, sync::Arc};
use crate::{
compiler::SsaPass,
deobfuscation::{
detection::DetectionScore, findings::DeobfuscationFindings, obfuscators::Obfuscator,
ConfuserExObfuscator, ObfuscarObfuscator,
},
CilObject,
};
pub struct ObfuscatorRegistry {
obfuscators: HashMap<String, Arc<dyn Obfuscator>>,
threshold: usize,
}
impl Default for ObfuscatorRegistry {
fn default() -> Self {
Self::new()
}
}
impl ObfuscatorRegistry {
#[must_use]
pub fn new() -> Self {
let mut obfuscators = HashMap::new();
let confuser: Arc<dyn Obfuscator> = Arc::new(ConfuserExObfuscator::new());
obfuscators.insert(confuser.name(), confuser);
let obfuscar: Arc<dyn Obfuscator> = Arc::new(ObfuscarObfuscator::new());
obfuscators.insert(obfuscar.name(), obfuscar);
Self {
obfuscators,
threshold: 50,
}
}
#[must_use]
pub fn empty() -> Self {
Self {
obfuscators: HashMap::new(),
threshold: 50,
}
}
pub fn set_threshold(&mut self, threshold: usize) {
self.threshold = threshold;
}
#[must_use]
pub fn threshold(&self) -> usize {
self.threshold
}
pub fn register(&mut self, obfuscator: Arc<dyn Obfuscator>) {
self.obfuscators.insert(obfuscator.id().clone(), obfuscator);
}
pub fn unregister(&mut self, id: &str) -> Option<Arc<dyn Obfuscator>> {
self.obfuscators.remove(id)
}
#[must_use]
pub fn get(&self, id: &str) -> Option<&Arc<dyn Obfuscator>> {
self.obfuscators.get(id)
}
#[must_use]
pub fn has(&self, id: &str) -> bool {
self.obfuscators.contains_key(id)
}
#[must_use]
pub fn obfuscator_ids(&self) -> Vec<&str> {
self.obfuscators.keys().map(String::as_str).collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.obfuscators.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.obfuscators.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Arc<dyn Obfuscator>> {
self.obfuscators.values()
}
pub fn detect(
&self,
assembly: &CilObject,
) -> Vec<(String, DetectionScore, DeobfuscationFindings)> {
let mut results: Vec<(String, DetectionScore, DeobfuscationFindings)> = self
.obfuscators
.iter()
.map(|(id, obfuscator)| {
let mut findings = DeobfuscationFindings::new();
let score = obfuscator.detect(assembly, &mut findings);
findings.detection = score.clone();
(id.clone(), score, findings)
})
.filter(|(_, score, _)| score.score() >= self.threshold)
.collect();
results.sort_by(|a, b| b.1.cmp(&a.1));
results
}
pub fn detect_best(&self, assembly: &CilObject) -> Option<Arc<dyn Obfuscator>> {
let results = self.detect(assembly);
results
.first()
.and_then(|(id, _, _)| self.obfuscators.get(id).cloned())
}
pub fn get_passes_for_detected(&self, assembly: &CilObject) -> Vec<Box<dyn SsaPass>> {
let detected = self.detect(assembly);
let mut passes = Vec::new();
for (id, _, findings) in &detected {
if let Some(obfuscator) = self.obfuscators.get(id) {
passes.extend(obfuscator.passes(findings));
}
}
passes
}
#[must_use]
pub fn obfuscator_info(&self) -> Vec<ObfuscatorInfo> {
self.obfuscators
.values()
.map(|o| ObfuscatorInfo {
id: o.id().clone(),
name: o.name().clone(),
description: o.description().to_string(),
versions: o
.supported_versions()
.iter()
.map(|s| (*s).to_string())
.collect(),
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct ObfuscatorInfo {
pub id: String,
pub name: String,
pub description: String,
pub versions: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
struct TestObfuscator {
id: String,
score: usize,
}
impl TestObfuscator {
fn new(id: impl ToString, score: usize) -> Self {
Self {
id: id.to_string(),
score,
}
}
}
impl Obfuscator for TestObfuscator {
fn id(&self) -> String {
self.id.clone()
}
fn name(&self) -> String {
self.id.clone()
}
fn detect(
&self,
_assembly: &CilObject,
_findings: &mut DeobfuscationFindings,
) -> DetectionScore {
DetectionScore::with_score(self.score)
}
}
#[test]
fn test_registry_basic() {
let mut registry = ObfuscatorRegistry::empty();
assert!(registry.is_empty());
registry.register(Arc::new(TestObfuscator::new("test1", 60)));
assert!(!registry.is_empty());
assert_eq!(registry.len(), 1);
assert!(registry.has("test1"));
assert!(!registry.has("test2"));
}
#[test]
fn test_registry_threshold_setting() {
let mut registry = ObfuscatorRegistry::empty();
assert_eq!(registry.threshold, 50);
registry.set_threshold(30);
assert_eq!(registry.threshold, 30);
}
#[test]
fn test_registry_get_obfuscator() {
let mut registry = ObfuscatorRegistry::empty();
registry.register(Arc::new(TestObfuscator::new("test1", 60)));
assert!(registry.get("test1").is_some());
assert!(registry.get("nonexistent").is_none());
}
#[test]
fn test_registry_iter() {
let mut registry = ObfuscatorRegistry::empty();
registry.register(Arc::new(TestObfuscator::new("a", 10)));
registry.register(Arc::new(TestObfuscator::new("b", 20)));
let ids: Vec<_> = registry.iter().map(|o| o.id()).collect();
assert_eq!(ids.len(), 2);
assert!(ids.contains(&"a".to_string()));
assert!(ids.contains(&"b".to_string()));
}
#[test]
fn test_registry_unregister() {
let mut registry = ObfuscatorRegistry::empty();
registry.register(Arc::new(TestObfuscator::new("test1", 60)));
assert!(registry.has("test1"));
let removed = registry.unregister("test1");
assert!(removed.is_some());
assert!(!registry.has("test1"));
let removed_again = registry.unregister("test1");
assert!(removed_again.is_none());
}
#[test]
fn test_registry_obfuscator_ids() {
let mut registry = ObfuscatorRegistry::empty();
registry.register(Arc::new(TestObfuscator::new("a", 10)));
registry.register(Arc::new(TestObfuscator::new("b", 20)));
let ids = registry.obfuscator_ids();
assert_eq!(ids.len(), 2);
assert!(ids.contains(&"a"));
assert!(ids.contains(&"b"));
}
#[test]
fn test_obfuscator_info() {
let mut registry = ObfuscatorRegistry::empty();
registry.register(Arc::new(TestObfuscator::new("test1", 60)));
let info = registry.obfuscator_info();
assert_eq!(info.len(), 1);
assert_eq!(info[0].id, "test1");
}
#[test]
fn test_registry_new_has_builtin_obfuscators() {
let registry = ObfuscatorRegistry::new();
assert!(!registry.is_empty());
assert!(registry.has("ConfuserEx"));
}
}