中间人技巧:使用AST自动去除OB混淆的反调试逻辑

大家好,今天分享一个使用 AST 动态去除 OB 混淆反调试的方案。

OB 混淆

OB 混淆已经提过很多次了,就不多说了。OB 混淆自带了大量的反调试手段,有的时候只要控制台一打开,还没做其他操作,浏览器就直接崩溃了。

image-20250409214427866

这种情况下严重影响调试,当然这个时候可以查看这篇文章来替换文件,手动去除反调试后再使用。

动态密钥

但是我之前分析的那个样本,其中有两行密钥是动态的,没看过的小伙伴可以先看一下之前的文章:JS逆向实战:OB 混淆代码分析+快速绕过 OB 混淆的格式化检测的方法

image-20250411221523131

这个样本中,几百行代码只有这两行赋值语句是有效的,其他的全部都是 OB 的反调试!并且这两行赋值语句还是动态变化的,每次带上 cookie 请求后都会返回一个新的值,这种情况下就无法使用静态替换的方式来调试代码了。因为静态替换是滞后的,无法实时的去除反调试语句,在下载代码替换后请求就已经发出去了,如果密钥不对的话整个请求就失败了。

这个时候我想到的方案就是动态的去除反调试。如果是静态的情况下,可以使用 AST 抠出解密函数然后解密字符串替换回去,但是动态的情况下就无法扣解密函数了,那就只有先忽略字符串解密了,因为目标是去除反调试,让我们可以在浏览器调试代码。

动态 AST

动态 AST 去除反调试使用的是中间人的方式,不了解的小伙伴可以先看这篇文章:中间人的妙用:自动反混淆 JS 代码并移除反调试代码

大概的思路就是先在 proxyman 中导入 AST 会用到的 babel 库,然后编写 AST 代码去根据特定的规则去除一些反调试函数。

image-20250411222658746

分析代码结构,通过前面的分析可知,除了两行 window 的赋值语句,其他都可以删除,但是又不能删除解密函数,因为解密函数在 window 属性赋值的时候会用到,所以只需要移除除了解密函数以外的所有函数即可。

在移除函数的时候,主要针对的是函数的执行,函数的定义不删除也无所谓,不影响调试。

来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const {types, parser, traverse, generator} = require('@users/bundle.js')

async function onRequest(context, url, request) {
console.log(url);
return request;
}

async function onResponse(context, url, request, response) {
let ast = parser.parse(response.body);
//移除setInterval
let removeSetInterval = {
CallExpression: {
exit(path) {
let {node} = path;
if (node.callee.name === 'setInterval') {
path.remove();
}
}
}
}
traverse(ast, removeSetInterval);

traverse(ast, {
CallExpression: {
enter(path) {
let {node} = path;
if (types.isFunctionExpression(node.callee) && node.arguments.length === 0) {
path.remove();
}
if (node.arguments.length === 0 && types.isIdentifier(node.callee) && types.isExpressionStatement(path.parentPath)&&
types.isProgram(path.parentPath.parentPath)) {
path.remove();
}
if (node.arguments.length === 2 && types.isIdentifier(node.callee) && types.isThisExpression(node.arguments[0]) &&
types.isFunctionExpression(node.arguments[1])) {
path.remove();
}
}
}
})

response.body = generator(ast, opts = {
"compact": true
}).code
return response;
}

主要有三种情况需要移除,一个是自执行函数,注意是无参的自执行函数,有参数的自执行函数是解密函数,不能移除。另一种情况是参数中带有 this 的函数,可以移除。最后一种情况就是整个文件最外层的函数调用需要移除,还有一个 setInterval 比较简单就不多说了。

注:以上情况可按需调整,以上代码仅限我遇到的样本才能生效,如有其他脚本可以自行调整。

将以上代码整体复制到 proxyman 的脚本功能中,重新刷新网页,会发现不会再触发反调试语句了,可以正常调试了。

总结

以后遇到类似情况的小伙伴可以尝试一下,如果可以做到在不扣解密函数的情况下自动解密字符串就更好了。

本文章首发于个人博客 LLLibra146’s blog

本文作者:LLLibra146

更多文章请关注公众号 (LLLibra146):LLLibra146

版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!

本文链接
https://blog.d77.xyz/archives/a579ff6d.html