# AST 语法树介绍

AST 全称 Abstract Syntax Tree, 中文名称为抽象语法树,主要原理就是把代码通过词法分析和句法分析转化为一颗树的形式,对于树来讲又包含很多节点,其中包含了变量的位置,类别,值等一些参数。

例如:

var a = 3;

对应的 AST 解析结果为 (采用 Babel 解析,结果只取 body.declarations 中的内容):

Node {
  type: 'VariableDeclarator',
  start: 4,
  end: 9,
  loc: SourceLocation {
    start: Position { line: 1, column: 4 },
    end: Position { line: 1, column: 9 },
    filename: undefined,
    identifierName: undefined
  },
  id: Node {
    type: 'Identifier',
    start: 4,
    end: 5,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: 'a'
    },
    name: 'a'
  },
  init: Node {
    type: 'NumericLiteral',
    start: 8,
    end: 9,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: undefined
    },
    extra: { rawValue: 3, raw: '3' },
    value: 3
  }
}

上述将代码解析为了三块,第一块的 type 为 'VariableDeclarator',对应 var, 第二块为 Identifier 为对应的变量 a, 第三块为 NumericLiteral,为对应的数字 3。

这样就把 js 代码解析成了树的结构,然后就可以根据遍历的方式,提取,修改其中的值。

# 本地运行 babel

运行 babel 首先需要安装 babel 的库。

npm install @babel/parser
npm install @babel/traverse
npm install @babel/generator

parser 主要是用于将代码解析成语法树的形式,traverse 主要是用于遍历语法树节点,generator 主要是用于将语法树解析回 js 代码。

# parser 用法

导包:

const {parse} = require('@babel/parser');

由于我们需要用的是 parser 这个包中的 parse 函数,而 parser 又是一个对象,所以我们采用 {} 的方式只导入 parse 方法。

然后我们就可以直接的采用以下方式,就可以直接将 js 代码转化为 ast 树。

parse('js_code');

# traverse 用法

导包:

const traverse = require("@babel/traverse").default;

这里是取出的 traverse 包中的默认方法.

traverse 方法需要两个参数,一个是 ast 树的对象,一个是要遍历对象。

const visitor = {
    NumericLiteral(path) {
        path.node.value = 100;
        console.log(path.node.value);
    }
};
traverse(ast_code, visitor);

上述通过将 ast 树中的属性为 NumericLiteral 的节点的值批量替换成了 100。

# generator 用法

导包:

const generator = require("@babel/generator").default;

与 traverse 类似,调用的包中的默认方法。

generator(ast_code).code

这样就把语法树修改回 js 的代码样式,然后再另存即可。

# types 用法

导包:

const types = require("@babel/types");

types 主要用于判断节点类型。

example: types.isStringLiteral (Node) 判断该节点是不是字符类型.

# 补充

node 和 path 的区别:

path 代表的是 traverse 的路径之类的东西,node 是 path 中的一个属性,代表的是具体遍历到的位置,通常我们直接使用 node 就可以对节点进行增删查改的操作。

scope 解释

  • path.scope 可以输出当前作用域
  • path.scope.dumps 可以输出当前作用域以及其节点的作用域。
  • 在 path.scope 中包含多种属性,我们通常关注 binding。

对于每一个变量的 binding 包括 4 种信息,constant 是否会被修改,refererces 被修改的次数,violations 被重新定义的次数,Kind 表明类型。

# AST 插件

# 常量混淆

所谓常量混淆主要指的是,将变量的属性用 ASCII 码,16 进制码的方式代替,由于 Js 的特性,在运行时,会自动的将其解析成实际的值,但是在我们看来,会造成分析的不便。

在 ast 语法树中,类似的 StringLiteral 节点中包含 extra 节点,其中 extra 中包含了 rawvalue,raw 的属性,rawvalue 对应的是其展示的值,与 value 的值相对应,raw 包含的是 ASCII 码、Unicode 编码类似的,输入的值。在该处 extra 节点是无意义的,删掉就可以返回实际的 value.

StringLiteral(path) {
        if(path.node.extra){
            console.log(path.node.extra);
            // 将其直接置换为未定义,即可删除该节点。
            path.node.extra = undefined;
        }
    }

# 常量折叠

对于常量折叠来讲,在解除了常量混淆后,我们希望看到类似的 'par'+'se' 直接变成 'parse' 的形式,这样就需要用到常量折叠的方式。

常量折叠可以抽象为如下几个步骤:

  1. 筛选操作节点
  2. 声明节点属性
  3. 判断节点类型
  4. 计算
  5. 替换
// 对于字符串类型的常量堆叠
BinaryExpression(path){
   		// 筛选操作节点
        let left = path.node.left;
        let right = path.node.right;
        // 字符串类型判断
        if(types.isStringLiteral(left)||types.isStringLiteral(right)){
            // console.log(left.value + right.value);
            //path.evaluate () 为节点计算,自动相加里面的 value, 得出三个参数:confident:true 为可相加,deopt: 暂时不清楚,value: 加后的值
            // 计算
            const {value} = path.evaluate();
            // console.log(path.evaluate());
            // 替换
            path.replaceInline(types.valueToNode(value));
        }
    }

# 函数还原

所谓函数还原,就是针对只包含 return,且作用域中只包含传入的参数的函数进行替换的操作,达到减少分析量的作用。

function add(a,b){
  	return a + b;
}
var c = add(3,4) + add(4,5)

最终目的是将其直接还原成 let c = 3 + 4 + 4 + 5; 的形式。

将上述代码通过 babel 解析后,发现,上述是由两个部分构成的,

1.FunctionDeclaration

2.VariableDeclaration

由于函数作用域的特性,我们在对函数还原的时候,往往需要遍历函数表达式,函数表达式的关联节点为对应的调用位置(对 scope 中的 binding 调用 referencePaths 方法), 然后上述对应的指向为 add,接着就需要将其转移到父节点(add (3,4)) 的位置进行还原。

const fast_run = {
    FunctionDeclaration(path){
        const {id,body} = path.node;
        // 获取函数作用域集
        const binding = path.scope.getBinding(id.name);
        // 获取作用域关联集 (函数调用集)
        let referpaths = binding.referencePaths;
        // console.log(referpaths.toString());
        // 单个选中
        // 将函数表达式加入内存中
        eval(path.toString());
        for(const referpath of referpaths){
            // 作用域关联到的对象只有调用对象,不包含具体的调用表达式,所以需要获取其父节点,然后就可已得到调用表达式,接着就是将调用表达式中的内容直接还原
            let callExpression = referpath.parentPath;
            // 直接调用函数表达式,获取其值
            let value = eval(callExpression.toString());
            // console.log(callExpression.toString()+'->'+value);
            callExpression.replaceWith(types.valueToNode(value));
        }
    }
}

# 控制流平坦化

所谓控制流平坦化,就是将类 while|switch、case 的结构平坦化,可以直接的看到运行流程,这里以一个简要的平坦化过程为例。

var arr = [2,1,0];
var a = 0;
while(!![]){
	switch (arr[a++]) {
		case 0:
			console.log(0);
			continue;
		case 1:
			console.log(1);
			continue;
		case 2:
			console.log(2);
	}
	break;
}

上述 AST 解析后,由 3 块构成:

  1. VariableDeclaration
  2. VariableDeclaration
  3. WhileStatement

在这里,我们需要对 WhileStatement 中的内容进行遍历,由于顺序已经知道,这里直接采用,针对改代码的插件如下:

