前言
现在一些网站对 JavaScript 代码采取了一定的保护措施,比如变量名混淆、执行逻辑混淆、反调试、核心逻辑加密等,有的还对数据接口进行了加密,这次案例是通过补环境过加密。
声明
本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!
案例分析
目标网址:
aHR0cHM6Ly9tb2JpbGUueWFuZ2tlZHVvLmNvbS8=
数据接口:
aHR0cHM6Ly9tb2JpbGUueWFuZ2tlZHVvLmNvbS9wcm94eS9hcGkvc2VhcmNoX3N1Z2dlc3Q=
以上均做了脱敏处理,Base64 编码及解码方式:
import base64
# 编码
# result = base64.b64encode('待编码字符串'.encode('utf-8'))
# 解码
result = base64.b64decode('待解码字符串'.encode('utf-8'))
print(result)
常规 JavaScript 逆向思路
一般情况下,JavaScript 逆向分为三步:
- 寻找入口:逆向在大部分情况下就是找一些加密参数到底是怎么来的,关键逻辑可能写在某个关键的方法或者隐藏在某个关键的变量里,一个网站可能加载了很多 JavaScript 文件,如何从这么多的 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 参数的接口 ~