一直在搞其他事情,好久没搞js加密分析了,正好遇到一个很简单的数据要搞。想着也很久没在论坛发帖了,这次就发下。【某点数据】的榜单数据抓取。
[Java] - 列表页接口:【https://xxx/pc/app/v1/rank?market_id=11&genre_id=4&country_id=24&device_id=0&page=1&time=1691424000&rank_type=1&brand_id=1&k=AVUJVF8GAh5QBQVWDFVeBwEcXRwXQhFUXgQGHlkBAVcOTUgRGkwNHBdCEUoYBhxaCFpa】
- 打开列表页链接清空下请求,点击下其他的分类,一下子就看到数据返回的接口了。除了k是根据动态计算的,其他都是按照分类或者时间戳写死的。
复制代码
[Asm] - 老规矩先全局搜下接口路径,发现没有,好家伙,藏的那么深,从接口执行堆栈流程看到有个getAppRank 过于明显果断从这里开始debug。
复制代码
[Asm] - debug过程中发现接口路径是从【/web/api/rank】替换到【/app/v1/rank】
复制代码
[Asm] - 最后我们顺着debug看下k的生成规则是啥,发现k是由请求参数+basse64生成这就好弄了,分析下这块逻辑。
复制代码
简单带你直接把这块js扣下看看缺啥补啥。具体看代码上的注释,已经把原代码和修改的备注了。
[Asm] - 20230810 【'1d8en73(i4'】 本以为是固定参数,后发现是每天虽则签名更改,一天都不会变动,等有空再更新上来
复制代码
[JavaScript] - function getToken(e, path, n, r) {
- function u8ArrZBase64(u8Arr) {
- try{
- let CHUNK_SIZE = 0x8000;
- let index = 0;
- let length = u8Arr.length;
- let result = '';
- let slice;
- while (index < length) {
- slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
- result += String.fromCharCode.apply(null, slice);
- index += CHUNK_SIZE;
- }
- return btoa(result);
- }
- catch(e) {
- throw e;
- }
- }
- var s = n.s
- , d = n.k
- , f = n.l
- , v = n.d
- , l = n.sort
- , k = n.num
- , y = function(content, t, e) {
- for (var a = Array.from(content), n = Array.from(t), r = a.length, o = n.length, d = String.fromCodePoint, i = 0; i < r; i++)
- a[i] = d(a[i].codePointAt(0) ^ n[(i + e) % o].codePointAt(0));
- return a.join("")
- }(function(s, t, path, e) {
- return [s, t, e, path].join("(&&)")
- }(function(t, e) {
- // 原代码var n = c()(t); 缺c()(t) debug后发现只是简单校验t,我们可以直接把t复制给n
- var n = t;
- // 原代码
- //if (!_()(n)) {
- // var r = [];
- // for (var d in n)
- // m()(n[d]) && "get" === e && (n[d] = n[d].join("")),
- // "post" === e && (m()(n[d]) || o()(n[d])) && (n[d] = JSON.stringify(n[d])),
- // r.push(n[d]);
- // return r.sort(),
- // r.join("")
- //}
- // debug并删除无用代码,就是简单拼接
- var r = [];
- for (var d in n){
- r.push(n[d])
- }
- return r.sort(),r.join("");
- // Object(h.b)(s, d, f) 方法主要针对入参n中s,k的Uint8Array,并转成字符串,进去发现只是可以是写死的字符串1d8en73(i4
- }(e, r), parseInt((new Date).getTime() / 1e3) - 655876800 - v, path, l), '1d8en73(i4', k);
- // 最后就是u8Arr转Base64
- return u8ArrZBase64(new TextEncoder().encode(y));
- }
- var path = '/v1/rank';
- var eData = {
- "market_id": 11,
- "genre_id": 6,
- "country_id": 24,
- "device_id": 0,
- "page": 1,
- "time": 1690732800,
- "rank_type": 1,
- "brand_id": 0
- }
- // s,k,l 均会过期,定时从列表页解析即可,https://xxx/rank/googleplay/11-1-14-24-0?time=1690387200000
- var nData = {
- "s": "7f260473b50b9a6beb9a384d3adc2027",
- "k": "d8462b23fc155996",
- "l": "4e226e4686f23bc3",
- "d": 0,
- "sort": "dd",
- "num": 10
- }
- var rData = "get";
- getToken(eData, path,nData, rData)
复制代码
[Asm] - 前几天没来得及更新,次日发现某点还有个需要签名会每天变动,因此以下补充签名生成规则。对应上述代码中 【Object(h.b)(s, d, f)】,开始debug进去查看,着急搬砖直接上解析后的加密规则,在代码里都注释了
复制代码
[JavaScript] - function readUInt32BE(tt, t) {
- return 16777216 * tt[t] + (tt[t + 1] << 16 | tt[t + 2] << 8 | tt[t + 3])
- }
- function oFunction(t) {
- for (var e = t.length / 4 | 0, r = new Array(e), i = 0; i < e; i++)
- r[i] = readUInt32BE(t, 4 * i);
- return r
- }
- // 后续至关重用
- var f = [0, 1, 2, 4, 8, 16, 32, 64, 128, 27, 54]
- , d = function() {
- for (var t = new Array(256), e = 0; e < 256; e++)
- t[e] = e < 128 ? e << 1 : e << 1 ^ 283;
- for (var r = [], n = [], o = [[], [], [], []], h = [[], [], [], []], l = 0, f = 0, i = 0; i < 256; ++i) {
- var d = f ^ f << 1 ^ f << 2 ^ f << 3 ^ f << 4;
- d = d >>> 8 ^ 255 & d ^ 99,
- r[l] = d,
- n[d] = l;
- var c = t[l]
- , m = t[c]
- , v = t[m]
- , y = 257 * t[d] ^ 16843008 * d;
- o[0][l] = y << 24 | y >>> 8,
- o[1][l] = y << 16 | y >>> 16,
- o[2][l] = y << 8 | y >>> 24,
- o[3][l] = y,
- y = 16843009 * v ^ 65537 * m ^ 257 * c ^ 16843008 * l,
- h[0][d] = y << 24 | y >>> 8,
- h[1][d] = y << 16 | y >>> 16,
- h[2][d] = y << 8 | y >>> 24,
- h[3][d] = y,
- 0 === l ? l = f = 1 : (l = c ^ t[t[t[v ^ c]]],
- f ^= t[t[f]])
- }
- return {
- SBOX: r,
- INV_SBOX: n,
- SUB_MIX: o,
- INV_SUB_MIX: h
- }
- }();
- function _reset(tt) {
- for (var t = tt, e = t.length, r = e + 6, n = 4 * (r + 1), o = [], h = 0; h < e; h++)
- o[h] = t[h];
- for (h = e; h < n; h++) {
- var l = o[h - 1];
- h % e == 0 ? (l = l << 8 | l >>> 24,
- l = d.SBOX[l >>> 24] << 24 | d.SBOX[l >>> 16 & 255] << 16 | d.SBOX[l >>> 8 & 255] << 8 | d.SBOX[255 & l],
- l ^= f[h / e | 0] << 24) : e > 6 && h % e == 4 && (l = d.SBOX[l >>> 24] << 24 | d.SBOX[l >>> 16 & 255] << 16 | d.SBOX[l >>> 8 & 255] << 8 | d.SBOX[255 & l]),
- o[h] = o[h - e] ^ l
- }
- for (var c = [], m = 0; m < n; m++) {
- var v = n - m
- , y = o[v - (m % 4 ? 0 : 4)];
- c[m] = m < 4 || v <= 4 ? y : d.INV_SUB_MIX[0][d.SBOX[y >>> 24]] ^ d.INV_SUB_MIX[1][d.SBOX[y >>> 16 & 255]] ^ d.INV_SUB_MIX[2][d.SBOX[y >>> 8 & 255]] ^ d.INV_SUB_MIX[3][d.SBOX[255 & y]]
- }
- return {
- '_nRounds' : r,
- '_keySchedule' : o,
- '_invKeySchedule' : c
- }
- }
- function DAES(t) {
- var _key = oFunction(t);
- var rs = _reset(_key);
- return {
- '_key' : _key,
- '_nRounds' : rs._nRounds,
- '_keySchedule' : rs._keySchedule,
- '_invKeySchedule' : rs._invKeySchedule
- }
- }
- function dFrom(sData,sP) {
- k(sData,sP,0,16);
- return sData;
- }
- function k(t, e, r, n) {
- r = Number(r) || 0;
- var f = t.length - r;
- n ? (n = Number(n)) > f && (n = f) : n = f;
- var o = e.length;
- if (o % 2 != 0)
- throw new TypeError("Invalid hex string");
- n > o / 2 && (n = o / 2);
- for (var i = 0; i < n; ++i) {
- var c = parseInt(e.substr(2 * i, 2), 16);
- if (isNaN(c))
- return i;
- t[r + i] = c
- }
- return i
- }
- function l(t, e, r, n, o) {
- for (var h, l, f, d, c = r[0], m = r[1], v = r[2], y = r[3], w = t[0] ^ e[0], _ = t[1] ^ e[1], M = t[2] ^ e[2], S = t[3] ^ e[3], E = 4, k = 1; k < o; k++)
- h = c[w >>> 24] ^ m[_ >>> 16 & 255] ^ v[M >>> 8 & 255] ^ y[255 & S] ^ e[E++],
- l = c[_ >>> 24] ^ m[M >>> 16 & 255] ^ v[S >>> 8 & 255] ^ y[255 & w] ^ e[E++],
- f = c[M >>> 24] ^ m[S >>> 16 & 255] ^ v[w >>> 8 & 255] ^ y[255 & _] ^ e[E++],
- d = c[S >>> 24] ^ m[w >>> 16 & 255] ^ v[_ >>> 8 & 255] ^ y[255 & M] ^ e[E++],
- w = h,
- _ = l,
- M = f,
- S = d;
- return h = (n[w >>> 24] << 24 | n[_ >>> 16 & 255] << 16 | n[M >>> 8 & 255] << 8 | n[255 & S]) ^ e[E++],
- l = (n[_ >>> 24] << 24 | n[M >>> 16 & 255] << 16 | n[S >>> 8 & 255] << 8 | n[255 & w]) ^ e[E++],
- f = (n[M >>> 24] << 24 | n[S >>> 16 & 255] << 16 | n[w >>> 8 & 255] << 8 | n[255 & _]) ^ e[E++],
- d = (n[S >>> 24] << 24 | n[w >>> 16 & 255] << 16 | n[_ >>> 8 & 255] << 8 | n[255 & M]) ^ e[E++],
- [h >>>= 0, l >>>= 0, f >>>= 0, d >>>= 0]
- }
- function N(t, e, r, n) {
- e < 0 && (e = 4294967295 + e + 1);
- for (var i = 0, f = Math.min(t.length - r, 4); i < f; ++i)
- t[r + i] = e >>> 8 * (n ? i : 3 - i) & 255
- }
- function writeUInt32BE(tData, t, e,r) {
- return t = +t,
- e |= 0,
- true ? (tData[e] = t >>> 24,
- tData[e + 1] = t >>> 16,
- tData[e + 2] = t >>> 8,
- tData[e + 3] = 255 & t) : N(tData, t, e, !1),
- e + 4
- }
- function decryptBlock(t,kData){
- var e = (t = oFunction(t))[1];
- t[1] = t[3],
- t[3] = e;
- var thisData = DAES(new TextEncoder().encode(kData));
- var tData = new Uint8Array(16);
- var r = l(t, thisData._invKeySchedule, d.INV_SUB_MIX, d.INV_SBOX, thisData._nRounds)
- , h = new Uint8Array(16);
- // console.log('e:'+e);
- // console.log('h:'+h);
- // console.log('r:'+r);
- // console.log('t:'+t);
- writeUInt32BE(tData, r[0], 0),
- writeUInt32BE(tData, r[3], 4),
- writeUInt32BE(tData, r[2], 8),
- writeUInt32BE(tData, r[1], 12),
- h;
- return tData;
- }
- function exports(a, b) {
- for (var t = Math.min(a.length, b.length), r = new Uint8Array(16), i = 0; i < t; ++i)
- r[i] = a[i] ^ b[i];
- return r
- }
- function slice(tData, t, e) {
- var r, n = tData.length;
- if ((t = ~~t) < 0 ? (t += n) < 0 && (t = 0) : t > n && (t = n),
- (e = void 0 === e ? n : ~~e) < 0 ? (e += n) < 0 && (e = 0) : e > n && (e = n),
- e < t && (e = t),true)
- (r = tData.subarray(t, e)).__proto__ = d.prototype;
- return r
- }
- function ttt(t) {
- var e = t[15];
- if (e < 1 || e > 16)
- throw new Error("unable to decrypt data");
- var i = -1;
- for (; ++i < e; )
- if (t[i + (16 - e)] !== e)
- throw new Error("unable to decrypt data");
- if (16 === e)
- return;
- return slice(t, 0, 16 - e)
- }
- function getSign(sData,kData,lData){
- // 按s字符串生成 对应 16位数组
- var dFromResult = dFrom(new Uint8Array(16),sData);
- console.log(dFromResult);
- // 将k转16为数组通过 DAES方法 生成_key、_nRounds、_keySchedule、_invKeySchedule,等签名所需参数,并返回执行结果
- var decryptBlockResult= decryptBlock(dFromResult,kData);
- console.log(decryptBlockResult);
- var exportsResult = exports(decryptBlockResult,new TextEncoder().encode(lData));
- console.log(exportsResult);
- // 按数组切片重组并转成字符串得到签名
- return new TextDecoder("utf-8").decode(ttt(exportsResult));
- }
- var params = process.argv.splice(2);
- process.stdout.write(getSign(params[0],params[1],params[2]));
复制代码
[Asm] - 最后先调用getSign 再根据参数调用getToken即可
复制代码
|