分析目标:
Cmh0dHBzOi8vd3d3LnpraC5jb20v
1. 接口分析
抓包数据详情页面价格接口,可以看到加密的参数非常之多,首先是请求头里面的X-Akc、X-Rgn参数,如下所示:
接着是接口响应数据的加密,如下所示:
最后是请求参数加密,如下图所示的两个参数traceId、cipher,traceId是一段随机的字符串,每次请求都需要带上。cipher参数的话就是请求价格的商品id
2. 断点分析
接下来先从请求头参数X-Akc、X-Rgn分析,两个是headers里面的加密参数,直接先字段名称关键词搜索,这里能够直接搜索到加密位置,如下所示:
点击进入到JS文件,这里分析发现X-Akc、X-Rgn参数需要先访问rsaKey接口,这个接口请求后会返回rsaKey、rsaGroup字段信息,如下所示:
rsaGroup字段就是请求头内X-Rgn参数的值,请求上面rsaKey接口需要带上traceId参数,这里我们就可以断点继续分析traceId参数的生成,如下所示:
经分析,traceId参数的生成代码如上图,直接拿下来修改一下,代码如下:
function TraceId(e = 8, t = !0) {
var n = ""
, n = Math.ceil(1e14 * Math.random()).toString().substr(0, e || 4);
return t && (n += Date.now()),
n
}
拿到控制台测试一下,看起来应该是没错的,至此请求头内X-Rgn参数已经解决,如下所示:
继续分析请求头内X-Akc参数的加密,还是下面这部分JS代码:
JS代码中现实需要公钥publicKey,在上面rsaKey接口的返回数据中rsaKey字段就是公钥!截图发现一摸一样~是吧?公钥已经有了,再看上面这段JS代码,用了JSEncrypt加密库,这里还原一下加密逻辑,可以就是如下这样:
const jsencrypt = require('jsencrypt')
o = rsaKey # 接口返回的公钥串
c = rsaGroup # traceId
let s = new jsencrypt();
s.setPublicKey(o)
const encryptedData = s.encrypt(N)
const headers = {
'x-akc': encryptedData,
'x-rgn': c.toString()
};
3. 扣代码
接下来分析完以后,就是扣代码环节。cipher参数也是里面稍难的,比较复杂!JS往下接着看cipher用到的b.a.encrypt来自Webpack打包的代码,k.a直接修改使用crypto-js的包:
这里没猜错的话,runtime…应该是Webpack的加载器代码
接下来找b.a,JS往上追溯尝试去找到b的定义处,找到之后需要注意t = n(399),什么意思?意思就是说b是webpack中索引399的代码,这个时候我们需要开始构造,在构造之前需要先找Webpack的加载器,如下所示:
点击上图runtime…跳转,之前分析的时候已经猜测它是一个加载器,这个JS文件,所以直接先扣出来,如下所示:
!function(l) {
function e(e) {
for (var r, t, n = e[0], o = e[1], u = e[2], i = 0, f = []; i < n.length; i++)
t = n[i],
c[t] && f.push(c[t][0]),
c[t] = 0;
for (r in o)
Object.prototype.hasOwnProperty.call(o, r) && (l[r] = o[r]);
for (s && s(e); f.length; )
f.shift()();
return p.push.apply(p, u || []),
a()
}
function a() {
for (var e, r = 0; r < p.length; r++) {
for (var t = p[r], n = !0, o = 1; o < t.length; o++) {
var u = t[o];
0 !== c[u] && (n = !1)
}
n && (p.splice(r--, 1),
e = i(i.s = t[0]))
}
return e
}
var t = {
}
, c = {
76: 0
}
, p = [];
function i(e) {
var r;
return (t[e] || (r = t[e] = {
i: e,
l: !1,
exports: {
}
},
l[e].call(r.exports, r, r.exports, i),
r.l = !0,
r)).exports
}
i.m = l,
i.c = t,
i.d = function(e, r, t) {
i.o(e, r) || Object.defineProperty(e, r, {
enumerable: !0,
get: t
})
}
,
i.r = function(e) {
"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
value: "Module"
}),
Object.defineProperty(e, "__esModule", {
value: !0
})
}
,
i.t = function(r, e) {
if (1 & e && (r = i(r)),
8 & e)
return r;
if (4 & e && "object" == typeof r && r && r.__esModule)
return r;
var t = Object.create(null);
if (i.r(t),
Object.defineProperty(t, "default", {
enumerable: !0,
value: r
}),
2 & e && "string" != typeof r)
for (var n in r)
i.d(t, n, function(e) {
return r[e]
}
.bind(null, n));
return t
}
,
i.n = function(e) {
var r = e && e.__esModule ? function() {
return e.default
}
: function() {
return e
}
;
return i.d(r, "a", r),
r
}
,
i.o = function(e, r) {
return Object.prototype.hasOwnProperty.call(e, r)
}
,
i.p = "//static.zkh360.com/file/resource/official/";
var r = (n = window.webpackJsonp = window.webpackJsonp || []).push.bind(n);
n.push = e;
for (var n = n.slice(), o = 0; o < n.length; o++)
e(n[o]);
var s = r;
a()
}([]); // 放索引的JS代码
加载器代码内[]是什么?是399索引的JS代码,需要继续接着扣,扣完再填充到加载器内!399代码如下:
399代码中还引用了其他代码,需要依次导入。看上图n(237),这种就接着扣就完事了:
另外,部分代码是没有写索引的,如上图,可根据列表中下标的位置找对应的代码!还需要扣上面引用的n(893)、n(894)、n(624)、n(896)
剩余需要扣的代码这里不详细描述了,感兴趣的可以自己根据上面的方式去慢慢扣
代码扣全之后,就可以直接调用生成请求头加密参数跟解密接口响应的加密数据:
/**
* @returns {
{"x-akc": "", "x-rgn": "", "cipher": ""}}
*/
get_cipher_and_headers = function (traceId, data, rsaKey, rsaGroup, N, E) {
o = rsaKey
c = rsaGroup
let s = new jsencrypt();
s.setPublicKey(o)
const encryptedData = s.encrypt(N)
// 准备HTTP请求的headers
const headers = {
'x-akc': encryptedData,
'x-rgn': c.toString() // 这里填写对应的区域值
};
// 使用ECB模式和PKCS7填充加密请求数据
a = loader(398)
let cipher = a.encrypt(JSON.stringify(data), E, {
mode: crypto_js.mode.ECB,
padding: crypto_js.pad.Pkcs7
}).toString()
headers["cipher"] = cipher
return headers
}
/**
* 解密返回的数据
*/
decrypt = function (data, E){
// 使用ECB模式和PKCS7填充加密请求数据
a = loader(398)
let n = a.decrypt(data, E, {
mode: crypto_js.mode.ECB,
padding: crypto_js.pad.Pkcs7
})
return JSON.parse(n.toString(crypto_js.enc.Utf8))
}
data = '' // 密文数据
let res = decrypt(data, {
'sigBytes': 32, 'words': [825700400, 943273273, 825833778, 959526704, 808991029, 959527225, 842608951, 808466489]});
console.log(res);
4. 测试结果
好了,到这里又到了跟大家说再见的时候了。创作不易,帮忙点个赞再走吧。你的支持是我创作的动力,希望能带给大家更多优质的文章