记一次js混淆逆向

事情来自于知乎的一个爬虫问题,刚好刷到了,看无人回答,好奇就点进去看了看。

问题链接和对应网站这里就不提了,感兴趣的可以根据文中关键字进行搜索,相信细心的你可以找到的😉.

这里先贴下工具,Chrome 最新版,抓包用 Charles,看 HTML 也用 Pycharm(🤣只是格式化一下,自带的就够用了),写代码验证还是 Pycharm。

注:这里并不讨论抓取数据,只分析相关混淆的流程和思路。

下面开始,为了防止干扰,打开一个新的无痕窗口,配置代理,打开抓包软件,访问对应的网址。

可以看到这里自动进行了两次同样的 URL 访问,查看源码发现第一次访问返回的是一个混淆过的 js 代码。

第二次访问则是正常返回了我们所需要的网页。

通过对比,发现两次请求时,第一次的请求头中完全没有 Cookie 字段,这是因为无痕模式没有访问过这个网页。

第二次请求时,带上了一些 Cookie,所以正常返回了我们所需要的数据。

接下来查看第二次请求多出来的两个 Cookie 是来自于哪里的。一般来说,Cookie 有两种来源,一是通过响应包的 Set-Cookie 字段来设置,浏览器会自动保存对应的 Cookie 到本地,然后再下次请求时会带上。另一种就是通过 js 脚本在本地生成,然后写入到本地。

查看第一个包的响应,发现只有一个 Cookie 是服务器返回的,并且在第一个响应包的中发现了如下代码,基本可以确定,正常返回网页所需要的两个 Cookie 中,acw_tc 是服务器返回的,acw_sc_v2 是本地生成的。

手动验证, 再次刷新网页,发现不会再返回混淆过的 js 代码,只会返回对应的网页。清除掉所有 Cookie 后,发现会通过 reload 的方式访问两次,到目前基本可以确定 Cookie 的生成流程。

确定好 Cookie 生成流程后,开始处理混淆过的代码。

为了方便后面的分析,先保存 Response 包到本地,开启 Map Local。

清除掉所有的 Cookie,刷新页面,打开浏览器开发者工具,发现有反调试,先过掉。

查看调用栈可以找到调用的位置,暂时只看到了在 try 中调用了反调试,所以修改本地的 test.html 文件, 删除掉 _0x355d23(0x0); 这一行。

由于我们开启了 Map Local,所有的访问都会重定向到我们的本地网页,加上混淆代码最后的 reload,过掉反调试会导致无限 reload(🤣不要问我怎么知道的),顺便删掉 reload 防止无限 reload。

程序正常执行,可以正常计算出对应的 Cookie。

下面要确定函数调用栈是怎样的,我们使用 debugger 语句来查找。

再次刷新页面,程序停在了 debugger 这一行,通过右边的 Call Stack 可以看到函数的调用关系。

最终确定,Cookie 的值就是这三行语句计算出来的。

其中 '\x72\x65\x6c\x6f\x61\x64\x28\x61\x72\x67\x32\x29'的值就是 “reload(arg2)”,也就是调用了设置 Cookie 的函数。

接下来就是分析代码,先将十六进制显示的代码转成正常的字符串,方便分析。

使用 Python 正则进行替换。

1
2
3
4
5
6
7
8
9
import re

with open(r'D:\xxx\test.html', 'r') as f:
html = f.read()
result = re.sub(r"'((?:\\x..)+?)'", lambda x: "'" + bytes.fromhex(x.group(1).replace('\\x', '')).decode() + "'",
html)
print(result)
with open(r'D:\xxx\test2.html', 'w') as ff:
ff.write(result)

注:这里将替换好的代码输出到了 test2.html,没有替换源文件,这是因为 js 代码中有反调试措施,如果格式化后代码就不能正常执行了,会死循环导致调试器崩溃。所以 Map Local 返回的网页不能格式化,但是可以在开发者工具中进行格式化查看(因为实际执行的代码没有格式化,所以可以正常执行),对应替换好的代码可以进行下一步的分析。

先不要着急格式化,因为有的正则表达式中有单引号,所以会导致出现语法错误,我们只需要将标红的字符串中的单引号前边加一个反斜线进行转义就可以了。

处理完后的效果

然后再进行格式化就可以得到比较方便好看的代码了。

这里我无法写出特别具体的分析流程,因为中间花了大量的时间进行调试分析。只能尽量说清楚函数的调用关系。

1
2
3
var _0x23a392 = arg1[parsedata('0x19', 'Pg54')]();
arg2 = _0x23a392[parsedata('0x1b', 'z5O&')](_0x5e8b26);
setTimeout('reload(arg2)', 0x2);

首先第一行的话,parsedata函数是我自己后来手动重命名的,方便识别,因为这个函数在代码中大量使用, 应该是一个解密函数。

解密之后的代码就变成了 arg1[“unsbox”](),我们选中它,可以看到它是一个函数。

可以看到这里给字符串 arg1 绑定了一个属性,值就是一个函数。arg1 的值在代码的开头,很好找。

执行时函数中的 this 就是字符串本身。动态调试起来可以很直观的看到函数的调用流程。

到这里第一行基本上就分析结束了。

同理可以找到第二个函数的调用位置。

将 js 翻译成 Python,可以得到和网页计算相同的值。

到这里其实已经得到 Cookie 的值了,但是这里的参数是从网页上复制下来的,所以还需要分析下解密的代码,因为所有的数据都被加密存储在了 _0x4818 里边。

但是分析中发现一个问题,4818 这个变量的索引位置不对,通过正确的索引取到了错误的数据。最后发现在通过索引获取数据之前,4818 中数据的位置被调整过,通过开头的循环代码将位置调整为正确的位置,也就是说后面我们使用 4818 的时候就已经是正确的结果了。

解密代码已经很清楚了,这里就不过多分析了。直接翻译成 Python 就可以了。

解密结果如下:

OK,到这里基本上就已经分析完了,只要取出 js 中的对应数据输入,就可以得到对应的 Cookie 的值。

这次分析大概花费了一个下午加一个晚上的时间,刚开始以为很容易,但是后来发现并不是那么回事。其中好多语法我还不会🤣,包括给字符串绑定属性之类的都是现查的,所以用了比较多的时间。

整个分析下来这次的混淆并没有那么困难,有点耐心的话应该都可以搞定,也算是我自己的一个小入门吧。

混淆之后也就几百行代码,写个 Python 来用还算是比较简单,但是如果是比较大的项目几千上万行的那种混淆的 js,就不推荐这么干了。

注:以上分析流程及结果仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果与作者无关。

本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
更多文章请关注:qrcode
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!
本文链接
https://blog.d77.xyz/archives/e83cc3ea.html