const switch_visitor = {
    WhileStatement(path){
        // 当前 while 块对应的节点
        const cur_node = path.node;
        //body 中包含两块,一块为 switch 块,另一块为 break 块,根据索引提取 switch 块,
        var switch_cell = cur_node.body.body[0];
        // 获取数组,针对不同情况需要重新定义
        var arr = [2,1,0];
        // 获取 case 块
        var case_list = switch_cell.cases;
        // 建立数组用于保存后续值
        var temp_arr_switch_case = [];
        // 数组映射,按给定数组顺序
        arr.map(targetIndex =>{
            // 获取 case 中的部分,删除 continue 节点,然后将剩余节点添加进数组
            var targetBody = case_list[targetIndex].consequent;
            if (types.isContinueStatement(targetBody[targetBody.length - 1])){
                    targetBody.pop();
                }
            temp_arr_switch_case = temp_arr_switch_case.concat(targetBody);
        });
        path.replaceWithMultiple(temp_arr_switch_case);
    }
}

上述代码中,对于数组部分,是直接采用的已经给定的数组,若要使插件适用性增加,需要优化获取数组,以及对是否包含控制流进行判断。

还原后的结果如下:

var arr = [2, 1, 0];
var a = 0;
console.log(2);
console.log(1);
console.log(0);

# 初探 OB 混淆

在此以简单的 Ob 为例。

function hi() {
  console.log("Hello World!");
}
hi();

JavaScript Obfuscator Tool 网站经过默认的混淆后结果如下:

var _0x30bb = ['log', 'Hello\x20World!'];
(function (_0x38d89d, _0x30bbb2) {
    var _0xae0a32 = function (_0x2e4e9d) {
        while (--_0x2e4e9d) {
            _0x38d89d['push'](_0x38d89d['shift']());
        }
    };
    _0xae0a32(++_0x30bbb2);
}(_0x30bb, 0x153));
var _0xae0a = function (_0x38d89d, _0x30bbb2) {
    _0x38d89d = _0x38d89d - 0x0;
    var _0xae0a32 = _0x30bb[_0x38d89d];
    return _0xae0a32;
};
function hi() {
    console[_0xae0a('0x1')](_0xae0a('0x0'));
}
hi();

可以看到,一个单纯的 hello world 被混淆成了这样,为后续分析增加了难度。将代码放入 ast 在线解析网址解析,可以发现上述主要被分为了 5 给代码块,按顺序依次为:

  1. VariableDeclaration
  2. ExpressionStatement
  3. FunctionDeclaration
  4. FunctionDeclaration
  5. ExpressionStatement

其中第一个声明了变量,第二个为自执行函数,最后一个为表达式,其余的为函数声明式,接下来就需要在各个部分具体发生了什么。

在开头加入 debugger; 然后在浏览器中调试,首先直接运行自执行函数 (第一个表达式),

我们发现,在第一行直接定义了一个数组,接着是一个自执行函数,传入的参数有两个,第一个为之前的数组,第二个为一个字节码的数字,接着我们发现在里面又定义了一个函数,内含对传入的数组的 push,shift 操作,通过反复调用该函数达到将数组顺序改变的目的。

['Hello World!', 'log']

在进行顺序改变后,数组就变成了上述的形式,紧接着定义了两个函数,最后执行 hi () 函数,在 hi 函数中,我们发现它调用了之前定义的_0xae0 这个函数,传入了一个字符串,接着跟进 _0xae0a 函数中,我们发现它首先是对传入的字符进行了一个运算操作,得到了 1,然后就用得到的数去访问之前声明的数组,然后将该值作为返回值,最后 _0xae0a ('0x1') 就变成了数组中的一个值,最后就运行了这条语句,输出了结果。

总结:上述主要进行了 4 部分

1. 声明了一个大数组

2. 对大数组的顺序进行了改变

3. 定义了一个函数,用于取得大数组中的数

4. 输出结果

在 4 中需要调用 3 才能拿到结果,我们通常把 4 成为加密函数,3 称为解密函数。

startID=>start: 声明大数组
operation1=>operation: 数组位置偏移
operation2=>operation: 解密函数
endID=>end: 函数入口
startID->operation1->operation2->endID
operation2->endID

在第一个执行函数中,我们发现其传入了两个参数,第一个参数刚好对应的是第二块内容 (也就是第一个函数声明),第二个参数为一个字节码的数字,可不管。在函数内部,首先声明了两个变量,分别调用第一个函数和第三个函数,调用第一个函数的返回值是一个数组,然后在 while 循环中通过第三个函数不断取数组中的值进行一个计算,接着在进行一个跳出判断,在此之间不断的对数组进行操作 (顺序改变).

即第一个自执行函数和第一个函数、第三个函数为一个整体,最终会得到改变了执行顺序的数组,同时将数组写入内存中。

接下来分析最后一个函数和第二个函数体,发现,最后一个函数是直接调用第二个函数体的,而且其函数名与未混淆前的一样为 hi ().

在 hi 中又重新调用了第三个函数,取出了其中的参数,然后对混淆进行了解除,最终达到了解除混淆的目的。

# AST 解 OB 混淆 (lowlevel)

还是以上述为例,在此我们采用了 lowlevel 的加密,首先还是对 AST 树进行分析。

(function (_0x4becbb, _0x142d2c) {
    var _0x4e95f5 = _0xcf3d,
    _0x5cece4 = _0x4becbb();
    while (!![]) {
        try {
            var _0xada9ef = -parseInt(_0x4e95f5(0x1be)) / 0x1 + parseInt(_0x4e95f5(0x1bc)) / 0x2 * (parseInt(_0x4e95f5(0x1bb)) / 0x3) + -parseInt(_0x4e95f5(0x1d2)) / 0x4 * (-parseInt(_0x4e95f5(0x1ca)) / 0x5) + -parseInt(_0x4e95f5(0x1c7)) / 0x6 * (parseInt(_0x4e95f5(0x1c5)) / 0x7) + parseInt(_0x4e95f5(0x1cb)) / 0x8 * (-parseInt(_0x4e95f5(0x1d1)) / 0x9) + -parseInt(_0x4e95f5(0x1d3)) / 0xa + parseInt(_0x4e95f5(0x1d4)) / 0xb;
            if (_0xada9ef === _0x142d2c)
                break;
            else
                _0x5cece4['push'](_0x5cece4['shift']());
        } catch (_0x52b941) {
            _0x5cece4['push'](_0x5cece4['shift']());
        }
    }
}
    (_0x3b5e, 0x373f7));
