js_deobfuscator/core/
deobfuscator.rs1use oxc::allocator::Allocator;
16use oxc::codegen::Codegen;
17use oxc::parser::Parser;
18use oxc::span::SourceType;
19
20use super::config::EngineConfig;
21use super::engine::Engine;
22use super::error::DeobError;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum Extension {
31 StringRotator,
33 ControlFlow,
35 Proxy,
37}
38
39#[derive(Debug)]
45pub struct DeobfuscateResult {
46 pub code: String,
48 pub iterations: usize,
50 pub modifications: usize,
52 pub converged: bool,
54}
55
56#[derive(Debug, Clone)]
85pub struct JSDeobfuscator {
86 config: EngineConfig,
87}
88
89impl Default for JSDeobfuscator {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl JSDeobfuscator {
96 pub fn new() -> Self {
98 Self {
99 config: EngineConfig::standard(),
100 }
101 }
102
103 #[must_use]
105 pub fn max_iterations(mut self, max: usize) -> Self {
106 self.config.max_iterations = max;
107 self
108 }
109
110 #[must_use]
112 pub fn ecma(mut self, enabled: bool) -> Self {
113 self.config.layers.ecma = enabled;
114 self
115 }
116
117 #[must_use]
119 pub fn runtime(mut self, enabled: bool) -> Self {
120 self.config.layers.runtime = enabled;
121 self
122 }
123
124 #[must_use]
131 pub fn extensions<I>(mut self, exts: I) -> Self
132 where
133 I: IntoIterator<Item = Extension>,
134 {
135 self.config.layers.extensions_config.string_array = false;
137 self.config.layers.extensions_config.control_flow = false;
138 self.config.layers.extensions_config.proxy = false;
139
140 for ext in exts {
141 match ext {
142 Extension::StringRotator => {
143 self.config.layers.extensions_config.string_array = true;
144 }
145 Extension::ControlFlow => {
146 self.config.layers.extensions_config.control_flow = true;
147 }
148 Extension::Proxy => {
149 self.config.layers.extensions_config.proxy = true;
150 }
151 }
152 }
153
154 self.config.layers.extensions = self.config.layers.extensions_config.string_array
156 || self.config.layers.extensions_config.control_flow
157 || self.config.layers.extensions_config.proxy;
158
159 self
160 }
161
162 pub fn deobfuscate(&self, source: &str) -> Result<String, DeobError> {
164 Ok(self.deobfuscate_with_result(source)?.code)
165 }
166
167 pub fn deobfuscate_with_result(&self, source: &str) -> Result<DeobfuscateResult, DeobError> {
169 let allocator = Allocator::default();
170 let ret = Parser::new(&allocator, source, SourceType::mjs()).parse();
171
172 if !ret.errors.is_empty() {
173 return Err(DeobError::Parse(
174 ret.errors
175 .iter()
176 .map(|e| e.to_string())
177 .collect::<Vec<_>>()
178 .join("; "),
179 ));
180 }
181
182 let mut program = ret.program;
183 let engine = Engine::with_config(self.config.clone());
184 let result = engine.run(&allocator, &mut program)?;
185
186 Ok(DeobfuscateResult {
187 code: Codegen::new().build(&program).code,
188 iterations: result.iterations,
189 modifications: result.total_modifications,
190 converged: result.converged,
191 })
192 }
193}
194
195pub fn deobfuscate(source: &str) -> Result<String, DeobError> {
201 JSDeobfuscator::new().deobfuscate(source)
202}
203
204pub fn deobfuscate_full(source: &str) -> Result<String, DeobError> {
206 JSDeobfuscator::new()
207 .extensions([Extension::StringRotator, Extension::ControlFlow, Extension::Proxy])
208 .deobfuscate(source)
209}
210
211#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_simple() {
221 let result = deobfuscate("var a = 1 + 2;").unwrap();
222 assert!(result.contains("3"));
223 }
224
225 #[test]
226 fn test_builder() {
227 let result = JSDeobfuscator::new()
228 .ecma(true)
229 .runtime(true)
230 .max_iterations(10)
231 .deobfuscate("var a = 1 + 2;")
232 .unwrap();
233 assert!(result.contains("3"));
234 }
235
236 #[test]
237 fn test_extensions() {
238 let result = JSDeobfuscator::new()
239 .extensions([Extension::StringRotator])
240 .deobfuscate("var a = 1 + 2;")
241 .unwrap();
242 assert!(result.contains("3"));
243 }
244}