声明
本文内容仅供学习交流使用,不得用于商业或非法用途。抓包内容、敏感网址、数据接口已脱敏处理,严禁转载、修改或二次传播。若有侵权,请联系作者删除。
逆向目标
- 目标:X-S加密
- 网址:aHR0cHM6Ly93d3cueGlhb2hvbmdzaHUuY29tL2V4cGxvcmU=
请求分析
切换频道后,定位到目标数据的接口请求,发现请求头中存在多个疑似加密参数,包括:X-Mns、X-S、X-S-Common,X-T为时间戳,经过测试,大部分接口仅对 X-S 进行校验,因此本次重点分析 X-S 的生成逻辑。
X-S逆向
全局搜索关键字 x-s 后,发现在多个 JS 文件中出现。可以在这些文件中逐一打上断点进行调试。为了节省时间,这里直接给出定位结果:
进入对应的 JS 源代码页面后,搜索 x-s,并在所有可疑位置设置断点。刷新页面后,代码在下图所示的位置被成功拦截。
x-s 的值保存在 v 中,而 v = f(p, u) || {}。先来看 p 和 u 分别代表什么,p 是请求的 url 的一部分,u 是请求体。
一开始看到 f = encrypt_sign,以为就是生成 x-s 的方法,但实际加密后结果太短,明显不对。往下看发现,f 实际由一个三元表达式决定。继续跟进,调用 window._webmsxyw(p, u)后得到的值才是正确的 x-s。
进入 window._webmsxyw 后可以看到代码已经被混淆,直接分析加密逻辑难度较大,既然该方法可以通过window调用,那我们就把所有的JS代码扣下来,补全所需要的环境就好了。
补环境
在本地运行扣下来的 JS 代码时,首先遇到 window 未定义的错误。
1.补Window
window = global;
// 防止网站检测
delete global;
delete Buffer;
接着运行,遇到 createElement 未定义的错误,这是 document 对象中的方法,暂时补空函数。
2.补Document
document = {
createElement: function (tagName) {}
}
继续运行时遇到了 TypeError: Cannot read properties of undefined (reading ‘getAttribute’) 的错误,不清楚 getAttribute 具体是哪个对象在调用。此时,可以使用代理(Proxy)来帮助我们分析。
代理示例,把经常检测的环境都加上
function getEnvs(proxyObjs) {
for (let i = 0; i < proxyObjs.length; i++) {
const handler = `{
get: function(target, property, receiver) {
if (property !== "Math" && property !== "isNaN" && property !== "encodeURI"){
if (target[property] && typeof target[property] != "string" && Object.keys(target[property]).length>3){
}else{
console.log("方法:", "get ", "对象:", "${proxyObjs[i]}", " 属性:", property, " 属性类型:", typeof property, ", 属性值:", target[property], ", 属性值类型:", typeof target[property]);}}
return target[property];
},
set: function(target, property, value, receiver) {
console.log("方法:", "set ", "对象:", "${proxyObjs[i]}", " 属性:", property, " 属性类型:", typeof property, ", 属性值:", value, ", 属性值类型:", typeof target[property]);
return Reflect.set(...arguments);
}
}`;
eval(`try {
${proxyObjs[i]};
${proxyObjs[i]} = new Proxy(${proxyObjs[i]}, ${handler});
} catch (e) {
${proxyObjs[i]} = {};
${proxyObjs[i]} = new Proxy(${proxyObjs[i]}, ${handler});
}`);
}
}
proxyObjs = ['window', 'document', 'location', 'navigator', 'history', 'screen', 'localStorage']
getEnvs(proxyObjs);
在使用代理调试时,我们发现 document 对象中缺少 documentElement 属性,我们把它补上。
document = {
createElement: function (tagName) { },
documentElement: {}
}
执行 createElement 时仍然报错,说明直接将 createElement 替换为空函数的做法并不奏效。实际上,createElement 创建标签后,紧接着会执行 getContext 方法。因此,我们需要进一步分析 createElement 创建了什么标签。
将createElement的内容改成:
createElement: function (tagName) {
console.log(tagName)
}
查看控制台输出的为canvas。
补上canvas,同时,在执行 document.createElement 时,应针对不同标签类型进行判断,确保根据创建的标签返回正确的对象。
canvas = {
getContext: function (tagName) {
}
}
document = {
createElement: function (tagName) {
console.log(tagName);
if (tagName === 'canvas') {
return canvas;
} else if (tagName === 'div'){
return {};
}
},
documentElement: {}
}
再次运行,发现没有报错了。接下来,测试 window._webmsxyw(p, u)是否能够成功生成 x-s。
运行结果显示,虽然成功得到了 x-s,但其长度明显不对。这是因为当前环境尚未完全补全,接下来需要对属性值为 undefined 的对象进行补全。
补全 document.cookie 后,运行结果显示缺失 localStorage。
3.补LocalStorage
localStorage = {
getItem: function () {}
}
成功得到 x-s,并且长度明显增加,但不要高兴得太早。在使用这个值进行请求时,仍然无法获取数据。原因是浏览器生成的 x-s 长度为672,而我们生成的只有648,没办法,继续补环境吧。
4.补Navigator
navigator = {
appCodeName: "Mozilla",
appName: "Netscape",
appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47",
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.20447',
platform: "Win32",
webdriver: false,
language: 'zh-CN',
}
5.补Location
location = {
"ancestorOrigins": {},
"href": "xxx",
"origin": "xxx",
"protocol": "https:",
"host": "xxx",
"hostname": "xxx",
"port": "",
"pathname": "/explore",
"search": "",
"hash": ""
}
到目前为止,环境已经补得差不多了,但最终结果仍然是648位。如果继续补充缺失的部分,还是无法得到正确结果。真正的关键在于 getItem 方法。之前只是简单地补了一个空函数,实际上它应该像 createElement 那样进行处理。具体实现方式就不再展示了,相信聪明的你一定知道如何处理,最终结果如下。
结果展示
首页:
作者笔记:
笔记详情:
评论:
希望这篇文章对你有所帮助,如果你有更好的思路,欢迎在评论区交流!