function _0x3b5e() {
    var _0x3cd390 = ['1386052gLrmrQ', '456540taaToI', '2342241LghZeo', 'exception', 'log', 'info', '45411uGtYTE', '2wZNffB', '__proto__', '172005fARfwU', 'warn', 'return\x20(function()\x20', '(((.+)+)+)+$', 'apply', 'console', 'search', '7ehYmcm', 'bind', '556752jyeSlY', '{}.constructor(\x22return\x20this\x22)(\x20)', 'trace', '5zuIaAZ', '184OzUZAg', 'toString', 'prototype', 'constructor', 'error', 'Hello\x20World!', '14805EviJWK'];
    _0x3b5e = function () {
        return _0x3cd390;
    };
    return _0x3b5e();
}
function hi() {
    debugger;
    var _0x12b47a = _0xcf3d,
    _0xbdadaf = function () {
        var _0x22d85c = !![];
        return function (_0x403758, _0x1a9953) {
            var _0x48963d = _0x22d85c ? function () {var _0x52f5f2 = _0xcf3d;if (_0x1a9953) {var _0x1c3993 = _0x1a9953[_0x52f5f2(0x1c2)](_0x403758, arguments);return _0x1a9953 = null,_0x1c3993;}}
             : function () {};
            return _0x22d85c = ![],
            _0x48963d;
        };
    }
    (),
    _0x5c0371 = _0xbdadaf(this, function () {
        var _0x36e602 = _0xcf3d;
        return _0x5c0371['toString']()[_0x36e602(0x1c4)](_0x36e602(0x1c1))['toString']()[_0x36e602(0x1ce)](_0x5c0371)[_0x36e602(0x1c4)](_0x36e602(0x1c1));
    });
    _0x5c0371();
    var _0xa991eb = function () {
        var _0x9d0201 = !![];
        return function (_0x35b283, _0x1e4ff) {
            var _0x5b9df8 = _0x9d0201 ? function () {
                var _0x4d958a = _0xcf3d;
                if (_0x1e4ff) {
                    var _0x34974c = _0x1e4ff[_0x4d958a(0x1c2)](_0x35b283, arguments);
                    return _0x1e4ff = null,
                    _0x34974c;
                }
            }
             : function () {};
            return _0x9d0201 = ![],
            _0x5b9df8;
        };
    }
    (),
    _0x46ef64 = _0xa991eb(this, function () {
        var _0x442076 = _0xcf3d,
        _0x15972f = function () {
            var _0xa6d883 = _0xcf3d,
            _0x5ee4a3;
            try {
                _0x5ee4a3 = Function(_0xa6d883(0x1c0) + _0xa6d883(0x1c8) + ');')();
            } catch (_0x37267b) {
                _0x5ee4a3 = window;
            }
            return _0x5ee4a3;
        },
        _0x52d182 = _0x15972f(),
        _0xb55846 = _0x52d182[_0x442076(0x1c3)] = _0x52d182[_0x442076(0x1c3)] || {},
        _0x503491 = [_0x442076(0x1b9), _0x442076(0x1bf), _0x442076(0x1ba), _0x442076(0x1cf), _0x442076(0x1b8), 'table', _0x442076(0x1c9)];
        for (var _0x2e6262 = 0x0; _0x2e6262 < _0x503491['length']; _0x2e6262++) {
            var _0xade831 = _0xa991eb[_0x442076(0x1ce)][_0x442076(0x1cd)][_0x442076(0x1c6)](_0xa991eb),
            _0x202fb0 = _0x503491[_0x2e6262],
            _0x33f2a4 = _0xb55846[_0x202fb0] || _0xade831;
            _0xade831[_0x442076(0x1bd)] = _0xa991eb[_0x442076(0x1c6)](_0xa991eb),
            _0xade831[_0x442076(0x1cc)] = _0x33f2a4[_0x442076(0x1cc)][_0x442076(0x1c6)](_0x33f2a4),
            _0xb55846[_0x202fb0] = _0xade831;
        }
    });
    _0x46ef64();
    console[_0x12b47a(0x1b9)](_0x12b47a(0x1d0));
}
function _0xcf3d(_0x24ba4c, _0x435eb9) {
    var _0x3efe0d = _0x3b5e();
    return _0xcf3d = function (_0x2feccf, _0x4d6d2b) {
        _0x2feccf = _0x2feccf - 0x1b8;
        var _0x33ef9c = _0x3efe0d[_0x2feccf];
        return _0x33ef9c;
    },
    _0xcf3d(_0x24ba4c, _0x435eb9);
}
hi();
  1. ExpressionStatement

  2. FunctionDeclaration

  3. FunctionDeclaration

  4. FunctionDeclaration

  5. ExpressionStatement

对比默认级的 ob 我们可以发现,在第一个部分,虽然这里变成了 3 个函数声明式,但是其实在第二个函数声明式中,还是包含了创建数组这一操作,在自执行函数中依旧包含了数组移位的操作。从执行流程来看,上述分别对应,数组移位,声明数组,加密函数,解密函数,调用函数。

同时,相对之前的 Ob 来看,这里对数字进行了混淆操作,为了增加代码的可读性,我们先试着对其进行一个数字解混淆的操作。

(function (_0x4becbb, _0x142d2c) {
  var _0x4e95f5 = _0xcf3d,
      _0x5cece4 = _0x4becbb();
  while (!![]) {
    try {
      var _0xada9ef = -parseInt(_0x4e95f5(446)) / 1 + parseInt(_0x4e95f5(444)) / 2 * (parseInt(_0x4e95f5(443)) / 3) + -parseInt(_0x4e95f5(466)) / 4 * (-parseInt(_0x4e95f5(458)) / 5) + -parseInt(_0x4e95f5(455)) / 6 * (parseInt(_0x4e95f5(453)) / 7) + parseInt(_0x4e95f5(459)) / 8 * (-parseInt(_0x4e95f5(465)) / 9) + -parseInt(_0x4e95f5(467)) / 10 + parseInt(_0x4e95f5(468)) / 11;
      if (_0xada9ef === _0x142d2c) break;else _0x5cece4["push"](_0x5cece4["shift"]());
    } catch (_0x52b941) {
      _0x5cece4["push"](_0x5cece4["shift"]());
    }
  }
})(_0x3b5e, 226295);
function _0x3b5e() {
  var _0x3cd390 = ["1386052gLrmrQ", "456540taaToI", "2342241LghZeo", "exception", "log", "info", "45411uGtYTE", "2wZNffB", "__proto__", "172005fARfwU", "warn", "return (function() ", "(((.+)+)+)+$", "apply", "console", "search", "7ehYmcm", "bind", "556752jyeSlY", "{}.constructor(\"return this\")( )", "trace", "5zuIaAZ", "184OzUZAg", "toString", "prototype", "constructor", "error", "Hello World!", "14805EviJWK"];
  _0x3b5e = function () {
    return _0x3cd390;
  };
  return _0x3b5e();
}
function _0xcf3d(_0x24ba4c, _0x435eb9) {
  var _0x3efe0d = _0x3b5e();
  return _0xcf3d = function (_0x2feccf, _0x4d6d2b) {
    _0x2feccf = _0x2feccf - 440;
    var _0x33ef9c = _0x3efe0d[_0x2feccf];
    return _0x33ef9c;
  }, _0xcf3d(_0x24ba4c, _0x435eb9);
}
function hi() {
  var _0x12b47a = _0xcf3d,
      _0xbdadaf = function () {
    var _0x22d85c = !![];
    return function (_0x403758, _0x1a9953) {
      var _0x48963d = _0x22d85c ? function () {
        var _0x52f5f2 = _0xcf3d;
        if (_0x1a9953) {
          var _0x1c3993 = _0x1a9953[_0x52f5f2(450)](_0x403758, arguments);
          return _0x1a9953 = null, _0x1c3993;
        }
      } : function () {};
      return _0x22d85c = ![], _0x48963d;
    };
  }(),
      _0x5c0371 = _0xbdadaf(this, function () {
    var _0x36e602 = _0xcf3d;
    return _0x5c0371["toString"]()[_0x36e602(452)](_0x36e602(449))["toString"]()[_0x36e602(462)](_0x5c0371)[_0x36e602(452)](_0x36e602(449));
  });
  _0x5c0371();
  var _0xa991eb = function () {
    var _0x9d0201 = !![];
    return function (_0x35b283, _0x1e4ff) {
      var _0x5b9df8 = _0x9d0201 ? function () {var _0x4d958a = _0xcf3d;if (_0x1e4ff) {var _0x34974c = _0x1e4ff[_0x4d958a(450)](_0x35b283, arguments);return _0x1e4ff = null, _0x34974c;}} : function () {};return _0x9d0201 = ![], _0x5b9df8;};}(),
      _0x46ef64 = _0xa991eb(this, function () {
    var _0x442076 = _0xcf3d,
        _0x15972f = function () {
      var _0xa6d883 = _0xcf3d,
          _0x5ee4a3;
      try {
        _0x5ee4a3 = Function(_0xa6d883(448) + _0xa6d883(456) + ");")();
      } catch (_0x37267b) {
        _0x5ee4a3 = window;
      }
      return _0x5ee4a3;
    },
        _0x52d182 = _0x15972f(),
        _0xb55846 = _0x52d182[_0x442076(451)] = _0x52d182[_0x442076(451)] || {},
        _0x503491 = [_0x442076(441), _0x442076(447), _0x442076(442), _0x442076(463), _0x442076(440), "table", _0x442076(457)];
    for (var _0x2e6262 = 0; _0x2e6262 < _0x503491["length"]; _0x2e6262++) {
      var _0xade831 = _0xa991eb[_0x442076(462)][_0x442076(461)][_0x442076(454)](_0xa991eb),
          _0x202fb0 = _0x503491[_0x2e6262],
          _0x33f2a4 = _0xb55846[_0x202fb0] || _0xade831;
      _0xade831[_0x442076(445)] = _0xa991eb[_0x442076(454)](_0xa991eb), _0xade831[_0x442076(460)] = _0x33f2a4[_0x442076(460)][_0x442076(454)](_0x33f2a4), _0xb55846[_0x202fb0] = _0xade831;
    }
  });
  _0x46ef64();
  console[_0x12b47a(441)](_0x12b47a(464));
}
hi();

