现在一些网站对 JavaScript 代码采取了一定的保护措施,比如变量名混淆、执行逻辑混淆、反调试、核心逻辑加密等,有的还对数据接口进行了加密,这次案例是通过补环境过加密。
本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!
目标网址:
aHR0cHM6Ly9tb2JpbGUueWFuZ2tlZHVvLmNvbS8=
数据接口:
aHR0cHM6Ly9tb2JpbGUueWFuZ2tlZHVvLmNvbS9wcm94eS9hcGkvc2VhcmNoX3N1Z2dlc3Q=
以上均做了脱敏处理,Base64 编码及解码方式:
- import base64
- # 编码
- # result = base64.b64encode('待编码字符串'.encode('utf-8'))
- # 解码
- result = base64.b64decode('待解码字符串'.encode('utf-8'))
- print(result)
一般情况下,JavaScript 逆向分为三步:
接下来开始正式进行案例分析:
打开开发者人员工具,进行抓包,从响应预览中可以看到搜索内容的下拉列表的接口为 search_suggest?pdduid=XXX&query=XXX:

负载中可以看到一些接口 url 的参数,query 就是搜索栏中输入的内容,很明显,anti_content 参数经过加密,接下来就需要对这个参数进行逆向分析:

anti_content 是接口 url 中的参数,且下拉数据是通过 ajax 加载的,所以通过 Hook 或者 XHR 断点的方式都能成功定位,不过大道至简,这里直接全局搜索 anti_content,因为只有一个 js 文件中包含这个关键字:

点击 SearchViewUI.js 文件跟进去,然后通过左下角的 { } 对其进行格式化操作,ctrl + f 局部搜索 anti_content 关键字,只有一个结果,在第1949 行,在 第1939 行打下断点调试:

可以看到 f 即 anti_content 参数的值,f 定义在第 1938 行,f = e.sent,此时加密后的参数已经生成了,所以需要向上跟栈,看看是哪加密的:

跟到 vendors_xxx 文件的第 19158 行,这里就是个异步 Promise,在 19619 行打下断点会发现此时的 e 还是 undefined,没生成加密参数:

XMLHttpRequest 这种异步的都很麻烦跳来跳去,类似如下几个地方,跟栈需要耐心:
- var l = p(e, t, n);
- return this._invoke(t, e)
- return e.apply(this, arguments)
- getAntiContent
- initRiskCntroller
- messagePackSync
跟到后面会跳转到一个叫 react_anti_XXX 的文件,这个文件还记录了鼠标移动轨迹:

在该文件的第 1791 行是 anti_content 参数的关键加密位置,这部分经过混淆,不过也没必要解混淆:

控制台打印看看,Lt() 即返回这个值的函数:

滑到开头,会发现这是个通过 webpack 打包了的 js 文件,框出来的部分就是模块加载器,有个 webpack 很明显的标志:
return t[e].call(o.exports, o, o.exports, r)

webpack 相关推荐看看这篇文章:JavaScript 之 webpack 加密代码扣取
这里为文件本身的加载器调用,删掉,后面调用加载器的时候再传值进去就行了:

现在只需要把加载器导出为全局变量,然后将该函数改为自执行调用即可:
- !(function (t) {
- var n = {};
- function r(e) {
- if (n[e])
- return n[e].exports;
- var o = n[e] = {
- i: e,
- l: !1,
- exports: {}
- };
- return t[e].call(o.exports, o, o.exports, r),
- o.l = !0,
- o.exports
- };
-
- window.rose = r;
- })
- ...
- ...
- ...
- let anti_content = window.rose(4);
- result = new anti_content();
将此时的 js 代码放到浏览器环境中运行,在 result 处打断点,断住后跟进到第 1830 行:

会跳转到如下代码处:
- var Vt = new Bt;
- t[a(831, "C0uu")] = function() {
- var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}
- , n = a;
- return t[w] && X && Vt[n(548, "YD8i")](t[w]),
- Vt
- }
Vt 继承了 Bt 的方法,返回值部分 t[w] 是一个时间戳,w 为 serverTime:

跟到 Vt[n(548, "YD8i")] 中看看,这里是通过 now() 方法生成的时间戳:

所以可以写成如下样式:
- let anti_content = window.rose(4);
- result = new anti_content({ serverTime: new Date().getTime() });
Vt 原型下的 messagePack 函数调用了刚刚的加密函数 Lt():

所以来试试 result 调用 messagePack 函数能否得到加密值:

在控制台中成功打印出了加密结果,证明加密方法就是这样的,接下补环境即可,整理后的 js 文件如下:
- !(function (t) {
- var n = {};
- function r(e) {
- if (n[e])
- return n[e].exports;
- var o = n[e] = {
- i: e,
- l: !1,
- exports: {}
- };
- return t[e].call(o.exports, o, o.exports, r),
- o.l = !0,
- o.exports
- };
-
- window.rose = r;
-
- })([function (t, n, r) {
-
- // 此部分与原 js 文件一样, 在此省略
-
- ])
-
- let anti_content = window.rose(4);
- result = new anti_content({ serverTime: new Date().getTime() });
-
补完环境后即可在 node 环境下成功得到加密结果:

python 调用:

欢迎提供更多需要 anti_content 参数的接口 ~