从一道Web游戏题学习AST反混淆技术

01

前言

本次案例是一道web游戏题,但是用到的js文件被混淆了,同时在线的反混淆网站不能对其进行有效的反混淆,所以我们来学习编写ast代码来反混淆。附件放在最后。

02

处理过程

拿到的js代码是被压缩成一行的,我们找个格式化的网站格式化一下代码,然后拿到本地分析。

从一道Web游戏题学习AST反混淆技术

整体读下来,我们发现大概是将完整的代码分成了几部分,然后分别混淆,再整合到一个Js文件里,因此我们分析好局部就行了。

part.js

function q({
    const x = ["return (function() ""51rDvFsO""280bIrfll""crossOrigin""debu""5573704jgYESE""526422EOMPDB""19pdzydt""70220etHPRV""26502443qIuDbf"'{}.constructor("return this")( )'"type""string""172742zcyDzi""tagName""include""link""LINK""same-origin""test""function *\( *\)""apply""init""386856yRDrIu""addedNodes"'link[rel="modulepreload"]'"length""input""stateObject""modulepreload""relList""createElement""supports""10594465MEmbDB""5JjJNqT""setInterval""querySelectorAll""referrerPolicy""credentials""gger""anonymous""integrity""observe""action""use-credentials""constructor""omit""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
    return q = function({
        return x
    },
    q()
} (function(x, n{
    const e = z,
    t = x();
    for (;;) try {
        if ( - parseInt(e(285)) / 1 * ( - parseInt(e(291)) / 2) + parseInt(e(279)) / 3 * (parseInt(e(286)) / 4) + -parseInt(e(264)) / 5 * (parseInt(e(284)) / 6) + -parseInt(e(263)) / 7 + -parseInt(e(283)) / 8 + -parseInt(e(253)) / 9 * (parseInt(e(280)) / 10) + parseInt(e(287)) / 11 === n) break;
        t.push(t.shift())
    } catch {
        t.push(t.shift())
    }
})(q, -256319 + -5 * -139997 + 7 * 57662),
function({
    const x = z;
    let n;
    try {
        n = Function(x(278) + x(288) + ");")()
    } catch {
        n = window
    }
    n[x(265)](V, 9793 + -977 * 9)
} (),
function({
    const n = z,
    e = function({
        let o = !0;
        return function(c, i{
            const f = o ?
            function({
                const b = z;
                if (i) {
                    const s = i[b(251)](c, arguments);
                    return i = null,
                    s
                }
            }: function({};
            return o = !1,
            f
        }
    } (),
    t = document[n(261)](n(294))[n(260)];
    if (t && t[n(262)] && t[n(262)](n(259))) return;
    for (const o of document[n(266)](n(255))) a(o);
    new MutationObserver(o => {
        const c = n;
        for (const i of o) if (i[c(289)] === "childList"for (const f of i[c(254)]) f[c(292)] === c(295) && f.rel === c(259) && a(f)
    })[n(272)](document, {
        childList: !0,
        subtree: !0
    });
    function r(o{
        const c = n,
        i = {};
        return o[c(271)] && (i.integrity = o[c(271)]),
        o[c(267)] && (i[c(267)] = o[c(267)]),
        o[c(281)] === c(274) ? i.credentials = c(293) : o.crossOrigin === c(270) ? i[c(268)] = c(276) : i[c(268)] = c(296),
        i
    }
    function a(o{
        if (function({
            e(this,
            function({
                const i = z,
                f = new RegExp(i(250)),
                b = new RegExp(i(277), "i"),
                s = V(i(252)); ! f[i(249)](s + "chain") || !b[i(249)](s + i(257)) ? s("0") : V()
            })()
        } (), o.ep) return;
        o.ep = !0;
        const c = r(o);
        fetch(o.href, c)
    }
} ();
function z(x, n{
    const e = q();
    return z = function(t, r{
        return t = t - ( - 109 * -23 + -6806 + 4548),
        e[t]
    },
    z(x, n)
}
function V(x{
    function n(e{
        const t = z;
        if (typeof e === t(290)) return (function(r{})[t(275)]("while (true) {}")[t(251)]("counter"); ("" + e / e)[t(256)] !== 3561 + 712 * -5 || e % (10 * 929 + 676 + 4973 * -2) === 8536 + -1 * 5 + -1 * 8531 ? (function({
            return ! 0
        })[t(275)](t(282) + t(269)).call(t(273)) : (function({
            return ! 1
        })[t(275)]("debu" + t(269))[t(251)](t(258)),
        n(++e)
    }
    try {
        if (x) return n;
        n(599 * -15 + 8263 * 1 + 722)
    } catch {}
}
我们先来看到这两个函数
function q({
    const x = ["return (function() ""51rDvFsO""280bIrfll""crossOrigin""debu""5573704jgYESE""526422EOMPDB""19pdzydt""70220etHPRV""26502443qIuDbf"'{}.constructor("return this")( )'"type""string""172742zcyDzi""tagName""include""link""LINK""same-origin""test""function *\( *\)""apply""init""386856yRDrIu""addedNodes"'link[rel="modulepreload"]'"length""input""stateObject""modulepreload""relList""createElement""supports""10594465MEmbDB""5JjJNqT""setInterval""querySelectorAll""referrerPolicy""credentials""gger""anonymous""integrity""observe""action""use-credentials""constructor""omit""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
    return q = function({
        return x
    },
    q()


function z(x, n{
    const e = q();
    return z = function(t, r{
        return t = t - ( - 109 * -23 + -6806 + 4548),
        e[t]
    },
    z(x, n)
}

如果调用了q函数,q() return 的q(),实际上就是返回x数组;

function q({
    const x = ["return (function() ""51rDvFsO""280bIrfll""crossOrigin""debu""5573704jgYESE""526422EOMPDB""19pdzydt""70220etHPRV""26502443qIuDbf"'{}.constructor("return this")( )'"type""string""172742zcyDzi""tagName""include""link""LINK""same-origin""test""function *\( *\)""apply""init""386856yRDrIu""addedNodes"'link[rel="modulepreload"]'"length""input""stateObject""modulepreload""relList""createElement""supports""10594465MEmbDB""5JjJNqT""setInterval""querySelectorAll""referrerPolicy""credentials""gger""anonymous""integrity""observe""action""use-credentials""constructor""omit""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
    return x


=>
function q({
  return ["return (function() ""51rDvFsO""280bIrfll""crossOrigin""debu""5573704jgYESE""526422EOMPDB""19pdzydt""70220etHPRV""26502443qIuDbf"'{}.constructor("return this")( )'"type""string""172742zcyDzi""tagName""include""link""LINK""same-origin""test""function *\( *\)""apply""init""386856yRDrIu""addedNodes"'link[rel="modulepreload"]'"length""input""stateObject""modulepreload""relList""createElement""supports""10594465MEmbDB""5JjJNqT""setInterval""querySelectorAll""referrerPolicy""credentials""gger""anonymous""integrity""observe""action""use-credentials""constructor""omit""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
}

调用z函数,其实调用的是

z = function(t, r{
        return t = t - ( - 109 * -23 + -6806 + 4548),
        e[t]
    }
我们的第一个目标简化q,z函数。下面来看一些必须的东西

AST explorer


这是一个在线网址,https://astexplorer.net/。可以将js代码根据选择的编译器转为json结构的ast代码

如下图,左侧是源代码,右侧可以选择Tree的视图或者Json视图:

从一道Web游戏题学习AST反混淆技术

移动鼠标到代码上,右侧就会相应得标红

从一道Web游戏题学习AST反混淆技术

写代码时对着解析网站来写会比较好。

babel


从一道Web游戏题学习AST反混淆技术

要安装几个库
npm install @babel/core
npm install @babel/parser --save-dev
npm install @babel/traverse --save-dev
npm install @babel/generator --save-dev
npm install @babel/types

@babel/core 是 Babel 工具链的核心模块;接着是解析器,转换器,代码生成器,最后的types模块可以帮助判断node类型,构造节点。

具体的一些api用到再说吧。

化简q类函数


目标是这样
function q({
    const x = ["return (function() ""51rDvFsO""280bIrfll""crossOrigin""debu""5573704jgYESE""526422EOMPDB""19pdzydt""70220etHPRV""26502443qIuDbf"'{}.constructor("return this")( )'"type""string""172742zcyDzi""tagName""include""link""LINK""same-origin""test""function *\( *\)""apply""init""386856yRDrIu""addedNodes"'link[rel="modulepreload"]'"length""input""stateObject""modulepreload""relList""createElement""supports""10594465MEmbDB""5JjJNqT""setInterval""querySelectorAll""referrerPolicy""credentials""gger""anonymous""integrity""observe""action""use-credentials""constructor""omit""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
    return q = function({
        return x
    },
    q()

=>
function q({
  return ["return (function() ""51rDvFsO""280bIrfll""crossOrigin""debu""5573704jgYESE""526422EOMPDB""19pdzydt""70220etHPRV""26502443qIuDbf"'{}.constructor("return this")( )'"type""string""172742zcyDzi""tagName""include""link""LINK""same-origin""test""function *\( *\)""apply""init""386856yRDrIu""addedNodes"'link[rel="modulepreload"]'"length""input""stateObject""modulepreload""relList""createElement""supports""10594465MEmbDB""5JjJNqT""setInterval""querySelectorAll""referrerPolicy""credentials""gger""anonymous""integrity""observe""action""use-credentials""constructor""omit""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
}
我们把代码放到解析网站上。

从一道Web游戏题学习AST反混淆技术

从一道Web游戏题学习AST反混淆技术

对比标黄的部分,很明显的可以看出来,q1的body只有一个ReturnStatement

我们直接把return的值改为 x数组,请看下面的代码

const returnArrayVisitor = {
    FunctionDeclaration(path) {
      var funcname = path.node.id.name;
      if (funcname.length === 1 && path.node.body.body.length === 2) {
        let body = path.node.body.body;
        if (
          types.isVariableDeclaration(body[0]) &&
          types.isReturnStatement(body[1])
        ) {
          let elements;
          if (types.isArrayExpression(body[0].declarations[0].init)) {
            elements = body[0].declarations[0].init.elements;
            // 构造ArrayExpression节点作为返回值
            const arrayExpression = types.arrayExpression(elements);
            body[1] = types.returnStatement(arrayExpression);
            
            body.splice(01);
          }
        }
      }
    }
  };

首先是FunctionDeclaration,意思是所有traverse所有FunctionDeclaration类型的节点。

接着是pathnode,path即是路径,node就是节点。其实本身一个json数据,这里的.就是取出子json结构的数据。

types可以判断类型,用法types.isXXXXXXXXXX
// 判断是否符合q函数类型
var funcname = path.node.id.name;
if (funcname.length === 1 && path.node.body.body.length === 2)
// 进一步明确
if (types.isVariableDeclaration(body[0]) &&types.isReturnStatement(body[1])
//这里的话就是拿到数组,然后构造返回的节点
let elements;
if (types.isArrayExpression(body[0].declarations[0].init)) {
elements = body[0].declarations[0].init.elements;
// 构造ArrayExpression节点作为返回值
const arrayExpression = types.arrayExpression(elements);
body[1] = types.returnStatement(arrayExpression);

完整代码.js

const fs = require('fs');
const types = require("@babel/types");
const parser = require("@babel/parser");
const template = require("@babel/template").default;
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;

//将源代码解析为AST
var sourceCode = `
function q() {
  const x = ["return (function() ", "51rDvFsO", "280bIrfll", "crossOrigin", "debu", "5573704jgYESE", "526422EOMPDB", "19pdzydt", "70220etHPRV", "26502443qIuDbf", '{}.constructor("return this")( )', "type", "string", "172742zcyDzi", "tagName", "include", "link", "LINK", "same-origin", "test", "function *\\( *\\)", "apply", "init", "386856yRDrIu", "addedNodes", 'link[rel="modulepreload"]', "length", "input", "stateObject", "modulepreload", "relList", "createElement", "supports", "10594465MEmbDB", "5JjJNqT", "setInterval", "querySelectorAll", "referrerPolicy", "credentials", "gger", "anonymous", "integrity", "observe", "action", "use-credentials", "constructor", "omit", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
  return q = function() {
      return x
  },
  q()
}
`

let ast = parser.parse(sourceCode);

const returnArrayVisitor = {
    FunctionDeclaration(path) {
      var funcname = path.node.id.name;
      if (funcname.length === 1 && path.node.body.body.length === 2) {
        let body = path.node.body.body;
        if (
          types.isVariableDeclaration(body[0]) &&
          types.isReturnStatement(body[1])
        ) {
          let elements;
          if (types.isArrayExpression(body[0].declarations[0].init)) {
            elements = body[0].declarations[0].init.elements;
            // 构造ArrayExpression节点作为返回值
            const arrayExpression = types.arrayExpression(elements);
            body[1] = types.returnStatement(arrayExpression);
            body.splice(01);
          }
        }
      }
    }
  };
traverse(ast, returnArrayVisitor);

let decodeFile = "./encode_ok.js"
let { code } = generator(ast, opts = {
 "compact"false,
 "comments"false,
 "jsescOption": { "minimal"true },
});

fs.writeFile(decodeFile, code, (err) => { });
运行后结果:
function q({
  return ["return (function() ""51rDvFsO""280bIrfll""crossOrigin""debu""5573704jgYESE""526422EOMPDB""19pdzydt""70220etHPRV""26502443qIuDbf"'{}.constructor("return this")( )'"type""string""172742zcyDzi""tagName""include""link""LINK""same-origin""test""function *\( *\)""apply""init""386856yRDrIu""addedNodes"'link[rel="modulepreload"]'"length""input""stateObject""modulepreload""relList""createElement""supports""10594465MEmbDB""5JjJNqT""setInterval""querySelectorAll""referrerPolicy""credentials""gger""anonymous""integrity""observe""action""use-credentials""constructor""omit""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
}
后面我们写好visitor,然后用traverse调用。

化简z类函数


z函数

function z(x, n{
  const e = q();
  return z = function(t, r{
      return t = t - ( - 109 * -23 + -6806 + 4548),
      e[t]
  },
  z(x, n)
}
如果调用z(x, n),retun的是function z,也就是z函数被重新定义了。我们的目标如下流程,因为作用域的问题,需要把e = q()放到函数里。至于 – 109 * -23 + -6806 + 4548,常量折叠即可
function z(x, n{
  const e = q();
  return z = function(t, r{
      return t = t - ( - 109 * -23 + -6806 + 4548),
      e[t]
  },
  z(x, n)
}

=>
function z(x, n{
  const e = q();
  z = function (t, r{
    t = t - (-109 * -23 + -6806 + 4548);
    return e[t];
  };
  return z(x, n);
}
=>

var z = function(t, r{
   const e = q();
   t = t - ( - 109 * -23 + -6806 + 4548)
      return e[t]
  }
还是把代码放到解析网站上,先把逗号表达式去一下

从一道Web游戏题学习AST反混淆技术

const sequenceVisitor =
    {
      SequenceExpression(path)
      {
        let {scope,parentPath,node} = path;
        let expressions = node.expressions;
        if (parentPath.isReturnStatement({"argument":node}))
        {
          let lastExpression = expressions.pop();
          for (let expression of expressions)
          {
            parentPath.insertBefore(types.ExpressionStatement(expression=expression));
          }

          path.replaceInline(lastExpression);
        }
        else if (parentPath.isExpressionStatement({"expression":node}))
        {
          let body = [];
          expressions.forEach(express=>{body.push(types.ExpressionStatement(express));});
          path.replaceWithMultiple(body);
        }
        else
        {
          return;
        }

        scope.crawl();
      }
    }
结果如下:

从一道Web游戏题学习AST反混淆技术

再把这个函数放到解析网站上

从一道Web游戏题学习AST反混淆技术

可以看到body下有三个node,主要处理第二个。请看下面的插件

//给构造好的z = funcion加一个var。也就是 var z= function
statements.push(types.variableDeclaration('var', [variableDeclaration]));
const moveFunctionBodyVisitor = {
  FunctionDeclaration(path) {
    const { params, body } = path.node;
    const statements = [];
    if (body.body.length == 3) {
      body.body.forEach((statement) => {
        if (types.isVariableDeclaration(statement)) {
          statements.push(types.variableDeclaration(statement.kind, statement.declarations));
        } else if (types.isExpressionStatement(statement)) {
          const { expression } = statement;
          if (types.isAssignmentExpression(expression)) {
            const { left, right } = expression;
            if (types.isIdentifier(left)) {
              const variableDeclaration = types.variableDeclarator(left, right);
              variableDeclaration.init.body.body.unshift(statements[0]);

              statements.push(types.variableDeclaration('var', [variableDeclaration]));
            }
          }
        } else if (!types.isReturnStatement(statement)) {
          statements.push(statement);
        }
      });
      statements.shift()
      path.replaceWithMultiple(statements);
    }
  },
};

traverse(ast, moveFunctionBodyVisitor)

完整代码.js

const fs = require('fs');
const types = require("@babel/types");
const parser = require("@babel/parser");
const template = require("@babel/template").default;
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;

//将源代码解析为AST
var sourceCode = `
function q() {
  const x = ["return (function() ", "51rDvFsO", "280bIrfll", "crossOrigin", "debu", "5573704jgYESE", "526422EOMPDB", "19pdzydt", "70220etHPRV", "26502443qIuDbf", '{}.constructor("return this")( )', "type", "string", "172742zcyDzi", "tagName", "include", "link", "LINK", "same-origin", "test", "function *\\( *\\)", "apply", "init", "386856yRDrIu", "addedNodes", 'link[rel="modulepreload"]', "length", "input", "stateObject", "modulepreload", "relList", "createElement", "supports", "10594465MEmbDB", "5JjJNqT", "setInterval", "querySelectorAll", "referrerPolicy", "credentials", "gger", "anonymous", "integrity", "observe", "action", "use-credentials", "constructor", "omit", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
  return q = function() {
      return x
  },
  q()
}
function z(x, n) {
  const e = q();
  return z = function(t, r) {
      return t = t - ( - 109 * -23 + -6806 + 4548),
      e[t]
  },
  z(x, n)
}
`

let ast = parser.parse(sourceCode);

const returnArrayVisitor = {
    FunctionDeclaration(path) {
      var funcname = path.node.id.name;
      if (funcname.length === 1 && path.node.body.body.length === 2) {
        let body = path.node.body.body;
        if (
          types.isVariableDeclaration(body[0]) &&
          types.isReturnStatement(body[1])
        ) {
          let elements;
          if (types.isArrayExpression(body[0].declarations[0].init)) {
            elements = body[0].declarations[0].init.elements;
            // 构造ArrayExpression节点作为返回值
            const arrayExpression = types.arrayExpression(elements);
            body[1] = types.returnStatement(arrayExpression);

            body.splice(01);
          }
        }
      }
    }
  };
traverse(ast, returnArrayVisitor);

const sequenceVisitor =
    {
      SequenceExpression(path)
      {
        let {scope,parentPath,node} = path;
        let expressions = node.expressions;
        if (parentPath.isReturnStatement({"argument":node}))
        {
          let lastExpression = expressions.pop();
          for (let expression of expressions)
          {
            parentPath.insertBefore(types.ExpressionStatement(expression=expression));
          }

          path.replaceInline(lastExpression);
        }
        else if (parentPath.isExpressionStatement({"expression":node}))
        {
          let body = [];
          expressions.forEach(express=>{body.push(types.ExpressionStatement(express));});
          path.replaceWithMultiple(body);
        }
        else
        {
          return;
        }

        scope.crawl();
      }
    }

traverse(ast, sequenceVisitor);

const moveFunctionBodyVisitor = {
  FunctionDeclaration(path) {
    const { params, body } = path.node;
    const statements = [];
    if (body.body.length === 3) {
      body.body.forEach((statement) => {
        if (types.isVariableDeclaration(statement)) {
          statements.push(types.variableDeclaration(statement.kind, statement.declarations));
        } else if (types.isExpressionStatement(statement)) {
          const { expression } = statement;
          if (types.isAssignmentExpression(expression)) {
            const { left, right } = expression;
            if (types.isIdentifier(left)) {
              const variableDeclaration = types.variableDeclarator(left, right);
              variableDeclaration.init.body.body.unshift(statements[0]);

              statements.push(types.variableDeclaration('var', [variableDeclaration]));
            }
          }
        } else if (!types.isReturnStatement(statement)) {
          statements.push(statement);
        }
      });
      statements.shift()
      path.replaceWithMultiple(statements);
    }
  },
};
traverse(ast,moveFunctionBodyVisitor);

let decodeFile = "./encode_ok.js"
let { code } = generator(ast, opts = {
 "compact"false,
 "comments"false,
 "jsescOption": { "minimal"true },
});

fs.writeFile(decodeFile, code, (err) => { });
运行结果如下:

从一道Web游戏题学习AST反混淆技术

剩余处理


我们目前只是处理了两个函数,现在来还原剩下的部分
源码如下
var Z = E;
var E = function (t, r{
    var e = N();
    t = t - 481;
    var a = e[t];
    return a;
};
function N({
    return ["action""string""2331990Smsoio""length""function *\( *\)""call""input""stateObject""counter""930bExSFt""savePosition""while (true) {}""chain""98601tspbnR""setInterval""constructor""10EjKgiA""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)""value""test""mergedFrom""init""debu""prototype""56CjCzAS""677128zAClZZ""previousPosition""75022iPEXCA""15202JaLHoO""apply""581502egQFhJ""gger""531924HtjIlh""51xGTVPz""serialize"];
}

(function ({
    for (var e = E, t = N();;) {
        try {
            var r = parseInt(e(494)) / 1 + -parseInt(e(508)) / 2 * (-parseInt(e(514)) / 3) + -parseInt(e(513)) / 4 * (-parseInt(e(497)) / 5) + -parseInt(e(511)) / 6 + -parseInt(e(505)) / 7 * (parseInt(e(506)) / 8) + parseInt(e(483)) / 9 + -parseInt(e(490)) / 10 * (parseInt(e(509)) / 11);
            if (r === 358789) {
                break;
            }
            t["push"](t["shift"]());
        } catch {
            t["push"](t["shift"]());
        }
    }
})();
var d0 = function ({
    var x = true;
    return function (n, e{
        var t = x ? function ({
            var r = E;
            if (e) {
                var a = e[r(510)](n, arguments);
                e = null;
                return a;
            }
        } : function ({};
        x = false;
        return t;
    };
}();
(function ({
    d0(thisfunction ({
        var x = E;
        var n = new RegExp(x(485));
        var e = new RegExp(x(498), "i");
        var t = U(x(502));
        !n[x(500)](t + x(493)) || !e[x(500)](t + x(487)) ? t("0") : U();
    })();
})();
(function ({
    var x = E;
    try {
        t = Function('return (function() {}.constructor("return this")( ));')();
    } catch {
        t = window;
    }
    var e = n();
    e[x(495)](U, 1000);
})();
function j(x, n{
    var e = E;
    this["x"] = x["x"];
    this["y"] = x["y"];
    this[e(499)] = n || 2;
    this[e(507)] = null;
    this[e(501)] = null;
}

j[Z(504)][Z(491)] = function ({
    var x = Z;
    this[x(507)] = {
        "x"this["x"],
        "y"this["y"]
    };
};
j["prototype"]["updatePosition"] = function (x{
    this["x"] = x["x"];
    this["y"] = x["y"];
};
j[Z(504)][Z(515)] = function ({
    var x = Z;
    return {
        "position": {
            "x"this["x"],
            "y"this["y"]
        },
        "value"this[x(499)]
    };
};
function U(x{
    function n(e{
        var t = E;
        if (typeof e === t(482)) {
            return function (r{}[t(496)](t(492))[t(510)](t(489));
        }
        ("" + e / e)[t(484)] !== 1 || e % 20 === 0 ? function ({
            return true;
        }[t(496)](t(503) + t(512))[t(486)](t(481)) : function ({
            return false;
        }[t(496)](t(503) + t(512))[t(510)](t(488));
        n(++e);
    }
    try {
        if (x) {
            return n;
        }
        n(0);
    } catch {}
}
可以看到代码里有非常多的调用,我们把这些解出来

从一道Web游戏题学习AST反混淆技术

我们想法是,直接计算出函数调用的结果,然后替换,在webstorm里看了下引用(赋值)的地方。

从一道Web游戏题学习AST反混淆技术

我的选择是把要调用的函数放到main.js里,然后碰到调用函数名长度为1函数且实参是一个数字,就执行。

var E = function (t, r{
    var e = N();
    t = t - 481;
    var a = e[t];
    return a;
};
function N({
    return ["action""string""2331990Smsoio""length""function *\( *\)""call""input""stateObject""counter""930bExSFt""savePosition""while (true) {}""chain""98601tspbnR""setInterval""constructor""10EjKgiA""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)""value""test""mergedFrom""init""debu""prototype""56CjCzAS""677128zAClZZ""previousPosition""75022iPEXCA""15202JaLHoO""apply""581502egQFhJ""gger""531924HtjIlh""51xGTVPz""serialize"];
}
const callToLiteral=
{
    CallExpression(path)
    {
      let {callee,arguments} = path.node;
      if (!types.isIdentifier(callee) || arguments.length !== 1)
      {
        return;
      }

      let name = callee.name;
   // if(['x','r','x','t'].includes(name) && types.isNumericLiteral(arguments[0])){
  //   let value = A(arguments[0].value);
  //   path.replaceWith(types.valueToNode(value));
   // }
  if(['r','s','t','i','x','l','n'].includes(name) && types.isNumericLiteral(arguments[0])){
   let value = T(arguments[0].value);
   path.replaceWith(types.valueToNode(value));
  }
    }
}

再次给一下完整代码

main.js
const fs = require('fs');
const types = require("@babel/types");
const parser = require("@babel/parser");
const template = require("@babel/template").default;
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;

//将源代码解析为AST
var sourceCode = `
var Z = E;
var E = function (t, r) {
    var e = N();
    t = t - 481;
    var a = e[t];
    return a;
};
function N() {
    return ["action", "string", "2331990Smsoio", "length", "function *\\( *\\)", "call", "input", "stateObject", "counter", "930bExSFt", "savePosition", "while (true) {}", "chain", "98601tspbnR", "setInterval", "constructor", "10EjKgiA", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "value", "test", "mergedFrom", "init", "debu", "prototype", "56CjCzAS", "677128zAClZZ", "previousPosition", "75022iPEXCA", "15202JaLHoO", "apply", "581502egQFhJ", "gger", "531924HtjIlh", "51xGTVPz", "serialize"];
}

(function () {
    for (var e = E, t = N();;) {
        try {
            var r = parseInt(e(494)) / 1 + -parseInt(e(508)) / 2 * (-parseInt(e(514)) / 3) + -parseInt(e(513)) / 4 * (-parseInt(e(497)) / 5) + -parseInt(e(511)) / 6 + -parseInt(e(505)) / 7 * (parseInt(e(506)) / 8) + parseInt(e(483)) / 9 + -parseInt(e(490)) / 10 * (parseInt(e(509)) / 11);
            if (r === 358789) {
                break;
            }
            t["push"](t["shift"]());
        } catch {
            t["push"](t["shift"]());
        }
    }
})();
var d0 = function () {
    var x = true;
    return function (n, e) {
        var t = x ? function () {
            var r = E;
            if (e) {
                var a = e[r(510)](n, arguments);
                e = null;
                return a;
            }
        } : function () {};
        x = false;
        return t;
    };
}();
(function () {
    d0(this, function () {
        var x = E;
        var n = new RegExp(x(485));
        var e = new RegExp(x(498), "i");
        var t = U(x(502));
        !n[x(500)](t + x(493)) || !e[x(500)](t + x(487)) ? t("0") : U();
    })();
})();
(function () {
    var x = E;
    try {
        t = Function('return (function() {}.constructor("return this")( ));')();
    } catch {
        t = window;
    }
    var e = n();
    e[x(495)](U, 1000);
})();
function j(x, n) {
    var e = E;
    this["x"] = x["x"];
    this["y"] = x["y"];
    this[e(499)] = n || 2;
    this[e(507)] = null;
    this[e(501)] = null;
}

j[Z(504)][Z(491)] = function () {
    var x = Z;
    this[x(507)] = {
        "x": this["x"],
        "y": this["y"]
    };
};
j["prototype"]["updatePosition"] = function (x) {
    this["x"] = x["x"];
    this["y"] = x["y"];
};
j[Z(504)][Z(515)] = function () {
    var x = Z;
    return {
        "position": {
            "x": this["x"],
            "y": this["y"]
        },
        "value": this[x(499)]
    };
};
function U(x) {
    function n(e) {
        var t = E;
        if (typeof e === t(482)) {
            return function (r) {}[t(496)](t(492))[t(510)](t(489));
        }
        ("" + e / e)[t(484)] !== 1 || e % 20 === 0 ? function () {
            return true;
        }[t(496)](t(503) + t(512))[t(486)](t(481)) : function () {
            return false;
        }[t(496)](t(503) + t(512))[t(510)](t(488));
        n(++e);
    }
    try {
        if (x) {
            return n;
        }
        n(0);
    } catch {}
}
`

let ast = parser.parse(sourceCode);

const returnArrayVisitor = {
    FunctionDeclaration(path) {
      var funcname = path.node.id.name;
      if (funcname.length === 1 && path.node.body.body.length === 2) {
        let body = path.node.body.body;
        if (
          types.isVariableDeclaration(body[0]) &&
          types.isReturnStatement(body[1])
        ) {
          let elements;
          if (types.isArrayExpression(body[0].declarations[0].init)) {
            elements = body[0].declarations[0].init.elements;
            // 构造ArrayExpression节点作为返回值
            const arrayExpression = types.arrayExpression(elements);
            body[1] = types.returnStatement(arrayExpression);

            body.splice(01);
          }
        }
      }
    }
  };
traverse(ast, returnArrayVisitor);

const sequenceVisitor =
    {
      SequenceExpression(path)
      {
        let {scope,parentPath,node} = path;
        let expressions = node.expressions;
        if (parentPath.isReturnStatement({"argument":node}))
        {
          let lastExpression = expressions.pop();
          for (let expression of expressions)
          {
            parentPath.insertBefore(types.ExpressionStatement(expression=expression));
          }

          path.replaceInline(lastExpression);
        }
        else if (parentPath.isExpressionStatement({"expression":node}))
        {
          let body = [];
          expressions.forEach(express=>{body.push(types.ExpressionStatement(express));});
          path.replaceWithMultiple(body);
        }
        else
        {
          return;
        }

        scope.crawl();
      }
    }

traverse(ast, sequenceVisitor);

const moveFunctionBodyVisitor = {
  FunctionDeclaration(path) {
    const { params, body } = path.node;
    const statements = [];
    if (body.body.length === 3) {
      body.body.forEach((statement) => {
        if (types.isVariableDeclaration(statement)) {
          statements.push(types.variableDeclaration(statement.kind, statement.declarations));
        } else if (types.isExpressionStatement(statement)) {
          const { expression } = statement;
          if (types.isAssignmentExpression(expression)) {
            const { left, right } = expression;
            if (types.isIdentifier(left)) {
              const variableDeclaration = types.variableDeclarator(left, right);
              variableDeclaration.init.body.body.unshift(statements[0]);

              statements.push(types.variableDeclaration('var', [variableDeclaration]));
            }
          }
        } else if (!types.isReturnStatement(statement)) {
          statements.push(statement);
        }
      });
      statements.shift()
      path.replaceWithMultiple(statements);
    }
  },
};
traverse(ast,moveFunctionBodyVisitor);

var E = function (t, r{
  var e = N();
  t = t - 481;
  var a = e[t];
  return a;
};
function N({
  return ["action""string""2331990Smsoio""length""function *\( *\)""call""input""stateObject""counter""930bExSFt""savePosition""while (true) {}""chain""98601tspbnR""setInterval""constructor""10EjKgiA""\+\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)""value""test""mergedFrom""init""debu""prototype""56CjCzAS""677128zAClZZ""previousPosition""75022iPEXCA""15202JaLHoO""apply""581502egQFhJ""gger""531924HtjIlh""51xGTVPz""serialize"];
}

const callToLiteral=
    {
      CallExpression(path)
      {
        let {callee,arguments} = path.node;
        if (!types.isIdentifier(callee) || arguments.length !== 1)
        {
          return;
        }
        let name = callee.name;
        if(['r','s','t','i','x','l','e','Z'].includes(name) && types.isNumericLiteral(arguments[0])){
          let value = E(arguments[0].value);
          path.replaceWith(types.valueToNode(value));
        }
      }
    }

traverse(ast,callToLiteral)
let decodeFile = "./encode_ok.js"
let { code } = generator(ast, opts = {
 "compact"false,
 "comments"false,
 "jsescOption": { "minimal"true },
});

fs.writeFile(decodeFile, code, (err) => { });
结果如下:

从一道Web游戏题学习AST反混淆技术


03

总结

学习反混淆技术可以降低分析代码逻辑的时间和成本。

附件:

https://pan.baidu.com/s/1SCSttJKq1JQiN6CF4RPSqA?pwd=laij

原文始发于微信公众号(山石网科安全技术研究院):从一道Web游戏题学习AST反混淆技术

版权声明:admin 发表于 2024年2月23日 上午11:04。
转载请注明:从一道Web游戏题学习AST反混淆技术 | CTF导航

相关文章