李恒道 发表于 2022-10-9 23:19:22

JS混淆分析AST实战还原字符串初体验

# 前言
因为刚入门js逆向
所以可能存在一些比较无语的操作
请见谅
# 开始
我们依然以
https://bbs.tampermonkey.net.cn/thread-3395-1-1.html最简单的为例
这节课我们尝试使用AST还原代码
# AST是什么
在我们写出源代码之后
会经过词法分析,语法分析,生成一个AST树,最后由编译器编译出二进制程序,或由解释器进行运行
可以看出AST是计算机阅读源代码的语言
因为我们使用源代码进行混淆还原是极其蛋疼的一件事(也可以)
所以我们使用AST从计算机的角度来进行代码还原
使用的插件是babel
用了@babel/traverse、@babel/generator、@babel/types、@babel/parser
parser用于解析源代码到ast
generator用于从ast生成到代码
traverse用于遍历ast函数
types用于对对应的ast来进行判断以及生成ast节点
在nodejs下安装一下
npm i @babel/parser
npm i @babel/types
npm i @babel/traverse
npm i @babel/generator
然后写代码引入
```
const fs = require("fs");
const parser = require("@babel/parser");
const types = require("@babel/types");
const generator = require("@babel/generator").default;
const traverse = require("@babel/traverse").default;
```
我将混淆代码放到了fuck.js文件里,读入并且生成AST树
```
const fuck_js = fs.readFileSync("./fuck.js", {
encoding: "utf-8",
});
let ast = parser.parse(fuck_js);
```
我们先到Ast Exploer观察一下
发现_0x43f9函数解析成AST后是一个CallExpression节点
名字在CallExpression节点下的callee的name上
![图片.png](data/attachment/forum/202210/09/230106km9pdclm9h2iihvp.png)
而传入的两个参数在arguments上
![图片.png](data/attachment/forum/202210/09/230158najv4mmlvotjasys.png)
我们酝酿一下,首先找到_0x43f9函数,然后提取出来参数,最后替换回去
理论建立完毕,实战开始
traverse传入两个参数,一个是ast树,另一个是在ast由traverse遍历节点的时候,一旦发现第二个参数的对象里有对应的节点名称,就会进行回调,传给你一个path参数,path相当于两个节点之间的连接点,我们可以通过parent.node拿到连接点下方的函数,也可以随意切断下方的节点,更换为其他节点等操作。
我们通过path.node.callee.name拿到函数名字,判断是否等于_0x43f9
然后用path.node.arguments读取所有参数,并且放到数组里
```javascript
const fuck_js = fs.readFileSync("./fuck.js", {
encoding: "utf-8",
});
let ast = parser.parse(fuck_js);
traverse(ast, {
CallExpression(path) {
    if (path.node.callee.name === "_0x43f9") {
      let arg = [];
      path.node.arguments.forEach((item) => {
      arg.push(item.value);
      });
      console.log(arg);
    }
},
});
```
可以看到拿到了数组
![图片.png](data/attachment/forum/202210/09/231435ygzr3zuexen0n2tv.png)
然后我们的目标就变成了替换函数成字符串了
我们把原来的函数抽到nodejs里,直接在index.js里调用
因为他判断了window变量,所以我也跟随声明了一个windows变量
```javascript
let window = {};
let __0xecbce = [
"w7Q+w6jDqcKnMMOewpgF",
"TsOTIcKVw4IjwpsGc2NVQR/CtQ==",
"5YiH6ZmZ54uo5pyg5YyF772HfcOr5L+Z5a6R5p+y5byO56ms",
"w4I7Ohwo",
"dXkZw4kL",
"wrnClsKNIsKX",
"54if5p6c5Y6v772NCGDkv7rlr6zmnYXlvqPnq4nvvaPov4Hor6Xmla3mjIXmi6jkuaTnm7Dlt4zkv4o=",
"wpk8WMOuQA==",
"wqojw6I=",
];
(function (_0x4a174f, _0x2b3ed7) {
var _0x51adc6 = function (_0x4c4f72) {
    while (--_0x4c4f72) {
      _0x4a174f["push"](_0x4a174f["shift"]());
    }
};
_0x51adc6(++_0x2b3ed7);
})(__0xecbce, 0x155);
var _0x43f9 = function (_0x15b28a, _0x370d4f) {
_0x15b28a = _0x15b28a - 0x0;
var _0x958f5c = __0xecbce;
if (_0x43f9["initialized"] === undefined) {
    (function () {
      var _0x375bef =
      typeof window !== "undefined"
          ? window
          : typeof process === "object" &&
            typeof require === "function" &&
            typeof global === "object"
          ? global
          : this;
      var _0x36add6 =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
      _0x375bef["atob"] ||
      (_0x375bef["atob"] = function (_0x2b8d70) {
          var _0x786ab7 = String(_0x2b8d70)["replace"](/=+$/, "");
          for (
            var _0x2efd19 = 0x0,
            _0x5032a4,
            _0x1a3fe4,
            _0x5cb9a4 = 0x0,
            _0x3d9d3c = "";
            (_0x1a3fe4 = _0x786ab7["charAt"](_0x5cb9a4++));
            ~_0x1a3fe4 &&
            ((_0x5032a4 =
            _0x2efd19 % 0x4 ? _0x5032a4 * 0x40 + _0x1a3fe4 : _0x1a3fe4),
            _0x2efd19++ % 0x4)
            ? (_0x3d9d3c += String["fromCharCode"](
                  0xff & (_0x5032a4 >> ((-0x2 * _0x2efd19) & 0x6))
                ))
            : 0x0
          ) {
            _0x1a3fe4 = _0x36add6["indexOf"](_0x1a3fe4);
          }
          return _0x3d9d3c;
      });
    })();
    var _0x4d9f1d = function (_0x11262d, _0x148d0f) {
      var _0x31b35b = [],
      _0x2d6c29 = 0x0,
      _0x24b4f5,
      _0x196a93 = "",
      _0x56258a = "";
      _0x11262d = atob(_0x11262d);
      for (
      var _0x3d7663 = 0x0, _0x34f9e9 = _0x11262d["length"];
      _0x3d7663 < _0x34f9e9;
      _0x3d7663++
      ) {
      _0x56258a +=
          "%" +
          ("00" + _0x11262d["charCodeAt"](_0x3d7663)["toString"](0x10))[
            "slice"
          ](-0x2);
      }
      _0x11262d = decodeURIComponent(_0x56258a);
      for (var _0x48101c = 0x0; _0x48101c < 0x100; _0x48101c++) {
      _0x31b35b = _0x48101c;
      }
      for (_0x48101c = 0x0; _0x48101c < 0x100; _0x48101c++) {
      _0x2d6c29 =
          (_0x2d6c29 +
            _0x31b35b +
            _0x148d0f["charCodeAt"](_0x48101c % _0x148d0f["length"])) %
          0x100;
      _0x24b4f5 = _0x31b35b;
      _0x31b35b = _0x31b35b;
      _0x31b35b = _0x24b4f5;
      }
      _0x48101c = 0x0;
      _0x2d6c29 = 0x0;
      for (var _0x206f10 = 0x0; _0x206f10 < _0x11262d["length"]; _0x206f10++) {
      _0x48101c = (_0x48101c + 0x1) % 0x100;
      _0x2d6c29 = (_0x2d6c29 + _0x31b35b) % 0x100;
      _0x24b4f5 = _0x31b35b;
      _0x31b35b = _0x31b35b;
      _0x31b35b = _0x24b4f5;
      _0x196a93 += String["fromCharCode"](
          _0x11262d["charCodeAt"](_0x206f10) ^
            _0x31b35b[(_0x31b35b + _0x31b35b) % 0x100]
      );
      }
      return _0x196a93;
    };
    _0x43f9["rc4"] = _0x4d9f1d;
    _0x43f9["data"] = {};
    _0x43f9["initialized"] = !![];
}
var _0x106a87 = _0x43f9["data"];
if (_0x106a87 === undefined) {
    if (_0x43f9["once"] === undefined) {
      _0x43f9["once"] = !![];
    }
    _0x958f5c = _0x43f9["rc4"](_0x958f5c, _0x370d4f);
    _0x43f9["data"] = _0x958f5c;
} else {
    _0x958f5c = _0x106a87;
}
return _0x958f5c;
};
```
然后我们编写一下替换代码
调用let result = _0x43f9(...arg);获取结果
然后使用path.replaceWith(types.valueToNode(result));替换path下方的ast节点
types.valueToNode(result)是生成一个ast节点
而path.replaceWith是将自身的下方节点替换为传入的节点
```javascript
traverse(ast, {
CallExpression(path) {
    if (path.node.callee.name === "_0x43f9") {
      let arg = [];
      path.node.arguments.forEach((item) => {
      arg.push(item.value);
      });
      console.log(arg)
      let result = _0x43f9(...arg);
      path.replaceWith(types.valueToNode(result));
    }
},
});
```
然后我们生成代码看看
```javascript
let code = generator(ast, ).code;
fs.writeFile("./convert.js", code, (err) => {});
```
![图片.png](data/attachment/forum/202210/09/231808etbqvq1v1qubu1vv.png)
可以看到成功了,但是还有\u这种编码,可以改一下ast的输出参数
```javascript
let code = generator(ast, {
jsescOption: { minimal: true },
}).code;
fs.writeFile("./convert.js", code, (err) => {});
```
发现编码转换成功了~~~~
![图片.png](data/attachment/forum/202210/09/231858zns86npvv38kf0ta.png)
# 结语
撒花~

maxzhang 发表于 2022-10-10 10:11:52

路还很长

函数调用还没还原啊{:4_109:}

李恒道 发表于 2022-10-10 10:18:08

maxzhang 发表于 2022-10-10 10:11
路还很长

函数调用还没还原啊

{:4_98:}慢慢来嘛
等我从涛那骗代码!

王一之 发表于 2022-10-10 13:59:18

看不懂了 呜呜{:4_115:}

李恒道 发表于 2022-10-10 18:17:36

王一之 发表于 2022-10-10 13:59
看不懂了 呜呜

哪里不懂
我手把手教♂你

tansuo 发表于 2023-12-9 12:44:53

Ast Exploer是个工具吗?如果是如何下载

李恒道 发表于 2023-12-9 13:03:08

tansuo 发表于 2023-12-9 12:44
Ast Exploer是个工具吗?如果是如何下载

https://astexplorer.net/
页: [1]
查看完整版本: JS混淆分析AST实战还原字符串初体验