use std::{ops::Deref, sync::Arc};
use dashmap::DashSet;
use crate::{
compiler::CompilerContext,
deobfuscation::{
config::EngineConfig, decryptors::DecryptorContext, statemachine::StateMachineProvider,
},
emulation::Hook,
metadata::token::Token,
};
pub type HookFactory = Box<dyn Fn() -> Hook + Send + Sync>;
pub struct AnalysisContext {
pub compiler: CompilerContext,
pub decryptors: Arc<DecryptorContext>,
pub dispatchers: Arc<DashSet<Token>>,
pub unflattened_dispatchers: Arc<DashSet<Token>>,
pub config: EngineConfig,
pub emulation_hooks: Arc<boxcar::Vec<HookFactory>>,
pub warmup_methods: Arc<boxcar::Vec<Token>>,
pub statemachine_providers: Arc<boxcar::Vec<Arc<dyn StateMachineProvider>>>,
}
impl Deref for AnalysisContext {
type Target = CompilerContext;
fn deref(&self) -> &CompilerContext {
&self.compiler
}
}
impl AnalysisContext {
pub fn new(call_graph: Arc<crate::analysis::CallGraph>) -> Self {
Self::with_config(call_graph, EngineConfig::default())
}
pub fn with_config(call_graph: Arc<crate::analysis::CallGraph>, config: EngineConfig) -> Self {
Self {
compiler: CompilerContext::new(call_graph),
decryptors: Arc::new(DecryptorContext::new()),
dispatchers: Arc::new(DashSet::new()),
unflattened_dispatchers: Arc::new(DashSet::new()),
config,
emulation_hooks: Arc::new(boxcar::Vec::new()),
warmup_methods: Arc::new(boxcar::Vec::new()),
statemachine_providers: Arc::new(boxcar::Vec::new()),
}
}
#[must_use]
pub fn is_dispatcher(&self, token: Token) -> bool {
self.dispatchers.contains(&token)
}
pub fn mark_dispatcher(&self, token: Token) {
self.dispatchers.insert(token);
self.compiler.no_inline.insert(token);
}
pub fn register_emulation_hook<F>(&self, factory: F)
where
F: Fn() -> Hook + Send + Sync + 'static,
{
self.emulation_hooks.push(Box::new(factory));
}
#[must_use]
pub fn create_emulation_hooks(&self) -> Vec<Hook> {
self.emulation_hooks.iter().map(|(_, f)| f()).collect()
}
#[must_use]
pub fn has_emulation_hooks(&self) -> bool {
!self.emulation_hooks.is_empty()
}
#[must_use]
pub fn emulation_max_instructions(&self) -> u64 {
self.config.emulation_max_instructions
}
pub fn register_warmup_method(&self, method: Token) {
if self.warmup_methods.iter().any(|(_, &m)| m == method) {
return;
}
self.warmup_methods.push(method);
}
#[must_use]
pub fn warmup_methods(&self) -> Vec<Token> {
self.warmup_methods.iter().map(|(_, &m)| m).collect()
}
#[must_use]
pub fn has_warmup_methods(&self) -> bool {
!self.warmup_methods.is_empty()
}
pub fn register_statemachine_provider(&self, provider: Arc<dyn StateMachineProvider>) {
self.statemachine_providers.push(provider);
}
#[must_use]
pub fn has_statemachine_providers(&self) -> bool {
self.statemachine_providers.count() > 0
}
#[must_use]
pub fn get_statemachine_provider_for_method(
&self,
method: Token,
) -> Option<Arc<dyn StateMachineProvider>> {
for (_, provider) in self.statemachine_providers.iter() {
if provider.applies_to_method(method) {
return Some(Arc::clone(provider));
}
}
None
}
#[must_use]
pub fn is_statemachine_method(&self, method: Token) -> bool {
self.get_statemachine_provider_for_method(method).is_some()
}
#[must_use]
pub fn statemachine_methods(&self) -> Vec<Token> {
let mut methods = Vec::new();
for (_, provider) in self.statemachine_providers.iter() {
methods.extend(provider.methods());
}
methods
}
}
#[cfg(test)]
mod tests {
use crate::{
analysis::{CallGraph, ConstValue, SsaVarId},
compiler::CallSiteInfo,
deobfuscation::context::AnalysisContext,
metadata::token::Token,
};
use std::sync::Arc;
#[test]
fn test_call_site_info() {
let info = CallSiteInfo {
caller: Token::new(0x06000001),
offset: 10,
arguments: vec![Some(ConstValue::I32(42)), None],
is_live: true,
};
assert_eq!(info.caller, Token::new(0x06000001));
assert_eq!(info.offset, 10);
assert_eq!(info.arguments.len(), 2);
}
#[test]
fn test_known_values() {
let call_graph = Arc::new(CallGraph::new());
let ctx = AnalysisContext::new(call_graph);
let method = Token::new(0x06000001);
let var1 = SsaVarId::new();
let var2 = SsaVarId::new();
let var3 = SsaVarId::new();
assert!(!ctx.has_known_value(method, var1));
assert_eq!(ctx.known_value_count(method), 0);
ctx.add_known_value(method, var1, ConstValue::I32(42));
ctx.add_known_value(method, var2, ConstValue::I64(100));
ctx.add_known_value(method, var3, ConstValue::True);
assert!(ctx.known_value_is(method, var1, |v| *v == ConstValue::I32(42)));
assert!(ctx.known_value_is(method, var2, |v| *v == ConstValue::I64(100)));
assert!(ctx.known_value_is(method, var3, |v| *v == ConstValue::True));
assert_eq!(ctx.known_value_count(method), 3);
ctx.add_known_value(method, var1, ConstValue::I32(99));
assert!(ctx.known_value_is(method, var1, |v| *v == ConstValue::I32(99)));
let method2 = Token::new(0x06000002);
assert!(!ctx.has_known_value(method2, var1));
ctx.add_known_value(method2, var1, ConstValue::I32(1));
assert!(ctx.known_value_is(method2, var1, |v| *v == ConstValue::I32(1)));
assert!(ctx.known_value_is(method, var1, |v| *v == ConstValue::I32(99)));
ctx.clear_known_values(method);
assert!(!ctx.has_known_value(method, var1));
assert_eq!(ctx.known_value_count(method), 0);
assert!(ctx.known_value_is(method2, var1, |v| *v == ConstValue::I32(1)));
}
#[test]
fn test_known_values_iterator() {
let call_graph = Arc::new(CallGraph::new());
let ctx = AnalysisContext::new(call_graph);
let method = Token::new(0x06000001);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
ctx.add_known_value(method, v0, ConstValue::I32(1));
ctx.add_known_value(method, v1, ConstValue::I32(2));
ctx.add_known_value(method, v2, ConstValue::I32(3));
let mut count = 0;
ctx.for_each_known_value(method, |_, _| count += 1);
assert_eq!(count, 3);
}
#[test]
fn test_thread_safe_access() {
use std::thread;
let call_graph = Arc::new(CallGraph::new());
let ctx = Arc::new(AnalysisContext::new(call_graph));
let method1 = Token::new(0x06000001);
let method2 = Token::new(0x06000002);
let handles: Vec<_> = (0..4)
.map(|i| {
let ctx = Arc::clone(&ctx);
let method = if i % 2 == 0 { method1 } else { method2 };
thread::spawn(move || {
for j in 0..100 {
let var = SsaVarId::new();
ctx.add_known_value(method, var, ConstValue::I32(j));
ctx.mark_dead(Token::new(0x06000000 + i * 1000 + j as u32));
ctx.add_entry_point(Token::new(0x06000000 + i * 1000 + j as u32));
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert!(ctx.known_value_count(method1) > 0);
assert!(ctx.known_value_count(method2) > 0);
}
}