这里的数组移位和上述的还是有所不同的,这里是不断对函数里面的数组进行移位操作,所以在解密过程中,还需要从声明数组的函数中取出数组。

接着我们就需要分析解密函数和加密函数。

解密函数看到很直白,就是传入参数,将参数值进行一个减法操作,然后取出数组下标为该参数的值。

接下来就看加密函数,我们很轻易的就能从最后一行中发现,该处应该是最后打印值的地方,那么上述一些步骤到底在做些什么呢?

我们发现,其实该处只是调用了数组,我们只需要找到数组的声明位置即可。

通过调试,可以发现,最后一句才是我们要的结果,通过解密函数可以直接打印出结果。

如果混淆后的代码较短,我们就可以很轻易的分析出实际的逻辑。

# 实战 (某比赛第二题)

将 js 代码放入 babel 网站解析,发现明显的分为四块:

  1. VariableDeclaration
  2. ExpressionStatement
  3. VariableDeclaration
  4. ExpressionStatement

在经过上述分析后,我们可以把 Ob 大至抽象为如下四个阶段:

  1. 定义大数组
  2. 数组移位
  3. 定义解密函数
  4. 函数入口

补充:AST 还原主要是为了让干扰项尽可能减少,让代码逻辑尽可能的变清晰,本身并不能达到秒杀的结果。

# step1:解密函数内存写入

函数入口中的内容大部分都是由解密函数生成的,那么我们首先要做的就是将解密函数写入内存中。

// 定义解密数组名称,与原函数对其
let descrypt_arr_name = '$b'
let descrypt_strfun_js = '';
for(let i=0;i<=2;i++){
    // 加入代码
    descrypt_strfun_js += generator(ast_code.program.body[i], {compact:true}).code
    delete ast_code.program.body[i]
}
// 写入内存
eval(descrypt_strfun_js);
// 此时就可以打印出结果
console.log($b);
# step2:解密函数还原

在内存中包含解数组后,我们就只需要将每一个调用解密数组的地方替换即可,不过在此之前还需要对整体的 ASCII 码类型进行还原,同时若是数组调用类型为 a ['1'+'2'] 这种形式也需要先还原成 a ['12'] 这种形式。

const ASCII_visitor = {
    // 对 ASCII 码进行还原
    StringLiteral(path) {
        if(path.node.extra){
            path.node.extra = undefined;
        }
    },
    NumericLiteral(path) {
        if(path.node.extra){
            path.node.extra = undefined;
        }
    },
    BinaryExpression(path){
        // 二项式计算,都为字符类型,则直接相加
        let left = path.node.left;
        let right = path.node.right;
        let operator = path.node.operator;
        // 字符串相加
        if(types.isStringLiteral(left) && types.isStringLiteral(right) && operator === '+'){
            //path.evaluate () 为节点计算,自动相加里面的 value, 得出三个参数:confident:true 为可相加,deopt: 暂时不清楚,value: 加后的值
            const {value} = path.evaluate();
            path.replaceInline(types.valueToNode(value));
        }
    }
};
traverse(ast_code, ASCII_visitor);
arr_change_visitor = {
    // 解密数组还原
    CallExpression(path) {
        // 调用数组名字与数组函数相同
        if(path.node.callee.name  === descrypt_arr_name && path.node.arguments.length ===2){
            path.replaceInline(types.valueToNode(eval(path.toString())));
        }
    }
}
traverse(ast_code, arr_change_visitor);
# step3:对象置换与写入

接下来就发现,存在对象声明以及对象写入,以如下形式表现:

var a = {};
a['1'] = '1' + '2' + '3';
a['213' + '231'] = function(a,b){
    return a + b
};
var b = a;

这里沿用上述思想,也将对象写入内存中,然后直接替换,由于 '1'+'2'+‘3 类似这种需要采用上述 ASCII_visitor 进一步还原,是为了防

存入内存后,部分函数无法还原。同时,在此基础上还需要加上变量替换、无效代码去除的操作。

