js_deobfuscator/core/
engine.rs1use std::time::{Duration, Instant};
4
5use oxc::allocator::Allocator;
6use oxc::ast::ast::Program;
7use oxc::semantic::SemanticBuilder;
8use oxc_traverse::traverse_mut;
9
10use super::config::EngineConfig;
11use super::error::{DeobError, PassError};
12use crate::ecma::EcmaTransformer;
13use crate::extensions::string_rotator::StringRotator;
14use crate::runtime::RuntimeTransformer;
15
16#[derive(Debug)]
22pub struct EngineResult {
23 pub iterations: usize,
25
26 pub total_modifications: usize,
28
29 pub modifications_per_iteration: Vec<usize>,
31
32 pub converged: bool,
34
35 pub elapsed: Duration,
37
38 pub errors: Vec<PassError>,
40}
41
42impl EngineResult {
43 pub fn is_success(&self) -> bool {
45 self.converged
46 }
47}
48
49#[derive(Default)]
81pub struct Engine {
82 config: EngineConfig,
83}
84
85impl Engine {
86 pub fn new() -> Self {
88 Self::default()
89 }
90
91 pub fn with_config(config: EngineConfig) -> Self {
93 Self { config }
94 }
95
96 pub fn config(&self) -> &EngineConfig {
98 &self.config
99 }
100
101 #[tracing::instrument(
103 skip(self, allocator, program),
104 fields(max_iterations = self.config.max_iterations)
105 )]
106 pub fn run<'a>(
107 &self,
108 allocator: &'a Allocator,
109 program: &mut Program<'a>,
110 ) -> Result<EngineResult, DeobError> {
111 let start = Instant::now();
112 let mut total_modifications = 0;
113 let mut modifications_per_iteration = Vec::new();
114 let mut converged = false;
115 let errors = Vec::new();
116
117 tracing::info!(target: "deob::engine", "starting deobfuscation");
118
119 for iteration in 0..self.config.max_iterations {
120 let iter_span = tracing::info_span!(target: "deob::engine", "iteration", n = iteration);
121 let _guard = iter_span.enter();
122
123 let mut iter_mods = 0;
124
125 if self.config.layers.extensions && self.config.layers.extensions_config.string_array {
127 match StringRotator::transform(allocator, program) {
128 Ok(result) => {
129 let ext_mods = result.strings_decoded + result.functions_removed;
130 tracing::debug!(
131 target: "deob::engine",
132 systems = result.systems_detected,
133 decoded = result.strings_decoded,
134 inlined = result.calls_inlined,
135 removed = result.functions_removed,
136 "Extensions layer complete"
137 );
138 iter_mods += ext_mods;
139 }
140 Err(e) => {
141 tracing::warn!(target: "deob::engine", error = %e, "Extensions layer error");
142 }
143 }
144 }
145
146 tracing::debug!(target: "deob::engine", "building scoping");
148 let semantic = SemanticBuilder::new().build(program).semantic;
149 let scoping = semantic.into_scoping();
150
151 if self.config.layers.ecma {
153 let mut ecma = EcmaTransformer::new(&self.config.layers.ecma_config);
154 traverse_mut(&mut ecma, allocator, program, scoping, ());
155 let ecma_mods = ecma.modifications();
156 tracing::debug!(target: "deob::engine", ecma_mods, "ECMA layer complete");
157 iter_mods += ecma_mods;
158 }
159
160 let semantic = SemanticBuilder::new().build(program).semantic;
162 let scoping = semantic.into_scoping();
163
164 if self.config.layers.runtime {
166 let mut runtime = RuntimeTransformer::new(&self.config.layers.runtime_config);
167 traverse_mut(&mut runtime, allocator, program, scoping, ());
168 let runtime_mods = runtime.modifications();
169 tracing::debug!(target: "deob::engine", runtime_mods, "Runtime layer complete");
170 iter_mods += runtime_mods;
171 }
172
173 tracing::info!(
174 target: "deob::engine",
175 modifications = iter_mods,
176 "iteration complete"
177 );
178
179 total_modifications += iter_mods;
180 modifications_per_iteration.push(iter_mods);
181
182 if iter_mods == 0 {
184 tracing::info!(target: "deob::engine", iterations = iteration + 1, "converged");
185 converged = true;
186 break;
187 }
188 }
189
190 if !converged {
191 tracing::warn!(
192 target: "deob::engine",
193 max = self.config.max_iterations,
194 "max iterations reached without convergence"
195 );
196 }
197
198 let elapsed = start.elapsed();
199 tracing::info!(
200 target: "deob::engine",
201 total_modifications,
202 iterations = modifications_per_iteration.len(),
203 elapsed_ms = elapsed.as_millis(),
204 "deobfuscation complete"
205 );
206
207 Ok(EngineResult {
208 iterations: modifications_per_iteration.len(),
209 total_modifications,
210 modifications_per_iteration,
211 converged,
212 elapsed,
213 errors,
214 })
215 }
216}