上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

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

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2022-10-9 23:19:22 | 显示全部楼层 | 阅读模式

    前言

    因为刚入门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
    而传入的两个参数在arguments上
    图片.png
    我们酝酿一下,首先找到_0x43f9函数,然后提取出来参数,最后替换回去
    理论建立完毕,实战开始
    traverse传入两个参数,一个是ast树,另一个是在ast由traverse遍历节点的时候,一旦发现第二个参数的对象里有对应的节点名称,就会进行回调,传给你一个path参数,path相当于两个节点之间的连接点,我们可以通过parent.node拿到连接点下方的函数,也可以随意切断下方的节点,更换为其他节点等操作。
    我们通过path.node.callee.name拿到函数名字,判断是否等于_0x43f9
    然后用path.node.arguments读取所有参数,并且放到数组里

    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
    然后我们的目标就变成了替换函数成字符串了
    我们把原来的函数抽到nodejs里,直接在index.js里调用
    因为他判断了window变量,所以我也跟随声明了一个windows变量

    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[_0x15b28a];
      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] = _0x48101c;
          }
          for (_0x48101c = 0x0; _0x48101c < 0x100; _0x48101c++) {
            _0x2d6c29 =
              (_0x2d6c29 +
                _0x31b35b[_0x48101c] +
                _0x148d0f["charCodeAt"](_0x48101c % _0x148d0f["length"])) %
              0x100;
            _0x24b4f5 = _0x31b35b[_0x48101c];
            _0x31b35b[_0x48101c] = _0x31b35b[_0x2d6c29];
            _0x31b35b[_0x2d6c29] = _0x24b4f5;
          }
          _0x48101c = 0x0;
          _0x2d6c29 = 0x0;
          for (var _0x206f10 = 0x0; _0x206f10 < _0x11262d["length"]; _0x206f10++) {
            _0x48101c = (_0x48101c + 0x1) % 0x100;
            _0x2d6c29 = (_0x2d6c29 + _0x31b35b[_0x48101c]) % 0x100;
            _0x24b4f5 = _0x31b35b[_0x48101c];
            _0x31b35b[_0x48101c] = _0x31b35b[_0x2d6c29];
            _0x31b35b[_0x2d6c29] = _0x24b4f5;
            _0x196a93 += String["fromCharCode"](
              _0x11262d["charCodeAt"](_0x206f10) ^
                _0x31b35b[(_0x31b35b[_0x48101c] + _0x31b35b[_0x2d6c29]) % 0x100]
            );
          }
          return _0x196a93;
        };
        _0x43f9["rc4"] = _0x4d9f1d;
        _0x43f9["data"] = {};
        _0x43f9["initialized"] = !![];
      }
      var _0x106a87 = _0x43f9["data"][_0x15b28a];
      if (_0x106a87 === undefined) {
        if (_0x43f9["once"] === undefined) {
          _0x43f9["once"] = !![];
        }
        _0x958f5c = _0x43f9["rc4"](_0x958f5c, _0x370d4f);
        _0x43f9["data"][_0x15b28a] = _0x958f5c;
      } else {
        _0x958f5c = _0x106a87;
      }
      return _0x958f5c;
    };

    然后我们编写一下替换代码
    调用let result = _0x43f9(...arg);获取结果
    然后使用path.replaceWith(types.valueToNode(result));替换path下方的ast节点
    types.valueToNode(result)是生成一个ast节点
    而path.replaceWith是将自身的下方节点替换为传入的节点

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

    然后我们生成代码看看

    let code = generator(ast, ).code;
    fs.writeFile("./convert.js", code, (err) => {});

    图片.png
    可以看到成功了,但是还有\u这种编码,可以改一下ast的输出参数

    let code = generator(ast, {
      jsescOption: { minimal: true },
    }).code;
    fs.writeFile("./convert.js", code, (err) => {});

    发现编码转换成功了~~~~
    图片.png

    结语

    撒花~

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    奋斗
    2023-6-12 15:07
  • 签到天数: 6 天

    [LV.2]偶尔看看I

    7

    主题

    94

    回帖

    155

    积分

    荣誉开发者

    积分
    155

    荣誉开发者油中2周年生态建设者喜迎中秋

    发表于 2022-10-10 10:11:52 | 显示全部楼层
    路还很长

    函数调用还没还原啊
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2022-10-10 10:18:08 | 显示全部楼层
    maxzhang 发表于 2022-10-10 10:11
    路还很长

    函数调用还没还原啊

    慢慢来嘛
    等我从涛那骗代码!
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-13 10:14
  • 签到天数: 211 天

    [LV.7]常住居民III

    294

    主题

    3905

    回帖

    3825

    积分

    管理员

    积分
    3825

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

    发表于 2022-10-10 13:59:18 | 显示全部楼层
    看不懂了 呜呜
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。/ 微信公众号:一之哥哥
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2022-10-10 18:17:36 | 显示全部楼层

    哪里不懂
    我手把手教♂你
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-12-25 06:51
  • 签到天数: 35 天

    [LV.5]常住居民I

    9

    主题

    56

    回帖

    67

    积分

    初级工程师

    积分
    67

    油中3周年

    发表于 2023-12-9 12:44:53 | 显示全部楼层
    Ast Exploer是个工具吗?如果是如何下载
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-12-9 13:03:08 | 显示全部楼层
    tansuo 发表于 2023-12-9 12:44
    Ast Exploer是个工具吗?如果是如何下载

    https://astexplorer.net/
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表