use std::sync::atomic::{AtomicBool, Ordering};
use dashmap::{DashMap, DashSet};
use log::warn;
use crate::{
analysis::SsaFunction,
metadata::token::Token,
{Error, Result},
};
#[derive(Debug)]
pub enum WorkItem {
BuildSsa(Vec<Token>),
InjectSsa {
token: Token,
function: Box<SsaFunction>,
},
RedetectMethods(Vec<Token>),
RedetectTypes(Vec<Token>),
RedetectAssembly,
}
#[derive(Debug)]
pub struct DrainedWorkItems {
pub build_ssa: Vec<Token>,
pub inject_ssa: Vec<(Token, Box<SsaFunction>)>,
pub redetect_methods: Vec<Token>,
pub redetect_types: Vec<Token>,
pub redetect_assembly: bool,
}
impl DrainedWorkItems {
pub fn is_empty(&self) -> bool {
self.build_ssa.is_empty()
&& self.inject_ssa.is_empty()
&& self.redetect_methods.is_empty()
&& self.redetect_types.is_empty()
&& !self.redetect_assembly
}
pub fn has_redetect(&self) -> bool {
!self.redetect_methods.is_empty()
|| !self.redetect_types.is_empty()
|| self.redetect_assembly
}
}
pub struct WorkQueue {
build_ssa: DashSet<Token>,
inject_ssa: DashMap<Token, Box<SsaFunction>>,
redetect_methods: DashSet<Token>,
redetect_types: DashSet<Token>,
redetect_assembly: AtomicBool,
}
impl WorkQueue {
#[must_use]
pub fn new() -> Self {
Self {
build_ssa: DashSet::new(),
inject_ssa: DashMap::new(),
redetect_methods: DashSet::new(),
redetect_types: DashSet::new(),
redetect_assembly: AtomicBool::new(false),
}
}
pub fn submit(&self, item: WorkItem) -> Result<()> {
match item {
WorkItem::BuildSsa(tokens) => {
for token in tokens {
self.build_ssa.insert(token);
}
}
WorkItem::InjectSsa { token, function } => {
if self.inject_ssa.contains_key(&token) {
warn!(
"Duplicate InjectSsa for token {}, rejecting submission",
token
);
return Err(Error::Deobfuscation(format!(
"Duplicate InjectSsa for token {token}"
)));
}
self.inject_ssa.insert(token, function);
}
WorkItem::RedetectMethods(tokens) => {
for token in tokens {
self.redetect_methods.insert(token);
}
}
WorkItem::RedetectTypes(tokens) => {
for token in tokens {
self.redetect_types.insert(token);
}
}
WorkItem::RedetectAssembly => {
self.redetect_assembly.store(true, Ordering::Release);
}
}
Ok(())
}
pub fn submit_all(&self, items: impl IntoIterator<Item = WorkItem>) -> Result<()> {
for item in items {
self.submit(item)?;
}
Ok(())
}
pub fn drain(&self) -> DrainedWorkItems {
let build_ssa: Vec<Token> = self.build_ssa.iter().map(|r| *r).collect();
self.build_ssa.clear();
let inject_ssa: Vec<(Token, Box<SsaFunction>)> = self
.inject_ssa
.iter()
.map(|r| *r.key())
.collect::<Vec<_>>()
.into_iter()
.filter_map(|k| self.inject_ssa.remove(&k))
.collect();
let redetect_methods: Vec<Token> = self.redetect_methods.iter().map(|r| *r).collect();
self.redetect_methods.clear();
let redetect_types: Vec<Token> = self.redetect_types.iter().map(|r| *r).collect();
self.redetect_types.clear();
let redetect_assembly = self.redetect_assembly.swap(false, Ordering::AcqRel);
DrainedWorkItems {
build_ssa,
inject_ssa,
redetect_methods,
redetect_types,
redetect_assembly,
}
}
pub fn is_empty(&self) -> bool {
self.build_ssa.is_empty()
&& self.inject_ssa.is_empty()
&& self.redetect_methods.is_empty()
&& self.redetect_types.is_empty()
&& !self.redetect_assembly.load(Ordering::Acquire)
}
pub fn len(&self) -> usize {
let assembly = usize::from(self.redetect_assembly.load(Ordering::Acquire));
self.build_ssa.len()
+ self.inject_ssa.len()
+ self.redetect_methods.len()
+ self.redetect_types.len()
+ assembly
}
}
impl Default for WorkQueue {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::{
analysis::SsaFunction,
deobfuscation::workqueue::{WorkItem, WorkQueue},
metadata::token::Token,
};
#[test]
fn test_submit_and_drain() {
let queue = WorkQueue::new();
queue
.submit(WorkItem::BuildSsa(vec![Token::new(0x06000001)]))
.unwrap();
queue.submit(WorkItem::RedetectAssembly).unwrap();
let items = queue.drain();
assert_eq!(items.build_ssa.len(), 1);
assert!(items.build_ssa.contains(&Token::new(0x06000001)));
assert!(items.redetect_assembly);
assert!(queue.is_empty());
}
#[test]
fn test_submit_all() {
let queue = WorkQueue::new();
let items = vec![
WorkItem::BuildSsa(vec![Token::new(0x06000001)]),
WorkItem::BuildSsa(vec![Token::new(0x06000002)]),
];
queue.submit_all(items).unwrap();
assert_eq!(queue.len(), 2);
}
#[test]
fn test_drain_empty() {
let queue = WorkQueue::new();
let items = queue.drain();
assert!(items.is_empty());
}
#[test]
fn test_thread_safety() {
let queue = Arc::new(WorkQueue::new());
let mut handles = Vec::new();
for i in 0..4 {
let queue = Arc::clone(&queue);
handles.push(std::thread::spawn(move || {
for j in 0..25 {
queue
.submit(WorkItem::BuildSsa(vec![Token::new(
0x06000000 + i * 100 + j,
)]))
.unwrap();
}
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(queue.len(), 100);
let items = queue.drain();
assert_eq!(items.build_ssa.len(), 100);
assert!(queue.is_empty());
}
#[test]
fn test_is_empty_and_len() {
let queue = WorkQueue::new();
assert!(queue.is_empty());
assert_eq!(queue.len(), 0);
queue
.submit(WorkItem::RedetectMethods(vec![Token::new(0x06000001)]))
.unwrap();
assert!(!queue.is_empty());
assert_eq!(queue.len(), 1);
queue.drain();
assert!(queue.is_empty());
assert_eq!(queue.len(), 0);
}
#[test]
fn test_build_ssa_dedup() {
let queue = WorkQueue::new();
queue
.submit(WorkItem::BuildSsa(vec![
Token::new(0x06000001),
Token::new(0x06000002),
]))
.unwrap();
queue
.submit(WorkItem::BuildSsa(vec![
Token::new(0x06000002),
Token::new(0x06000003),
]))
.unwrap();
queue
.submit(WorkItem::BuildSsa(vec![Token::new(0x06000001)]))
.unwrap();
let items = queue.drain();
assert_eq!(items.build_ssa.len(), 3);
assert!(items.build_ssa.contains(&Token::new(0x06000001)));
assert!(items.build_ssa.contains(&Token::new(0x06000002)));
assert!(items.build_ssa.contains(&Token::new(0x06000003)));
}
#[test]
fn test_inject_ssa_conflict() {
let queue = WorkQueue::new();
let token = Token::new(0x06000001);
queue
.submit(WorkItem::InjectSsa {
token,
function: Box::new(SsaFunction::new(0, 0)),
})
.unwrap();
let result = queue.submit(WorkItem::InjectSsa {
token,
function: Box::new(SsaFunction::new(0, 0)),
});
assert!(result.is_err());
let items = queue.drain();
assert_eq!(items.inject_ssa.len(), 1);
assert_eq!(items.inject_ssa[0].0, token);
}
#[test]
fn test_redetect_methods_dedup() {
let queue = WorkQueue::new();
queue
.submit(WorkItem::RedetectMethods(vec![
Token::new(0x06000001),
Token::new(0x06000002),
]))
.unwrap();
queue
.submit(WorkItem::RedetectMethods(vec![
Token::new(0x06000002),
Token::new(0x06000003),
]))
.unwrap();
let items = queue.drain();
assert_eq!(items.redetect_methods.len(), 3);
}
#[test]
fn test_redetect_types_dedup() {
let queue = WorkQueue::new();
queue
.submit(WorkItem::RedetectTypes(vec![
Token::new(0x02000001),
Token::new(0x02000002),
]))
.unwrap();
queue
.submit(WorkItem::RedetectTypes(vec![Token::new(0x02000002)]))
.unwrap();
let items = queue.drain();
assert_eq!(items.redetect_types.len(), 2);
}
#[test]
fn test_redetect_assembly_flag() {
let queue = WorkQueue::new();
queue.submit(WorkItem::RedetectAssembly).unwrap();
queue.submit(WorkItem::RedetectAssembly).unwrap();
assert_eq!(queue.len(), 1);
let items = queue.drain();
assert!(items.redetect_assembly);
assert!(queue.is_empty());
let items2 = queue.drain();
assert!(!items2.redetect_assembly);
}
#[test]
fn test_drained_work_items_is_empty() {
let queue = WorkQueue::new();
let items = queue.drain();
assert!(items.is_empty());
queue
.submit(WorkItem::BuildSsa(vec![Token::new(0x06000001)]))
.unwrap();
let items = queue.drain();
assert!(!items.is_empty());
}
#[test]
fn test_drained_work_items_has_redetect() {
let queue = WorkQueue::new();
queue
.submit(WorkItem::BuildSsa(vec![Token::new(0x06000001)]))
.unwrap();
let items = queue.drain();
assert!(!items.has_redetect());
queue
.submit(WorkItem::RedetectMethods(vec![Token::new(0x06000001)]))
.unwrap();
let items = queue.drain();
assert!(items.has_redetect());
queue
.submit(WorkItem::RedetectTypes(vec![Token::new(0x02000001)]))
.unwrap();
let items = queue.drain();
assert!(items.has_redetect());
queue.submit(WorkItem::RedetectAssembly).unwrap();
let items = queue.drain();
assert!(items.has_redetect());
}
}