use std::{collections::HashSet, sync::Arc};
use crate::{
deobfuscation::{detection::DetectionScore, StateMachineProvider},
metadata::token::Token,
};
#[derive(Debug, Clone)]
pub struct NativeHelperInfo {
pub token: Token,
pub rva: u32,
pub callers: Vec<Token>,
}
impl NativeHelperInfo {
#[must_use]
pub fn new(token: Token, rva: u32) -> Self {
Self {
token,
rva,
callers: Vec::new(),
}
}
pub fn add_caller(&mut self, caller: Token) {
if !self.callers.contains(&caller) {
self.callers.push(caller);
}
}
}
#[derive(Debug, Clone)]
pub struct DeobfuscationFindings {
pub detection: DetectionScore,
pub obfuscator_name: Option<String>,
pub obfuscator_version: Option<String>,
pub decryptor_methods: boxcar::Vec<Token>,
pub constant_data_fields: boxcar::Vec<Token>,
pub constant_data_types: boxcar::Vec<Token>,
pub statemachine_provider: Option<Arc<dyn StateMachineProvider>>,
pub native_helpers: boxcar::Vec<NativeHelperInfo>,
pub anti_tamper_methods: boxcar::Vec<Token>,
pub encrypted_method_count: usize,
pub decrypted_method_tokens: boxcar::Vec<Token>,
pub anti_debug_methods: boxcar::Vec<Token>,
pub anti_dump_methods: boxcar::Vec<Token>,
pub proxy_methods: boxcar::Vec<Token>,
pub resource_handler_methods: boxcar::Vec<Token>,
pub marker_attribute_tokens: boxcar::Vec<Token>,
pub obfuscator_type_tokens: boxcar::Vec<Token>,
pub suppress_ildasm_token: Option<Token>,
pub invalid_metadata_entries: boxcar::Vec<Token>,
pub obfuscator_marker_value: Option<u32>,
pub enc_tables: boxcar::Vec<u8>,
pub artifact_sections: boxcar::Vec<String>,
pub protection_infrastructure_types: boxcar::Vec<Token>,
pub infrastructure_fields: boxcar::Vec<Token>,
}
impl Default for DeobfuscationFindings {
fn default() -> Self {
Self {
detection: DetectionScore::new(),
obfuscator_name: None,
obfuscator_version: None,
decryptor_methods: boxcar::Vec::new(),
constant_data_fields: boxcar::Vec::new(),
constant_data_types: boxcar::Vec::new(),
statemachine_provider: None,
native_helpers: boxcar::Vec::new(),
anti_tamper_methods: boxcar::Vec::new(),
encrypted_method_count: 0,
decrypted_method_tokens: boxcar::Vec::new(),
anti_debug_methods: boxcar::Vec::new(),
anti_dump_methods: boxcar::Vec::new(),
proxy_methods: boxcar::Vec::new(),
resource_handler_methods: boxcar::Vec::new(),
marker_attribute_tokens: boxcar::Vec::new(),
obfuscator_type_tokens: boxcar::Vec::new(),
suppress_ildasm_token: None,
invalid_metadata_entries: boxcar::Vec::new(),
obfuscator_marker_value: None,
enc_tables: boxcar::Vec::new(),
artifact_sections: boxcar::Vec::new(),
protection_infrastructure_types: boxcar::Vec::new(),
infrastructure_fields: boxcar::Vec::new(),
}
}
}
impl DeobfuscationFindings {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn detection_score(&self) -> usize {
self.detection.score()
}
#[must_use]
pub fn detected(&self, threshold: usize) -> bool {
self.detection.score() >= threshold
}
#[must_use]
pub fn detection_summary(&self) -> String {
if let Some(name) = &self.obfuscator_name {
format!("Detected: {} (score={})", name, self.detection.score())
} else {
"No obfuscator detected".to_string()
}
}
#[must_use]
pub fn has_any_protection(&self) -> bool {
self.has_invalid_metadata()
|| self.decryptor_methods.count() > 0
|| self.anti_tamper_methods.count() > 0
|| self.anti_debug_methods.count() > 0
|| self.anti_dump_methods.count() > 0
|| self.resource_handler_methods.count() > 0
|| self.proxy_methods.count() > 0
|| self.has_enc_tables()
|| self.has_marker_attributes()
|| self.has_suppress_ildasm()
|| self.protection_infrastructure_types.count() > 0
|| self.infrastructure_fields.count() > 0
}
#[must_use]
pub fn has_marker_attributes(&self) -> bool {
self.marker_attribute_tokens.count() > 0
}
#[must_use]
pub fn has_suppress_ildasm(&self) -> bool {
self.suppress_ildasm_token.is_some()
}
#[must_use]
pub fn has_invalid_metadata(&self) -> bool {
self.invalid_metadata_entries.count() > 0 || self.obfuscator_marker_value.is_some()
}
#[must_use]
pub fn invalid_metadata_count(&self) -> usize {
self.invalid_metadata_entries.count()
}
#[must_use]
pub fn has_obfuscator_marker(&self) -> bool {
self.obfuscator_marker_value.is_some()
}
#[must_use]
pub fn has_enc_tables(&self) -> bool {
self.enc_tables.count() > 0
}
#[must_use]
pub fn needs_string_decryption(&self) -> bool {
self.decryptor_methods.count() > 0
}
#[must_use]
pub fn needs_anti_tamper_patch(&self) -> bool {
self.anti_tamper_methods.count() > 0
}
#[must_use]
pub fn needs_anti_tamper_decryption(&self) -> bool {
self.anti_tamper_methods.count() > 0 && self.encrypted_method_count > 0
}
#[must_use]
pub fn needs_anti_debug_patch(&self) -> bool {
self.anti_debug_methods.count() > 0
}
#[must_use]
pub fn needs_anti_dump_patch(&self) -> bool {
self.anti_dump_methods.count() > 0
}
#[must_use]
pub fn needs_resource_decryption(&self) -> bool {
self.resource_handler_methods.count() > 0
}
#[must_use]
pub fn needs_metadata_fix(&self) -> bool {
self.has_invalid_metadata()
}
#[must_use]
pub fn needs_marker_attribute_removal(&self) -> bool {
self.marker_attribute_tokens.count() > 0
}
#[must_use]
pub fn has_constant_data_infrastructure(&self) -> bool {
self.constant_data_fields.count() > 0 || self.constant_data_types.count() > 0
}
#[must_use]
pub fn has_protection_infrastructure_types(&self) -> bool {
self.protection_infrastructure_types.count() > 0
}
#[must_use]
pub fn has_infrastructure_fields(&self) -> bool {
self.infrastructure_fields.count() > 0
}
#[must_use]
pub fn needs_proxy_inlining(&self) -> bool {
self.proxy_methods.count() > 0
}
#[must_use]
pub fn needs_native_conversion(&self) -> bool {
!self.native_helpers.is_empty()
}
#[must_use]
pub fn all_protection_method_tokens(&self) -> HashSet<Token> {
let mut tokens = HashSet::new();
for (_, token) in &self.decryptor_methods {
tokens.insert(*token);
}
for (_, token) in &self.anti_tamper_methods {
tokens.insert(*token);
}
for (_, token) in &self.anti_debug_methods {
tokens.insert(*token);
}
for (_, token) in &self.anti_dump_methods {
tokens.insert(*token);
}
for (_, token) in &self.resource_handler_methods {
tokens.insert(*token);
}
for (_, token) in &self.proxy_methods {
tokens.insert(*token);
}
tokens
}
#[must_use]
pub fn all_removable_field_tokens(&self) -> HashSet<Token> {
let mut tokens = HashSet::new();
for (_, token) in &self.constant_data_fields {
tokens.insert(*token);
}
for (_, token) in &self.infrastructure_fields {
tokens.insert(*token);
}
tokens
}
#[must_use]
pub fn all_removable_type_tokens(&self) -> HashSet<Token> {
let mut tokens = HashSet::new();
for (_, token) in &self.obfuscator_type_tokens {
tokens.insert(*token);
}
for (_, token) in &self.constant_data_types {
tokens.insert(*token);
}
for (_, token) in &self.protection_infrastructure_types {
tokens.insert(*token);
}
tokens
}
#[must_use]
pub fn uses_cfg_mode(&self) -> bool {
self.statemachine_provider
.as_ref()
.is_some_and(|p| !p.methods().is_empty())
}
#[must_use]
pub fn is_cfg_mode_method(&self, token: Token) -> bool {
self.statemachine_provider
.as_ref()
.is_some_and(|p| p.applies_to_method(token))
}
}