动态

详情 返回 返回

用Babel操作AST實現JavaScript代碼的自動化生成與轉換 - 动态 详情

目錄

  • 目錄
  • 環境搭建
  • 代碼:修改AST的邏輯

    • 重命名函數名
    • 重命名變量並修改變量值
    • 函數調用替換
    • 控制流扁平化還原
    • 刪除未使用的變量
    • 對象屬性簡化
    • 條件表達式優化
    • 表達式還原

環境搭建

  1. 安裝環境

    npm install @babel/parser @babel/traverse @babel/generator @babel/types
  2. 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的邏輯

重命名函數名

  1. 案例

    function _0x1a2b(s) {
     return atob(s);
    }
    console.log(_0x1a2b("SGVsbG8=")); // 輸出 "Hello"
  2. 結果

    function decryptString(s) {
      return atob(s);
    }
    console.log(_0x1a2b("SGVsbG8=")); // 輸出 "Hello"
  3. 代碼實現:

    //重命名加密函數
     FunctionDeclaration(path) {
         if (path.node.id.name==='_0x1a2b') {
             path.node.id.name='decryptString';
         }
     }

重命名變量並修改變量值

  1. 案例

    function _0x12ab() {
      const _0x3cde = ["\\x48\\x65\\x6c\\x6c\\x6f", "\\x77\\x6f\\x72\\x6c\\x64"];
      return _0x3cde[0] + " " + _0x3cde[1];
    }
  2. 結果

    function _0x12ab() {
      const words = ["Hello", "world"];
      return words[0] + " " + words[1];
    }
  3. 代碼實現:

    //重命名變量並解密字符串
     //變量聲明節點
     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';
         }
     }

函數調用替換

  1. 案例

    function _0x1a2b(s) {
     return atob(s);
    }
    console.log(_0x1a2b("SGVsbG8=")); // 輸出 "Hello"
  2. 結果

    function _0x1a2b(s) {
      return atob(s);
    }
    console.log("Hello"); // 輸出 "Hello"
  3. 代碼實現:

     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
             });
    
         }
     }

控制流扁平化還原

  1. 案例

    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;
      }
    }
  2. 結果

    function _0x1234() {
      console.log("Hello");
      console.log("world");
      console.log("!");
    }
  3. AST轉換邏輯
    · 識別switch-case結構:找到SwitchStatement節點
    · 提取case順序:通過_0x5678數組的初始值確定執行順序(本例順序為2→0→1)
    · 重建代碼順序:按順序合併case塊中的語句,刪除switch和while結構
  4. 代碼實現:

     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);
     }

    刪除未使用的變量

  5. 案例

    // 原始代碼
    const unusedVar = "test"; // 無任何地方使用
    const activeVar = "data";
    console.log(activeVar);
  6. 結果

    const activeVar = "data";
    console.log(activeVar);
  7. 代碼實現:

     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
             }
         }
     }

對象屬性簡化

  1. 案例

    const _0xabc = {
      "xYz": function(s) { return s.toUpperCase(); }
    };
    console.log(_0xabc["xYz"]("hello")); // 輸出 "HELLO"
  2. 結果

    const _0xabc = {
      upper: function (s) {
     return s.toUpperCase();
      }
    };
    console.log(utils.upper("hello")); // 輸出 "HELLO"
  3. AST轉換邏輯

    1. 識別對象屬性:找到ObjectProperty節點中的動態鍵(如"xYz")
    2. 重命名屬性和調用方式:將_0xabc["xYz"]改為utils.upper
  4. 代碼實現:

     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; // 改為.訪問方式
         }
     }

條件表達式優化

  1. 案例

    const isVIP = !![]; // 混淆寫法:!![] → true
    console.log(isVIP ? "VIP User" : "Guest");
  2. 結果

    const isVIP = true; // 混淆寫法:!![] → true
    console.log("VIP User");
  3. AST轉換邏輯
    · 計算常量表達式:在AST遍歷階段預計算!![]的值
    · 刪除無效分支:根據計算結果刪除false分支
  4. 代碼實現:

     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
             );
         }
     }

表達式還原

  1. 案例

    var e=840;
    e= e - (-0x2 * 0x7d6 + -0xd1 * 0x14 + 0x210e);
  2. 結果

    var e = 840;
    e = e - 270;
  3. 代碼實現:

    // 靜態計算表達式值
    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));
         }
    }

Add a new 评论

Some HTML is okay.