use std::{
path::PathBuf,
sync::atomic::{AtomicBool, Ordering},
thread,
};
use clap::Parser;
use dotscope::{
deobfuscation::{DeobfuscationEngine, DeobfuscationResult, EngineConfig},
emulation::TracingConfig,
CilObject,
};
#[derive(Parser)]
#[command(name = "deobfuscate")]
#[command(about = "Deobfuscate .NET assemblies protected by known obfuscators", long_about = None)]
struct Cli {
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
aggressive: bool,
#[arg(long, default_value = "100")]
max_iterations: usize,
#[arg(long)]
trace: Option<PathBuf>,
}
static ABORT: AtomicBool = AtomicBool::new(false);
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
ctrlc::set_handler(|| {
eprintln!("\nAborting...");
ABORT.store(true, Ordering::SeqCst);
std::thread::sleep(std::time::Duration::from_millis(500));
std::process::exit(130);
})?;
eprintln!("Loading: {}", cli.input.display());
let output_path = cli.output.unwrap_or_else(|| {
let stem = cli.input.file_stem().unwrap_or_default().to_string_lossy();
let ext = cli.input.extension().unwrap_or_default().to_string_lossy();
cli.input
.with_file_name(format!("{}_deobfuscated.{}", stem, ext))
});
let input_path = cli.input.clone();
let max_iterations = cli.max_iterations;
let verbose = cli.verbose;
let aggressive = cli.aggressive;
let trace_path = cli.trace.clone();
let handle = thread::spawn(
move || -> Result<(CilObject, DeobfuscationResult), dotscope::Error> {
let mut config = if aggressive {
let mut c = EngineConfig::aggressive();
c.iterations.max_ssa_iterations = max_iterations;
c
} else {
let mut c = EngineConfig::default();
c.iterations.max_ssa_iterations = max_iterations;
c
};
if let Some(path) = trace_path {
config.emulation.tracing = Some(TracingConfig::full_trace(path));
}
let engine = DeobfuscationEngine::new(config);
engine.process_file(&input_path)
},
);
let result = loop {
if ABORT.load(Ordering::SeqCst) {
eprintln!("Deobfuscation aborted by user.");
std::process::exit(130);
}
if handle.is_finished() {
break handle.join().expect("Deobfuscation thread panicked");
}
thread::sleep(std::time::Duration::from_millis(100));
};
let (deobfuscated, result) = result?;
eprintln!();
eprintln!("=== Detection ===");
if let Some(ref attr) = result.attribution {
eprintln!(
"{}: {} techniques, {} supporting",
attr.obfuscator_name,
attr.technique_ids.len(),
attr.supporting_matched
);
if verbose {
for id in &attr.technique_ids {
eprintln!(" - technique: {}", id);
}
}
}
eprintln!();
eprintln!("=== Results ===");
eprintln!("{}", result.detailed_summary());
eprintln!();
eprintln!("=== Output ===");
let output_bytes = deobfuscated.file().data();
std::fs::write(&output_path, output_bytes)?;
eprintln!(
"Saved: {} ({} bytes)",
output_path.display(),
output_bytes.len()
);
Ok(())
}