// 变量替换
fun_use_visitor = {
    VariableDeclarator(path){
        const func_name = path.node.id.name;
        let binding = path.scope.getBinding(func_name);
        if(!binding){return};
        binding.scope.traverse(binding.scope.block,{
            VariableDeclarator(_path){
            let {id,init} = _path.node;
            if (!types.isIdentifier(init,{name:func_name})) return;
            _path.scope.rename(id.name,func_name);
            }
        })
    }
};
// 无效代码去除
dead_visitor = {
    VariableDeclarator(path) {
        const {id,init} = path.node;
        if(!init){return};
        if(id.name === init.name){
            path.remove();
        }
    }
};
traverse(ast_code, fun_use_visitor);
for(var i=0;i<=10;i++) {
    traverse(ast_code, ASCII_visitor);
}
// 对象内存写入,y1 对应的解密函数的实际值,方便后续观察读写中哪里存在问题
y1 = {};
memeory_visitor = {
    VariableDeclarator(path) {
        if(path.node.init && path.node.init.type === 'ObjectExpression'){
        const func_name = path.node.id.name;
        // 打印当前变量名称'
        eval(path.toString());
        // 对象内存替换,保证内存中的对象键值为 node 类型、
        for (temp of path.node.init.properties){
            let key = temp.key.value;
            let value = temp.value;
            eval(func_name)[key] = value;
        };  
        let binding = path.scope.getBinding(func_name);
        binding.scope.traverse(binding.scope.block, {
            AssignmentExpression(_path) {
                // 筛选需要的对象。判断是否为对象类型
                if(_path.node.left.type  !== 'MemberExpression' || _path.node.left.object.name !== func_name ){return};
                 eval(func_name)[_path.node.left.property.value] = _path.node.right;
                 y1[_path.node.left.property.value] = _path.toString();
                _path.remove();
            }
        }),
            path.remove()
}}
};
// 内部调用对象,将键值对去掉
second_visitor = {
    MemberExpression(path) {
        // 对于值为字符类型的进行替换
        cur_name = path.node.object.name;
        if(!global[cur_name]){return}
        // 如果存在内存中
        if(cur_name && cur_name in global){
            if(!eval(cur_name)[path.node.property.value]){return};
            if(eval(cur_name)[path.node.property.value].property && eval(cur_name)[path.node.property.value].property.type ==='StringLiteral'){
            // 替换节点
            path.replaceInline(types.stringLiteral(eval(cur_name)[path.node.property.value].property.value));
        }else if(eval(cur_name)[path.node.property.value].type === 'StringLiteral'){
                // console.log(eval(cur_name)[path.node.property.value]);
                path.replaceInline(types.stringLiteral(eval(cur_name)[path.node.property.value].value));
            }
        }
    },
    CallExpression(path){
        // 对值为函数类型的进行替换
        if(path.node.callee.type !== 'MemberExpression'){return};
        // 定义调用对象的名称
        var func_name = path.node.callee.object.name;
        // 只调用存在内存中的对象
        if(!global[func_name]){return};
        if(!eval(func_name)[path.node.callee.property.value]){return;};
        if(func_name && func_name in global && eval(func_name)[path.node.callee.property.value].type === 'FunctionExpression'){
            if(eval(func_name)[path.node.callee.property.value].body.body[0].argument.type === 'BinaryExpression'){
                const operator = eval(func_name)[path.node.callee.property.value].body.body[0].argument.operator;
                // console.log(path.node.arguments[0]);
                path.replaceInline(types.binaryExpression(operator, path.node.arguments[0], path.node.arguments[1]));
            }else if(eval(func_name)[path.node.callee.property.value].body.body[0].argument.type === 'CallExpression'){
                // console.log(eval(func_name)[path.node.callee.property.value].body.body[0].argument.arguments.length);
                if(eval(func_name)[path.node.callee.property.value].body.body[0].argument.arguments.length === 0){
                    // 内存函数替换,形如 a () 调用转换
                    var temp_func = eval(func_name)[path.node.callee.property.value].body.body[0].argument;
                    temp_func.callee.name = path.node.arguments[0].name;
                    path.replaceInline(eval(func_name)[path.node.callee.property.value].body.body[0].argument);
                    return;
                };
                if(!eval(func_name)[path.node.callee.property.value].body.body[0].argument.callee.object){return};
                const call_name = eval(func_name)[path.node.callee.property.value].body.body[0].argument.callee.object.name;
                if(!call_name){return};
                var arr = path.node.arguments;
                var a = arr.filter(function(item){return item.name != call_name});
                // 对参数进行切片,第一个为对应的函数名,其余的为参数
                path.replaceInline(types.callExpression(eval(func_name)[path.node.callee.property.value].body.body[0].argument.callee, a));
            }
        }
    }
};
traverse(ast_code, memeory_visitor);
traverse(ast_code, second_visitor);
third_visitor = {
    CallExpression(path){
        // 如果函数对应的部分也为函数调用式
        cur_node = path.node;
        if(!cur_node.callee.object){return};
        var func_name = path.node.callee.object.name;
        if(global[func_name]){
            // console.log(eval(func_name)[path.node.callee.property.value]);
        if(!eval(func_name)[path.node.callee.property.value]){return;};
        if(eval(func_name)[path.node.callee.property.value].body && eval(func_name)[path.node.callee.property.value].body.body[0].argument.type === 'CallExpression'){
            const arg = cur_node.arguments.slice(1);
            path.replaceInline(types.callExpression(cur_node.arguments[0], arg))
        }
    }}
};
traverse(ast_code, third_visitor);
traverse(ast_code, dead_visitor);
// 可判断对象内存写入是否存在问题。
// console.log(y1);
# step4:扫尾工作

经过上述步骤,代码已经压缩成了 240 行,代码 (如下) 的逻辑已经清晰可见,控制流也不是很复杂,暂时没必要用到还原,其中有个 BUG 处:

