js_deobfuscator/transform/
global.rs1use oxc::allocator::Allocator;
7use oxc::ast::ast::{Expression, Program};
8use oxc::semantic::Scoping;
9
10use oxc_traverse::{Traverse, TraverseCtx, traverse_mut};
11
12use crate::ast::{create, extract};
13use crate::engine::error::Result;
14use crate::engine::module::{Module, TransformResult};
15use crate::scope::safety;
16use crate::value::JsValue;
17
18pub struct GlobalResolver {
20 globals: std::collections::HashMap<String, serde_json::Value>,
21}
22
23impl GlobalResolver {
24 pub fn new(globals: std::collections::HashMap<String, serde_json::Value>) -> Self {
25 Self { globals }
26 }
27}
28
29impl Module for GlobalResolver {
30 fn name(&self) -> &'static str {
31 "GlobalResolver"
32 }
33
34 fn transform<'a>(
35 &mut self,
36 allocator: &'a Allocator,
37 program: &mut Program<'a>,
38 scoping: Scoping,
39 ) -> Result<TransformResult> {
40 if self.globals.is_empty() {
41 return Ok(TransformResult { modifications: 0, scoping });
42 }
43 let mut visitor = GlobalVisitor { globals: &self.globals, modifications: 0 };
44 let scoping = traverse_mut(&mut visitor, allocator, program, scoping, ());
45 Ok(TransformResult { modifications: visitor.modifications, scoping })
46 }
47}
48
49struct GlobalVisitor<'g> {
50 globals: &'g std::collections::HashMap<String, serde_json::Value>,
51 modifications: usize,
52}
53
54impl<'a, 'g> Traverse<'a, ()> for GlobalVisitor<'g> {
55 fn exit_expression(
56 &mut self,
57 expr: &mut Expression<'a>,
58 ctx: &mut TraverseCtx<'a, ()>,
59 ) {
60 if let Expression::Identifier(ident) = &*expr {
62 if !safety::is_global(ctx.scoping(), ident) { return; }
63 let name = ident.name.as_str();
64 if let Some(val) = self.globals.get(name) {
65 if let Some(js_val) = json_to_jsvalue(val) {
66 *expr = create::from_js_value(&js_val, &ctx.ast);
67 self.modifications += 1;
68 return;
69 }
70 }
71 }
72
73 if let Expression::StaticMemberExpression(_) = &*expr {
75 if let Some(val) = resolve_member_chain(expr, ctx.scoping(), self.globals) {
76 *expr = create::from_js_value(&val, &ctx.ast);
77 self.modifications += 1;
78 }
79 }
80 }
81}
82
83fn resolve_member_chain(
85 expr: &Expression,
86 scoping: &Scoping,
87 globals: &std::collections::HashMap<String, serde_json::Value>,
88) -> Option<JsValue> {
89 let mut chain = Vec::new();
91 let mut current = expr;
92
93 loop {
94 match current {
95 Expression::StaticMemberExpression(m) => {
96 chain.push(m.property.name.as_str());
97 current = &m.object;
98 }
99 Expression::ComputedMemberExpression(m) => {
100 let key = extract::string(&m.expression)?;
101 chain.push(key);
102 current = &m.object;
103 }
104 Expression::Identifier(ident) => {
105 if !safety::is_global(scoping, ident) { return None; }
106 chain.push(ident.name.as_str());
107 break;
108 }
109 _ => return None,
110 }
111 }
112
113 chain.reverse();
115
116 let root_name = chain.first()?;
118 let mut val = globals.get(*root_name)?;
119
120 for key in &chain[1..] {
121 val = val.get(key)?;
122 }
123
124 json_to_jsvalue(val)
125}
126
127fn json_to_jsvalue(val: &serde_json::Value) -> Option<JsValue> {
128 match val {
129 serde_json::Value::Number(n) => n.as_f64().map(JsValue::Number),
130 serde_json::Value::String(s) => Some(JsValue::String(s.clone())),
131 serde_json::Value::Bool(b) => Some(JsValue::Boolean(*b)),
132 serde_json::Value::Null => Some(JsValue::Null),
133 _ => None, }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use oxc::codegen::Codegen;
141 use oxc::parser::Parser;
142 use oxc::semantic::SemanticBuilder;
143 use oxc::span::SourceType;
144
145 fn resolve_globals(source: &str, globals: serde_json::Value) -> (String, usize) {
146 let allocator = Allocator::default();
147 let mut program = Parser::new(&allocator, source, SourceType::mjs()).parse().program;
148 let scoping = SemanticBuilder::new().build(&program).semantic.into_scoping();
149
150 let map: std::collections::HashMap<String, serde_json::Value> = globals
151 .as_object()
152 .unwrap()
153 .iter()
154 .map(|(k, v)| (k.clone(), v.clone()))
155 .collect();
156
157 let mut module = GlobalResolver::new(map);
158 let result = module.transform(&allocator, &mut program, scoping).unwrap();
159 (Codegen::new().build(&program).code, result.modifications)
160 }
161
162 #[test]
163 fn test_simple_global() {
164 let (code, mods) = resolve_globals(
165 "console.log(SECRET);",
166 serde_json::json!({"SECRET": "abc123"}),
167 );
168 assert!(mods > 0);
169 assert!(code.contains("\"abc123\""), "got: {code}");
170 }
171
172 #[test]
173 fn test_nested_property() {
174 let (code, mods) = resolve_globals(
175 "console.log(window.config.key);",
176 serde_json::json!({"window": {"config": {"key": "value123"}}}),
177 );
178 assert!(mods > 0);
179 assert!(code.contains("\"value123\""), "got: {code}");
180 }
181
182 #[test]
183 fn test_no_resolve_declared() {
184 let (_, mods) = resolve_globals(
186 "var x = 1; console.log(x);",
187 serde_json::json!({"x": 999}),
188 );
189 assert_eq!(mods, 0, "should not resolve locally declared var");
190 }
191
192 #[test]
193 fn test_number_global() {
194 let (code, mods) = resolve_globals(
195 "console.log(TIMEOUT);",
196 serde_json::json!({"TIMEOUT": 5000}),
197 );
198 assert!(mods > 0);
199 assert!(code.contains("5e3") || code.contains("5000"), "got: {code}");
200 }
201}