【某点数据】榜单数据抓取
本帖最后由 xinjun_ying 于 2023-8-15 14:50 编辑一直在搞其他事情,好久没搞js加密分析了,正好遇到一个很简单的数据要搞。想着也很久没在论坛发帖了,这次就发下。【某点数据】的榜单数据抓取。
列表页接口:【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是根据动态计算的,其他都是按照分类或者时间戳写死的。
老规矩先全局搜下接口路径,发现没有,好家伙,藏的那么深,从接口执行堆栈流程看到有个getAppRank 过于明显果断从这里开始debug。
debug过程中发现接口路径是从【/web/api/rank】替换到【/app/v1/rank】
最后我们顺着debug看下k的生成规则是啥,发现k是由请求参数+basse64生成这就好弄了,分析下这块逻辑。
简单带你直接把这块js扣下看看缺啥补啥。具体看代码上的注释,已经把原代码和修改的备注了。
20230810 【'1d8en73(i4'】 本以为是固定参数,后发现是每天虽则签名更改,一天都不会变动,等有空再更新上来
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 = d(a.codePointAt(0) ^ n[(i + e) % o].codePointAt(0));
return a.join("")
}(function(s, t, path, e) {
return .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) && "get" === e && (n = n.join("")),
// "post" === e && (m()(n) || o()(n)) && (n = JSON.stringify(n)),
// r.push(n);
// return r.sort(),
// r.join("")
//}
// debug并删除无用代码,就是简单拼接
var r = [];
for (var d in n){
r.push(n)
}
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)
前几天没来得及更新,次日发现某点还有个需要签名会每天变动,因此以下补充签名生成规则。对应上述代码中 【Object(h.b)(s, d, f)】,开始debug进去查看,着急搬砖直接上解析后的加密规则,在代码里都注释了
function readUInt32BE(tt, t) {
return 16777216 * tt + (tt << 16 | tt << 8 | tt)
}
function oFunction(t) {
for (var e = t.length / 4 | 0, r = new Array(e), i = 0; i < e; i++)
r = readUInt32BE(t, 4 * i);
return r
}
// 后续至关重用
var f =
, d = function() {
for (var t = new Array(256), e = 0; e < 256; e++)
t = 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 = d,
n = l;
var c = t
, m = t
, v = t
, y = 257 * t ^ 16843008 * d;
o = y << 24 | y >>> 8,
o = y << 16 | y >>> 16,
o = y << 8 | y >>> 24,
o = y,
y = 16843009 * v ^ 65537 * m ^ 257 * c ^ 16843008 * l,
h = y << 24 | y >>> 8,
h = y << 16 | y >>> 16,
h = y << 8 | y >>> 24,
h = y,
0 === l ? l = f = 1 : (l = c ^ t]],
f ^= t])
}
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 = t;
for (h = e; h < n; h++) {
var l = o;
h % e == 0 ? (l = l << 8 | l >>> 24,
l = d.SBOX << 24 | d.SBOX << 16 | d.SBOX << 8 | d.SBOX,
l ^= f << 24) : e > 6 && h % e == 4 && (l = d.SBOX << 24 | d.SBOX << 16 | d.SBOX << 8 | d.SBOX),
o = o ^ l
}
for (var c = [], m = 0; m < n; m++) {
var v = n - m
, y = o;
c = m < 4 || v <= 4 ? y : d.INV_SUB_MIX] ^ d.INV_SUB_MIX] ^ d.INV_SUB_MIX] ^ d.INV_SUB_MIX]
}
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 = c
}
return i
}
function l(t, e, r, n, o) {
for (var h, l, f, d, c = r, m = r, v = r, y = r, w = t ^ e, _ = t ^ e, M = t ^ e, S = t ^ e, E = 4, k = 1; k < o; k++)
h = c ^ m ^ v ^ y ^ e,
l = c ^ m ^ v ^ y ^ e,
f = c ^ m ^ v ^ y ^ e,
d = c ^ m ^ v ^ y ^ e,
w = h,
_ = l,
M = f,
S = d;
return h = (n << 24 | n << 16 | n << 8 | n) ^ e,
l = (n << 24 | n << 16 | n << 8 | n) ^ e,
f = (n << 24 | n << 16 | n << 8 | n) ^ e,
d = (n << 24 | n << 16 | n << 8 | n) ^ e,
}
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 = e >>> 8 * (n ? i : 3 - i) & 255
}
function writeUInt32BE(tData, t, e,r) {
return t = +t,
e |= 0,
true ? (tData = t >>> 24,
tData = t >>> 16,
tData = t >>> 8,
tData = 255 & t) : N(tData, t, e, !1),
e + 4
}
function decryptBlock(t,kData){
var e = (t = oFunction(t));
t = t,
t = 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),
writeUInt32BE(tData, r, 4),
writeUInt32BE(tData, r, 8),
writeUInt32BE(tData, r, 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 = a ^ b;
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;
if (e < 1 || e > 16)
throw new Error("unable to decrypt data");
var i = -1;
for (; ++i < e; )
if (t !== 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,params,params));
最后先调用getSign 再根据参数调用getToken即可
https://static.diandian.com/_app/app~f69643ec.f0b6bc0.js
1:400756 这个e(n)好像也能生成k这个参数 都是大佬啊,牛 大佬优秀,我去试试其他站可不可以 感谢分享,其他的我也试试看行不行 教程很详细,学习了 这个容易黑号 值得学习的文章 这个感觉还阔以 请问楼主,如果要扣这里的话,怎么扣和补环境?