(function $c(k) {
  var B = function () {
    var Y = !![];
    return function (Z, a0) {
      var a1 = Y ? function () {
        if (a0) {
          var a2 = a0["apply"](Z, arguments);
          a0 = null;
          return a2;
        }
      } : function () {};
      Y = ![];
      return a1;
    };
  }();
  function C(Y, Z) {
    var a0 = (65535 & Y) + (65535 & Z);
    return (Y >> 16) + (Z >> 16) + (a0 >> 16) << 16 | 65535 & a0;
  }
  function D(Y, Z) {
    return Y << Z | Y >>> 32 - Z;
  }
  function E(Y, Z, a0, a1, a2, a3) {
    return C(D(C(C(Z, Y), C(a1, a3)), a2), a0);
  }
  function F(Y, Z, a0, a1, a2, a3, a4) {
    return E(Z & a0 | ~Z & a1, Y, Z, a2, a3, a4);
  }
  function G(Y, Z, a0, a1, a2, a3, a4) {
    return E(Z & a1 | a0 & ~a1, Y, Z, a2, a3, a4);
  }
  function H(Y, Z) {
    let a0 = [99, 111, 110, 115, 111, 108, 101];
    let a1 = "";
    for (let a2 = 0; a2 < a0["length"]; a2++) {
      a1 += String["fromCharCode"](a0[a2]);
    }
    return a1;
  }
  function I(Y, Z, a0, a1, a2, a3, a4) {
    return E(Z ^ a0 ^ a1, Y, Z, a2, a3, a4);
  }
  function J(Y, Z, a0, a1, a2, a3, a4) {
    return E(a0 ^ (Z | ~a1), Y, Z, a2, a3, a4);
  }
  function K(Y, Z) {
    if (Z) {
      return J(Y);
    }
    return H(Y);
  }
  function L(Y, Z) {
    let a0 = "";
    for (let a1 = 0; a1 < Y["length"]; a1++) {
      a0 += String["fromCharCode"](Y[a1]);
    }
    return a0;
  }
  function M(Y, Z) {
    var a0 = "6|1|2|5|0|4|3"["split"]("|");
    var a1 = 0;
    while (!![]) {
      switch (a0[a1++]) {
        case "0":
          qz = [10, 99, 111, 110, 115, 111, 108, 101, 32, 61, 32, 110, 101, 119, 32, 79, 98, 106, 101, 99, 116, 40, 41, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 115, 41, 32, 123, 10, 32, 32, 32, 32, 119, 104, 105, 108, 101, 32, 40, 49, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 114, 40, 105, 61, 48, 59, 105, 60, 49, 49, 48, 48, 48, 48, 48, 59, 105, 43, 43, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 104, 105, 115, 116, 111, 114, 121, 46, 112, 117, 115, 104, 83, 116, 97, 116, 101, 40, 48, 44, 48, 44, 105, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 125, 10, 10, 125, 10, 99, 111, 110, 115, 111, 108, 101, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93, 39, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 402, 32, 116, 111, 83, 116, 114, 105, 110, 103, 40, 41, 32, 123, 32, 91, 110, 97, 116, 105, 118, 101, 32, 99, 111, 100, 101, 93, 32, 125, 39, 10];
          continue;
        case "1":
          var a2 = B(this, function () {
            var a5 = function () {
              var a6 = a5["constructor"]("ZKLFz")()["compile"]("^([^ ]+( +[^ ]+)+)+[^ ]}");
              return !a6["test"](a2);
            };
            return a5();
          });
          continue;
        case "2":
          a2();
          continue;
        case "3":
          try {
            if (global) {
              console["log"]("\u4EBA\u751F\u82E6\u77ED\uFF0C\u4F55\u5FC5python\uFF1F");
            } else {
              while (1) {
                console["log"]("\u4EBA\u751F\u82E6\u77ED\uFF0C\u4F55\u5FC5python\uFF1F");
                debugger;
              }
            }
          } catch (a5) {
            return navigator["vendorSub"];
          }
          continue;
        case "4":
          eval(L(qz));
          continue;
        case "5":
          K();
          continue;
        case "6":
          continue;
      }
      break;
    }
  }
  setInterval(M(), 500);
  function N(Y, Z) {
    Y[Z >> 5] |= 128 << Z % 32, Y[14 + (Z + 64 >>> 9 << 4)] = Z;
    if (qz) {
      var a0,
          a1,
          a2,
          a3,
          a4,
          a5 = 1732584193,
          a6 = -271733879,
          a7 = -1732584194,
          a8 = 271733878;
    } else {
      var a0,
          a1,
          a2,
          a3,
          a4,
          a5 = 0,
          a6 = -0,
          a7 = -0,
          a8 = 0;
    }
    for (a0 = 0; a0 < Y["length"]; a0 += 16) a1 = a5, a2 = a6, a3 = a7, a4 = a8, a5 = F(a5, a6, a7, a8, Y[a0], 7, -680876936), a8 = F(a8, a5, a6, a7, Y[a0 + 1], 12, -389564586), a7 = F(a7, a8, a5, a6, Y[a0 + 2], 17, 606105819), a6 = F(a6, a7, a8, a5, Y[a0 + 3], 22, -1044525330), a5 = F(a5, a6, a7, a8, Y[a0 + 4], 7, -176418897), a8 = F(a8, a5, a6, a7, Y[a0 + 5], 12, 1200080426), a7 = F(a7, a8, a5, a6, Y[a0 + 6], 17, -1473231341), a6 = F(a6, a7, a8, a5, Y[a0 + 7], 22, -45705983), a5 = F(a5, a6, a7, a8, Y[a0 + 8], 7, 1770010416), a8 = F(a8, a5, a6, a7, Y[a0 + 9], 12, -1958414417), a7 = F(a7, a8, a5, a6, Y[a0 + 10], 17, -42063), a6 = F(a6, a7, a8, a5, Y[a0 + 11], 22, -1990404162), a5 = F(a5, a6, a7, a8, Y[a0 + 12], 7, 1804603682), a8 = F(a8, a5, a6, a7, Y[a0 + 13], 12, -40341101), a7 = F(a7, a8, a5, a6, Y[a0 + 14], 17, -1502882290), a6 = F(a6, a7, a8, a5, Y[a0 + 15], 22, 1236535329), a5 = G(a5, a6, a7, a8, Y[a0 + 1], 5, -165796510), a8 = G(a8, a5, a6, a7, Y[a0 + 6], 9, -1069501632), a7 = G(a7, a8, a5, a6, Y[a0 + 11], 14, 643717713), a6 = G(a6, a7, a8, a5, Y[a0], 20, -373897302), a5 = G(a5, a6, a7, a8, Y[a0 + 5], 5, -701558691), a8 = G(a8, a5, a6, a7, Y[a0 + 10], 9, 38016083), a7 = G(a7, a8, a5, a6, Y[a0 + 15], 14, -660478335), a6 = G(a6, a7, a8, a5, Y[a0 + 4], 20, -405537848), a5 = G(a5, a6, a7, a8, Y[a0 + 9], 5, 568446438), a8 = G(a8, a5, a6, a7, Y[a0 + 14], 9, -1019803690), a7 = G(a7, a8, a5, a6, Y[a0 + 3], 14, -187363961), a6 = G(a6, a7, a8, a5, Y[a0 + 8], 20, 1163531501), a5 = G(a5, a6, a7, a8, Y[a0 + 13], 5, -1444681467), a8 = G(a8, a5, a6, a7, Y[a0 + 2], 9, -51403784), a7 = G(a7, a8, a5, a6, Y[a0 + 7], 14, 1735328473), a6 = G(a6, a7, a8, a5, Y[a0 + 12], 20, -1926607734), a5 = I(a5, a6, a7, a8, Y[a0 + 5], 4, -378558), a8 = I(a8, a5, a6, a7, Y[a0 + 8], 11, -2022574463), a7 = I(a7, a8, a5, a6, Y[a0 + 11], 16, 1839030562), a6 = I(a6, a7, a8, a5, Y[a0 + 14], 23, -35309556), a5 = I(a5, a6, a7, a8, Y[a0 + 1], 4, -1530992060), a8 = I(a8, a5, a6, a7, Y[a0 + 4], 11, 1272893353), a7 = I(a7, a8, a5, a6, Y[a0 + 7], 16, -155497632), a6 = I(a6, a7, a8, a5, Y[a0 + 10], 23, -1094730640), a5 = I(a5, a6, a7, a8, Y[a0 + 13], 4, 681279174), a8 = I(a8, a5, a6, a7, Y[a0], 11, -358537222), a7 = I(a7, a8, a5, a6, Y[a0 + 3], 16, -722521979), a6 = I(a6, a7, a8, a5, Y[a0 + 6], 23, 76029189), a5 = I(a5, a6, a7, a8, Y[a0 + 9], 4, -640364487), a8 = I(a8, a5, a6, a7, Y[a0 + 12], 11, -421815835), a7 = I(a7, a8, a5, a6, Y[a0 + 15], 16, 530742520), a6 = I(a6, a7, a8, a5, Y[a0 + 2], 23, -995338651), a5 = J(a5, a6, a7, a8, Y[a0], 6, -198630844), a8 = J(a8, a5, a6, a7, Y[a0 + 7], 10, 1126891415), a7 = J(a7, a8, a5, a6, Y[a0 + 14], 15, -1416354905), a6 = J(a6, a7, a8, a5, Y[a0 + 5], 21, -57434055), a5 = J(a5, a6, a7, a8, Y[a0 + 12], 6, 1700485571), a8 = J(a8, a5, a6, a7, Y[a0 + 3], 10, -1894986606), a7 = J(a7, a8, a5, a6, Y[a0 + 10], 15, -1051523), a6 = J(a6, a7, a8, a5, Y[a0 + 1], 21, -2054922799), a5 = J(a5, a6, a7, a8, Y[a0 + 8], 6, 1873313359), a8 = J(a8, a5, a6, a7, Y[a0 + 15], 10, -30611744), a7 = J(a7, a8, a5, a6, Y[a0 + 6], 15, -1560198380), a6 = J(a6, a7, a8, a5, Y[a0 + 13], 21, 1309151649), a5 = J(a5, a6, a7, a8, Y[a0 + 4], 6, -145523070), a8 = J(a8, a5, a6, a7, Y[a0 + 11], 10, -1120210379), a7 = J(a7, a8, a5, a6, Y[a0 + 2], 15, 718787259), a6 = J(a6, a7, a8, a5, Y[a0 + 9], 21, -343485441), a5 = C(a5, a1), a6 = C(a6, a2), a7 = C(a7, a3), a8 = C(a8, a4);
    return [a5, a6, a7, a8];
  }
  function O(Y) {
    var Z,
        a0 = "",
        a1 = 32 * Y["length"];
    for (Z = 0; Z < a1; Z += 8) a0 += String["fromCharCode"](Y[Z >> 5] >>> Z % 32 & 255);
    return a0;
  }
  function P(Y) {
    var Z = "0|1|4|2|3"["split"]("|");
    var a0 = 0;
    while (!![]) {
      switch (Z[a0++]) {
        case "0":
          var a1,
              a2 = [];
          continue;
        case "1":
          for (a2[(Y["length"] >> 2) - 1] = void 0, a1 = 0; a1 < a2["length"]; a1 += 1) a2[a1] = 0;
          continue;
        case "2":
          for (a1 = 0; a1 < a3; a1 += 8) a2[a1 >> 5] |= (255 & Y["charCodeAt"](a1 / 8)) << a1 % 32;
          continue;
        case "3":
          return a2;
        case "4":
          var a3 = 8 * Y["length"];
          continue;
      }
      break;
    }
  }
  function Q(Y) {
    return O(N(P(Y), 8 * Y["length"]));
  }
  function R(Y) {
    var Z,
        a0,
        a1 = "0123456789abcdef",
        a2 = "";
    for (a0 = 0; a0 < Y["length"]; a0 += 1) Z = Y["charCodeAt"](a0), a2 += a1["charAt"](Z >>> 4 & 15) + a1["charAt"](15 & Z);
    return a2;
  }
  function S(Y) {
    return unescape(encodeURIComponent(Y));
  }
  function T(Y) {
    return Q(S(Y));
  }
  function U(Y) {
    return R(T(Y));
  }
  function V(Y, Z, a0) {
    M();
    return Z ? a0 ? H(Z, Y) : y(Z, Y) : a0 ? T(Y) : U(Y);
  }
  function W(Y, Z) {
    document["cookie"] = "m" + M() + "=" + V(Y) + "|" + Y + "; path=/";
    location["reload"]();
  }
  function X(Y, Z) {
    return Date["parse"](new Date());
  }
  W(X());
})();

