# 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' 的形式,这样就需要用到常量折叠的方式。
常量折叠可以抽象为如下几个步骤:
- 筛选操作节点
- 声明节点属性
- 判断节点类型
- 计算
- 替换
// 对于字符串类型的常量堆叠 | |
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 块构成:
- VariableDeclaration
- VariableDeclaration
- 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 给代码块,按顺序依次为:
- VariableDeclaration
- ExpressionStatement
- FunctionDeclaration
- FunctionDeclaration
- 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(); |
ExpressionStatement
FunctionDeclaration
FunctionDeclaration
FunctionDeclaration
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 网站解析,发现明显的分为四块:
- VariableDeclaration
- ExpressionStatement
- VariableDeclaration
- ExpressionStatement
在经过上述分析后,我们可以把 Ob 大至抽象为如下四个阶段:
- 定义大数组
- 数组移位
- 定义解密函数
- 函数入口
补充: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
将代码放入在线网站解析,我们可以