背景:
在分析某站点接口时发现以前发现漏洞的JS修复后进行了强混淆,看起来十分抽象
于是乎搁置在一边没有继续分析,直到前几日在图书馆发现了小肩膀大佬写的爬虫混淆AST对抗,书中描述的几种混淆方式与该站点使用的十分相似,遂尝试使用AST对该JS进行一定程度的还原
本篇针对该JS中的字符串混淆进行还原
字符串是如何混淆的
解密方式
想要对字符串反混淆就要先分析该样本是如何对字符串进行混淆的
以一个字符串的解密为例子,可以发现他将字符串解密拆分成一串函数调用并对立即数进行减法操作来防止通用解密
而处于全局作用域的_0x1f1a68
实际上也是对另一个函数的调用
1 | function _0x1f1a68(_0x1be822, _0x79fd7, _0x340561, _0x170aa8, _0x35407a) { |
经过在VSCode
中对每个字符串解密函数查找定义,发现所有的字符串解密最终都是调用的_0x4903
由于每个函数的调用时机跟作用域都不同,获取每一个字符串解密函数的结果是不明智的
于是这里需要实现的第一个功能就是将每一个字符串的解析还原成对_0x4903
的调用,也就是将不同字符串解密函数的调用替换成对最根本的解密函数_0x4903
的幂等形式
还原
函数调用还原实现
举个例子
1 | function _0x3cb10b(_0x9056d3, _0xd6da67, _0x4e8aa3, _0x575cfa, _0x50067e) { |
我们的目标是将
_0x362f86(0x9a3, 0xef2, '1vkx', 0x369, 0xb40)
转换成
_0x3cb10b(0x9a3- 0x1a0, 0x9a3 - -0x370, '1vkx', 0x369 - 0x19c, 0xb40 - 0x120);
继而转换成
_0x1f1a68(0x9a3- 0x1a0 - 0x1ca, 0x9a3 - -0x370 - 0x97, '1vkx', 0x369 - 0x19c - 0x13c, 0x9a3 - -0x370 - 0x119);
最终转换成
_0x4903(0x9a3 - -0x370 - 0x119 - 0x252, '1vkx');
那么如何使用AST实现呢,为了尽可能实现上下文无关减少状态,这里采用像示例中的一样一层一层的处理
在代码实现上我将其分为了多个部分
1 | function replaceArgsToIndex(funcargs, arg) { |
第一步是将函数内的参数名转换成参数下标,这样就可以从CallExpression
中直接用下标获取对应的参数进行表达式替换,这里处理了BinaryExpression
是因为参数中存在减法表达式的情况,但变量永远在第一位,所以递归到最左面的变量再进行处理,同时如果参数已经被转化成argN的形式便不做处理。
这里放一下关于二值表达式的表示
如图,每个红框都是一个二值表达式,外层的二值表达式将内层的二值表达式作为左值,所以当变量为
xxx - 0x123 -0x456 -0x789
形式时我们要递归的获取左值。
转换后的形式为:
1 | function _0x1f1a68(_0x1be822, _0x79fd7, _0x340561, _0x170aa8, _0x35407a) { |
这样就可以检测所有对0x1f1a68
的调用,获取其中的第5个参数和第三个参数并把其放入_0x4903
调用的对应位置,然后将0x1f1a68
替换为_0x4903
将参数下标替换成参数的代码如下
1 | function convertIndexToArg(funcargs, arg) { |
其中funcargs
为CallExpression
中的参数,该函数同样递归处理二值表达式
实现函数展开只需要遍历所有的函数定义,判断是否满足混淆函数的格式,然后通过binding寻找他的调用表达式进行处理,下面为代码实现
1 | let doFlatten = { |
由于每次我们仅处理一层,所以这里多次处理,这样就不必为先后顺序发愁
1 | for (let level = 0; level < 3; level++) { |
字符串函数调用
上一步中我们将字符串混淆替换成了形似_0x4903(0x9a3 - -0x370 - 0x119 - 0x252, '1vkx');
的调用,这一步中我们要将对该函数的调用还原为字符串。
以下为_0x4903
的实现
1 | function _0x4903(_0x41f1e9, _0x3130bc) { |
这里不关心她如何实现,只要能够调用就好了,不过也进行了分析写在注释中
绕过其中的检测后就可以放到文件里然后直接引入该js执行了
为其添加导出
1 | module.exports = { |
1 | import { _0x4903 } from './strdeec' |
效果
原JS
1 | var _0x36ac29 = { |
还原后
1 | var _0x36ac29 = { |
至此成功还原字符串加密,此部分结束。