PS: 运行会报 ZKLFz 处的错误,可能是由于对 'y ["ZKLFz"] = "return /\" + this + \"/"' 这个还原的时候出现了一点小问题,通过分析该处代码,很明显这里是在对什么东西进行一个检测,然后返回的一个是 boolean 型变量,将代码修改即可结束。

最后将其中的环境检测补掉,最后即破解结束。

// 修改后完整代码如下
// 提取 hook 一个 console.log;
console_hk = console.log;
var B = function () {
    var Y = !![];
    return function (Z, a0) {
        var a1 = Y ? function () {
            if (a0) {
                var a2 = a0["apply"](Z, arguments);
                a0 = null;
                return a2;
            }
        } : function () {
        };
        Y = ![];
        return a1;
    };
}();
function C(Y, Z) {
    var a0 = (65535 & Y) + (65535 & Z);
    return (Y >> 16) + (Z >> 16) + (a0 >> 16) << 16 | 65535 & a0;
}
function D(Y, Z) {
    return Y << Z | Y >>> 32 - Z;
}
function E(Y, Z, a0, a1, a2, a3) {
    return C(D(C(C(Z, Y), C(a1, a3)), a2), a0);
}
function F(Y, Z, a0, a1, a2, a3, a4) {
    return E(Z & a0 | ~Z & a1, Y, Z, a2, a3, a4);
}
function G(Y, Z, a0, a1, a2, a3, a4) {
    return E(Z & a1 | a0 & ~a1, Y, Z, a2, a3, a4);
}
function H(Y, Z) {
    let a0 = [99, 111, 110, 115, 111, 108, 101];
    let a1 = "";
    for (let a2 = 0; a2 < a0["length"]; a2++) {
        a1 += String["fromCharCode"](a0[a2]);
    }
    return a1;
}
function I(Y, Z, a0, a1, a2, a3, a4) {
    return E(Z ^ a0 ^ a1, Y, Z, a2, a3, a4);
}
function J(Y, Z, a0, a1, a2, a3, a4) {
    return E(a0 ^ (Z | ~a1), Y, Z, a2, a3, a4);
}
function K(Y, Z) {
    if (Z) {
        return J(Y);
    }
    return H(Y);
}
function L(Y, Z) {
    let a0 = "";
    for (let a1 = 0; a1 < Y["length"]; a1++) {
        a0 += String["fromCharCode"](Y[a1]);
    }
    return a0;
}
function M(Y, Z) {
    var a0 = "6|1|2|5|0|4|3"["split"]("|");
    var a1 = 0;
    while (!![]) {
        switch (a0[a1++]) {
            case "0":
                qz = [10, 99, 111, 110, 115, 111, 108, 101, 32, 61, 32, 110, 101, 119, 32, 79, 98, 106, 101, 99, 116, 40, 41, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 115, 41, 32, 123, 10, 32, 32, 32, 32, 119, 104, 105, 108, 101, 32, 40, 49, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 114, 40, 105, 61, 48, 59, 105, 60, 49, 49, 48, 48, 48, 48, 48, 59, 105, 43, 43, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 104, 105, 115, 116, 111, 114, 121, 46, 112, 117, 115, 104, 83, 116, 97, 116, 101, 40, 48, 44, 48, 44, 105, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 125, 10, 10, 125, 10, 99, 111, 110, 115, 111, 108, 101, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93, 39, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 402, 32, 116, 111, 83, 116, 114, 105, 110, 103, 40, 41, 32, 123, 32, 91, 110, 97, 116, 105, 118, 101, 32, 99, 111, 100, 101, 93, 32, 125, 39, 10];
                continue;
            case "1":
                var a2 = B(this, function () {
                    var a5 = function () {
                        //var a6 = a5["constructor"]("ZKLFz")()["compile"]("^([^ ]+( +[^ ]+)+)+[^ ]}");
						//return !a6 ["test"](a2); 浏览器里为 true 直接替换
                        return true;
                    };
                    return a5();
                });
                continue;
            case "2":
                a2();
                continue;
            case "3":
                // 去掉无线 debugger;
                // try {
                // 这里对 node 的 global 进行了一个检测
                //     if (global) {
                //         console["log"]("\u4EBA\u751F\u82E6\u77ED\uFF0C\u4F55\u5FC5python\uFF1F");
                //     } else {
                //         while (1) {
                //             console["log"]("\u4EBA\u751F\u82E6\u77ED\uFF0C\u4F55\u5FC5python\uFF1F");
                //             debugger;
                //         }
                //     }
                // } catch (a5) {
                //     return navigator["vendorSub"];
                // }
                //navigator ["vendorSub"] 这个在浏览器里面就为 '' 所以直接返回这个。
                    return ''
                continue;
            case "4":
                eval(L(qz));
                continue;
            case "5":
                K();
                continue;
            case "6":
                var a3 = a3;
                continue;
        }
        break;
    }
}
// setInterval(M(), 500);
function N(Y, Z) {
    Y[Z >> 5] |= 128 << Z % 32, Y[14 + (Z + 64 >>> 9 << 4)] = Z;
    if (qz) {
        var a0,
            a1,
            a2,
            a3,
            a4,
            a5 = 1732584193,
            a6 = -271733879,
            a7 = -1732584194,
            a8 = 271733878;
    } else {
        var a0,
            a1,
            a2,
            a3,
            a4,
            a5 = 0,
            a6 = -0,
            a7 = -0,
            a8 = 0;
    }
    for (a0 = 0; a0 < Y["length"]; a0 += 16) a1 = a5, a2 = a6, a3 = a7, a4 = a8, a5 = F(a5, a6, a7, a8, Y[a0], 7, -680876936), a8 = F(a8, a5, a6, a7, Y[a0 + 1], 12, -389564586), a7 = F(a7, a8, a5, a6, Y[a0 + 2], 17, 606105819), a6 = F(a6, a7, a8, a5, Y[a0 + 3], 22, -1044525330), a5 = F(a5, a6, a7, a8, Y[a0 + 4], 7, -176418897), a8 = F(a8, a5, a6, a7, Y[a0 + 5], 12, 1200080426), a7 = F(a7, a8, a5, a6, Y[a0 + 6], 17, -1473231341), a6 = F(a6, a7, a8, a5, Y[a0 + 7], 22, -45705983), a5 = F(a5, a6, a7, a8, Y[a0 + 8], 7, 1770010416), a8 = F(a8, a5, a6, a7, Y[a0 + 9], 12, -1958414417), a7 = F(a7, a8, a5, a6, Y[a0 + 10], 17, -42063), a6 = F(a6, a7, a8, a5, Y[a0 + 11], 22, -1990404162), a5 = F(a5, a6, a7, a8, Y[a0 + 12], 7, 1804603682), a8 = F(a8, a5, a6, a7, Y[a0 + 13], 12, -40341101), a7 = F(a7, a8, a5, a6, Y[a0 + 14], 17, -1502882290), a6 = F(a6, a7, a8, a5, Y[a0 + 15], 22, 1236535329), a5 = G(a5, a6, a7, a8, Y[a0 + 1], 5, -165796510), a8 = G(a8, a5, a6, a7, Y[a0 + 6], 9, -1069501632), a7 = G(a7, a8, a5, a6, Y[a0 + 11], 14, 643717713), a6 = G(a6, a7, a8, a5, Y[a0], 20, -373897302), a5 = G(a5, a6, a7, a8, Y[a0 + 5], 5, -701558691), a8 = G(a8, a5, a6, a7, Y[a0 + 10], 9, 38016083), a7 = G(a7, a8, a5, a6, Y[a0 + 15], 14, -660478335), a6 = G(a6, a7, a8, a5, Y[a0 + 4], 20, -405537848), a5 = G(a5, a6, a7, a8, Y[a0 + 9], 5, 568446438), a8 = G(a8, a5, a6, a7, Y[a0 + 14], 9, -1019803690), a7 = G(a7, a8, a5, a6, Y[a0 + 3], 14, -187363961), a6 = G(a6, a7, a8, a5, Y[a0 + 8], 20, 1163531501), a5 = G(a5, a6, a7, a8, Y[a0 + 13], 5, -1444681467), a8 = G(a8, a5, a6, a7, Y[a0 + 2], 9, -51403784), a7 = G(a7, a8, a5, a6, Y[a0 + 7], 14, 1735328473), a6 = G(a6, a7, a8, a5, Y[a0 + 12], 20, -1926607734), a5 = I(a5, a6, a7, a8, Y[a0 + 5], 4, -378558), a8 = I(a8, a5, a6, a7, Y[a0 + 8], 11, -2022574463), a7 = I(a7, a8, a5, a6, Y[a0 + 11], 16, 1839030562), a6 = I(a6, a7, a8, a5, Y[a0 + 14], 23, -35309556), a5 = I(a5, a6, a7, a8, Y[a0 + 1], 4, -1530992060), a8 = I(a8, a5, a6, a7, Y[a0 + 4], 11, 1272893353), a7 = I(a7, a8, a5, a6, Y[a0 + 7], 16, -155497632), a6 = I(a6, a7, a8, a5, Y[a0 + 10], 23, -1094730640), a5 = I(a5, a6, a7, a8, Y[a0 + 13], 4, 681279174), a8 = I(a8, a5, a6, a7, Y[a0], 11, -358537222), a7 = I(a7, a8, a5, a6, Y[a0 + 3], 16, -722521979), a6 = I(a6, a7, a8, a5, Y[a0 + 6], 23, 76029189), a5 = I(a5, a6, a7, a8, Y[a0 + 9], 4, -640364487), a8 = I(a8, a5, a6, a7, Y[a0 + 12], 11, -421815835), a7 = I(a7, a8, a5, a6, Y[a0 + 15], 16, 530742520), a6 = I(a6, a7, a8, a5, Y[a0 + 2], 23, -995338651), a5 = J(a5, a6, a7, a8, Y[a0], 6, -198630844), a8 = J(a8, a5, a6, a7, Y[a0 + 7], 10, 1126891415), a7 = J(a7, a8, a5, a6, Y[a0 + 14], 15, -1416354905), a6 = J(a6, a7, a8, a5, Y[a0 + 5], 21, -57434055), a5 = J(a5, a6, a7, a8, Y[a0 + 12], 6, 1700485571), a8 = J(a8, a5, a6, a7, Y[a0 + 3], 10, -1894986606), a7 = J(a7, a8, a5, a6, Y[a0 + 10], 15, -1051523), a6 = J(a6, a7, a8, a5, Y[a0 + 1], 21, -2054922799), a5 = J(a5, a6, a7, a8, Y[a0 + 8], 6, 1873313359), a8 = J(a8, a5, a6, a7, Y[a0 + 15], 10, -30611744), a7 = J(a7, a8, a5, a6, Y[a0 + 6], 15, -1560198380), a6 = J(a6, a7, a8, a5, Y[a0 + 13], 21, 1309151649), a5 = J(a5, a6, a7, a8, Y[a0 + 4], 6, -145523070), a8 = J(a8, a5, a6, a7, Y[a0 + 11], 10, -1120210379), a7 = J(a7, a8, a5, a6, Y[a0 + 2], 15, 718787259), a6 = J(a6, a7, a8, a5, Y[a0 + 9], 21, -343485441), a5 = C(a5, a1), a6 = C(a6, a2), a7 = C(a7, a3), a8 = C(a8, a4);
    return [a5, a6, a7, a8];
}
function O(Y) {
    var Z,
        a0 = "",
        a1 = 32 * Y["length"];
    for (Z = 0; Z < a1; Z += 8) a0 += String["fromCharCode"](Y[Z >> 5] >>> Z % 32 & 255);
    return a0;
}
function P(Y) {
    var Z = "0|1|4|2|3"["split"]("|");
    var a0 = 0;
    while (!![]) {
        switch (Z[a0++]) {
            case "0":
                var a1,
                    a2 = [];
                continue;
            case "1":
                for (a2[(Y["length"] >> 2) - 1] = void 0, a1 = 0; a1 < a2["length"]; a1 += 1) a2[a1] = 0;
                continue;
            case "2":
                for (a1 = 0; a1 < a3; a1 += 8) a2[a1 >> 5] |= (255 & Y["charCodeAt"](a1 / 8)) << a1 % 32;
                continue;
            case "3":
                return a2;
            case "4":
                var a3 = 8 * Y["length"];
                continue;
        }
        break;
    }
}
function Q(Y) {
    return O(N(P(Y), 8 * Y["length"]));
}
function R(Y) {
    var Z,
        a0,
        a1 = "0123456789abcdef",
        a2 = "";
    for (a0 = 0; a0 < Y["length"]; a0 += 1) Z = Y["charCodeAt"](a0), a2 += a1["charAt"](Z >>> 4 & 15) + a1["charAt"](15 & Z);
    return a2;
}
function S(Y) {
    return unescape(encodeURIComponent(Y));
}
function T(Y) {
    return Q(S(Y));
}
function U(Y) {
    return R(T(Y));
}
function V(Y, Z, a0) {
    M();
    return Z ? a0 ? H(Z, Y) : y(Z, Y) : a0 ? T(Y) : U(Y);
}
function W(Y, Z) {
    var cookie = "m" + M() + "=" + V(Y) + "|" + Y + "; path=/";
    console_hk(cookie);
}
function X(Y, Z) {
    return Date["parse"](new Date());
}
W(X());

补充:上述只是对简单的 ob 进行了一个还原,由于其控制流不是很复杂,所以没必要进一步还原控制流,同时,在解密函数这部分,有的是解密数组,有的是解密函数,灵活变通即可。

# 实战

# 某验

# 代码分析

受害者地址:https://www.geetest.com/demo/slide-bind.html

将代码放入在线网站解析,我们可以

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

Mr2 WeChat Pay

WeChat Pay