文章目录
- 1. 写在前面
- 2. 接口分析
- 3. 断点分析
- 4. 扣代码
【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
1. 写在前面
又到了周末学习技术的时间,这篇文章要分析的是一个请求参数签名加密与响应数据加密的网站。对了!昨天我更新了一篇PDD的加密算法还原,跟商家后台不一样,是批发的,里面有作者测试的一些总结
如感兴趣可移步于此处进行阅读:最新PDD批发Anti-Content参数逆向分析与算法还原
2. 接口分析
开始!随便点击一个模块,我们监测一下请求发包,可以看到提交的请求参数中有一个sign,这个参数的加密倒是很简单,稍微调试去分析一下就能够找到加密逻辑代码,直接扣取下来即可!如下所示:
然后我们再看一下接口返回的数据,它是以密文的方式返回的,如下所示:
这种情况一般如果没有点实力的,那就被直接拒之门外了!自动化方案页面数据结构展示复杂,在此组装拼接的工程化代码将会是复杂头疼的!所以这种情况还是需要去分析解密算法进行还原的!数据解密在之前我的文章中有提到多种方案去定位与跟栈分析
3. 断点分析
sign参数的长度看起来像md5加密又不像,34位?魔改了?还是加盐了?先不去猜测,直接搜一下参数sign,如下所示:
上面代码(function() { return Z })是一个立即执行函数表达式,返回的Z函数很可能就是sign参数的加密函数,直接进入到Z</font方法看一下就知道, 如下所示:
进入到方法内部后断点,执行一下跳到最新的内部断点处,看着代码逻辑,放佛一切都即将真相大白,Z就是加密方法,如下所示:
我刷新了几次接口请求,sign参数的末尾总是固定的99,结合上面的代码逻辑,应该是先通过md5加密之后拼接的固定字符串,我们来验证一下,如下所示:
Z加密方法接受的两个参数,分别是API路径加请求提交的参数,常见的加密手法。我们可以在控制台看一下,如下所示:
4. 扣代码
完成了上面sign参数的加密分析,接下来我们开始扣代码,先把Z方法扣取下来,为了方便验证,把当前调试环境内的参与加密的参数都补上,测试一下。如下所示:
const crypto = require('crypto-js')
function md5(text){
text = String(text)
return crypto.MD5(text).toString()
}
var e = '' //浏览器环境复制
var t = {
"appType": "3",
"channelNumber": "GF1001",
"comId": "8",
"lang": "zh",
"platform": "pc",
"st": 1712998336085,
"timeZone": "8",
"version": "671",
"versionCode": "671"
}
function l() {
return e
}
function Z(e, t) {
var n = {}
, o = e;
for (var r in Object.keys(t).sort().map((function(e) {
n[e] = t[e]
}
)),
n)
o = o + r + n[r];
return o += md5("wjj"),
md5(o).toLowerCase() + "99"
}
console.log(Z(e, t));
这里l()即md5加密,可以直接导模块,当然也可以去扣,但是对于原生的没有必要!之后分别在本地跟控制台环境运行,加密结果一致,看来没有任何问题。如下所示:
接下来分析数据加密,我们先用以前文章提到过的方式Hook一下接口响应数据,可以看加密与解密的所有数据拦截情况,它这个加载解密是动态的,如下所示:
直接跟栈,跳转到4ed4143.js代码文件,数据解密的核心逻辑就这个文件内,待会下面会详细的分析入口,讲解为什么这个文件是核心!如下所示:
继续跟一下,走到一处AES解密的特征处,接受一个密文参数,进行解密操作,如下所示:
我们来分析一下这块代码的逻辑,之后把它拿下来稍微做一个验证就知道了,首先对777db0c19edfaace字符来了一个编码序列,用做后续的解密算法中的密钥
同样的操作,9876543210599311操作完给到用r做解密算法中的初始向量
核心就是AES.decrypt(…)使用AES算法对密文进行解密,CBC模式解密,PKCS7的填充,解密之后得到一个字节序列,再toString将结果进行UTF-8编码得到最终解密的字符串
这里我们将断点处的代码拿下来,改一下密文丢进去验证,修改后测试代码如下所示:
const crypto = require('crypto-js')
const key = crypto.enc.Utf8.parse("777db0c19edfaace");
const iv = crypto.enc.Utf8.parse("9876543210599311");
const DecryptText = (e) => {
const decrypted = crypto.AES.decrypt(e, key, {
iv: iv,
mode: crypto.mode.CBC,
padding: crypto.pad.Pkcs7
}).toString(crypto.enc.Utf8);
return decrypted;
};
const data = '27imP4/mfRigCHNGzKw0KXkwQIvIox4kaepLM/ZaPhnMiYwkTfSO4HIxyrUbeRktYs8mIJzMSfD9VT1+i+HPYl0253H8f4Csd7+zOHRKvxwhTkHgDDuT419yvxJDadZZp/WyP3nlpwLSECLj7jOl5w==';
console.log(DecryptText(data))
验证是没有问题的,接下来就是扣取核心逻辑,它这个是一段段密文进行传输的,我们需要对最终的那个长串密文,也就是组装出来的整个大字符串进行解密,看了一下,各种数组操作、转换、编码、解码…找到特征跟到核心加密逻辑,如下所示:
又回到前面说的4ed4143.js文件,这里再浅浅的提那么一下,一定要记住这个文件!需要扣的都在里面,大家从这里开始往下去扣,如下所示:
继续往下分析,来到最终长加密字符的解密位置,可以看到k是入口函数,接受的参数e就是密文,这个我们结合Sign签名调用接口拿密文直接丢给k,如下所示:
既然定位到入口,那就更加清晰了,看k方法内调用的y,所有解密都将从它开始,如下所示:
先打把这两个方法扣出来,如下所示:
function k(e) {
var t = e.split("").map((function(e) {
return e.charCodeAt(0)
}
))
, n = new Uint8Array(t);
return e = y(h.a.inflate(n))
}
function y(e) {
var t, i, n, o, r, c;
for (t = "",
n = e.length,
i = 0; i < n; )
switch ((o = e[i++]) >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
t += String.fromCharCode(o);
break;
case 12:
case 13:
r = e[i++],
t += String.fromCharCode((31 & o) << 6 | 63 & r);
break;
case 14:
r = e[i++],
c = e[i++],
t += String.fromCharCode((15 & o) << 12 | (63 & r) << 6 | (63 & c) << 0)
}
return JSON.parse(t)
}
控制台看一下,最终的所有完整数据解密,如下所示:
剩下的就是前面说的那个核心文件了,y(h.a.inflate(n))中你可以看到调用的核心文件就是4ed4143.js,进去扣就行
最终的算法真的是太长了,我原本把所有的代码都扣完后,准备折叠一下把关键点发出来给需要学习的小伙伴有一个参考,但是还是放弃了,如下所示:
如果你需要学习,有任何疑问都可以咨询作者寻求帮助,或者也可以找作者拿完整的JS算法文件!祝大家周末愉快~