目錄
- 目錄
- 環境搭建
-
代碼:修改AST的邏輯
- 重命名函數名
- 重命名變量並修改變量值
- 函數調用替換
- 控制流扁平化還原
- 刪除未使用的變量
- 對象屬性簡化
- 條件表達式優化
- 表達式還原
環境搭建
-
安裝環境
npm install @babel/parser @babel/traverse @babel/generator @babel/types -
ast轉換的代碼框架
const fs = require('fs'); const vm = require('node:vm'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const t = require('@babel/types'); const generator = require('@babel/generator').default; // 讀取混淆代碼文件 const code = fs.readFileSync('obfuscated.js', 'utf-8'); // 解析為AST const ast = parser.parse(code); // TODO 修改AST的邏輯將在這裏編寫 // 生成新代碼 const output = generator(ast); fs.writeFileSync('clean.js', output.code);
代碼:修改AST的邏輯
重命名函數名
-
案例
function _0x1a2b(s) { return atob(s); } console.log(_0x1a2b("SGVsbG8=")); // 輸出 "Hello" -
結果
function decryptString(s) { return atob(s); } console.log(_0x1a2b("SGVsbG8=")); // 輸出 "Hello" -
代碼實現:
//重命名加密函數 FunctionDeclaration(path) { if (path.node.id.name==='_0x1a2b') { path.node.id.name='decryptString'; } }
重命名變量並修改變量值
-
案例
function _0x12ab() { const _0x3cde = ["\\x48\\x65\\x6c\\x6c\\x6f", "\\x77\\x6f\\x72\\x6c\\x64"]; return _0x3cde[0] + " " + _0x3cde[1]; } -
結果
function _0x12ab() { const words = ["Hello", "world"]; return words[0] + " " + words[1]; } -
代碼實現:
//重命名變量並解密字符串 //變量聲明節點 VariableDeclarator(path) { if (path.node.id.name === '_0x3cde') { path.node.id.name = 'words'; // 修改變量名 // 遍歷elements數組 path.node.init.elements = path.node.init.elements.map(element => { // 解密十六進制字符串 const decoded = element.value.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)) ); // 關鍵 return { type: 'StringLiteral', value: decoded }; }); } }, //對象成員訪問節點 MemberExpression(path) { if(path.node.object.name==='_0x3cde'){ path.node.object.name='words'; } }
函數調用替換
-
案例
function _0x1a2b(s) { return atob(s); } console.log(_0x1a2b("SGVsbG8=")); // 輸出 "Hello" -
結果
function _0x1a2b(s) { return atob(s); } console.log("Hello"); // 輸出 "Hello" -
代碼實現:
CallExpression(path) { if (path.node.callee.name==='_0x1a2b'&&path.node.arguments[0].type==='StringLiteral') { //取出參數 const encryptedStr=path.node.arguments[0].value; // 對參數進行解密 const decryptedStr=atob(encryptedStr); // 把原來的參數調用_0x1a2b("SGVsbG8="),替換為decryptedStr,即對atob(encryptedStr) path.replaceWith({ type:'StringLiteral', value:decryptedStr }); } }
控制流扁平化還原
-
案例
function _0x1234() { const _0x5678 = [2, 0, 1]; while (true) { switch (_0x5678.shift()) { case 0: console.log("world"); continue; case 1: console.log("!"); continue; case 2: console.log("Hello"); continue; } break; } } -
結果
function _0x1234() { console.log("Hello"); console.log("world"); console.log("!"); } - AST轉換邏輯
· 識別switch-case結構:找到SwitchStatement節點
· 提取case順序:通過_0x5678數組的初始值確定執行順序(本例順序為2→0→1)
· 重建代碼順序:按順序合併case塊中的語句,刪除switch和while結構 -
代碼實現:
FunctionDeclaration(path) { // 1. 定位控制流數組聲明 const controlFlowDecl=path.node.body.body.find(n=> (t.isVariableDeclaration(n)&& n.declarations[0].id.name==='_0x5678') ); if (!controlFlowDecl) return; // 2. 提取控制流順序 [2, 0, 1] const controlFlowArray = controlFlowDecl.declarations[0].init.elements .map(e=>e.value); // 3. 刪除控制流數組聲明 path.node.body.body=path.node.body.body.filter(n=> n!==controlFlowDecl); // 4. 提取switch語句 const switchStmt = path.node.body.body .find(n => t.isWhileStatement(n)).body.body .find(n => t.isSwitchStatement(n));; // 5. 刪除while語句 path.node.body.body=path.node.body.body.filter(n=>!t.isWhileStatement(n)); // 6. 建立case值到語句的映射 const caseMap=new Map(); switchStmt.cases.forEach(n=>{ caseMap.set(n.test.value,n.consequent); }); // 7. 按控制流順序重組語句 const orderedStatements=[]; for(const caseVal of controlFlowArray){ const stmts=caseMap.get(caseVal) .filter(n=>!t.isContinueStatement(n));// 移除continue orderedStatements.push(...stmts); } // 8. 插入到函數體頭部(保留其他語句) path.node.body.body.unshift(...orderedStatements); }刪除未使用的變量
-
案例
// 原始代碼 const unusedVar = "test"; // 無任何地方使用 const activeVar = "data"; console.log(activeVar); -
結果
const activeVar = "data"; console.log(activeVar); -
代碼實現:
VariableDeclarator(path){ const binding = path.scope.getBinding(path.node.id.name); // 檢測變量是否未被引用 if (!binding || binding.references === 0) { // 刪除整個 VariableDeclaration(需判斷是否最後一個聲明) const parent = path.parent; if (parent.declarations.length === 1) { // 情況1:整個 VariableDeclaration 只有一個聲明 eg: const a = 1; path.parentPath.remove();// 刪除父節點(即整個聲明語句) } else { // 情況2:聲明語句中有多個變量 eg: let a = 1, b = 2; path.remove();// 只刪除當前 VariableDeclarator } } }
對象屬性簡化
-
案例
const _0xabc = { "xYz": function(s) { return s.toUpperCase(); } }; console.log(_0xabc["xYz"]("hello")); // 輸出 "HELLO" -
結果
const _0xabc = { upper: function (s) { return s.toUpperCase(); } }; console.log(utils.upper("hello")); // 輸出 "HELLO" -
AST轉換邏輯
- 識別對象屬性:找到ObjectProperty節點中的動態鍵(如"xYz")
- 重命名屬性和調用方式:將_0xabc["xYz"]改為utils.upper
-
代碼實現:
ObjectProperty(path) { // 重命名鍵名 if (path.node.key.value === "xYz") { path.node.key = t.identifier("upper"); // 改為標識符形式 } }, MemberExpression(path) { // 轉換動態屬性訪問為靜態 if ( t.isIdentifier(path.node.object, { name: "_0xabc" }) && t.isStringLiteral(path.node.property, { value: "xYz" }) ) { path.node.object.name = "utils"; path.node.property = t.identifier("upper"); path.node.computed = false; // 改為.訪問方式 } }
條件表達式優化
-
案例
const isVIP = !![]; // 混淆寫法:!![] → true console.log(isVIP ? "VIP User" : "Guest"); -
結果
const isVIP = true; // 混淆寫法:!![] → true console.log("VIP User"); - AST轉換邏輯
· 計算常量表達式:在AST遍歷階段預計算!![]的值
· 刪除無效分支:根據計算結果刪除false分支 -
代碼實現:
VariableDeclarator(path) { //!![] → true if (t.isUnaryExpression(path.node.init,{ operator: "!" })){ if (t.isUnaryExpression(path.node.init.argument,{ operator: "!" })){ // 3. 檢測最內層是否為空數組 [] const arrayExpr = path.node.init.argument.argument; if(t.isArrayExpression(arrayExpr) && arrayExpr.elements.length === 0){ path.get('init').replaceWith(t.booleanLiteral(true)); } } } }, ConditionalExpression(path) { // 步驟1:檢查 test 是否為變量引用(如 isVIP) if (!t.isIdentifier(path.node.test)) return; const varName = path.node.test.name; // 獲取變量名 'isVIP' // 步驟2:從作用域中獲取變量綁定信息 const binding = path.scope.getBinding(varName); // 步驟3:檢查綁定是否存在且是常量 if (!binding || !binding.constant) return; // 步驟4:找到變量的聲明節點 const declaration = binding.path.node; // 步驟5:提取變量的初始值 if (t.isVariableDeclarator(declaration)){ const initValue = declaration.init.value; // true console.log(`變量 ${varName} 的值為: ${initValue}`); // 步驟6(可選):根據值直接替換條件表達式 path.replaceWith( initValue ? path.node.consequent : path.node.alternate ); } }
表達式還原
-
案例
var e=840; e= e - (-0x2 * 0x7d6 + -0xd1 * 0x14 + 0x210e); -
結果
var e = 840; e = e - 270; -
代碼實現:
// 靜態計算表達式值 function evaluateExpression(node) { if (t.isLiteral(node)) { return node.value; } if (t.isUnaryExpression(node)) { const argValue = evaluateExpression(node.argument); if (typeof argValue === 'number') { return node.operator === '-' ? -argValue : argValue; } } if (t.isBinaryExpression(node)) { const left = evaluateExpression(node.left); const right = evaluateExpression(node.right); if (typeof left === 'number' && typeof right === 'number') { switch (node.operator) { case '+': return left + right; case '-': return left - right; case '*': return left * right; case '/': return left / right; // 可擴展其他運算符 } } } return undefined; // 無法靜態計算 }BinaryExpression(path) { const result = evaluateExpression(path.node); if (typeof result === 'number') { // 替換為計算結果的字面量 path.replaceWith(t.numericLiteral(result)); } }