xinjun_ying 发表于 2023-8-8 17:11

【某点数据】榜单数据抓取

本帖最后由 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即可

空竹 发表于 2023-8-9 10:54

https://static.diandian.com/_app/app~f69643ec.f0b6bc0.js

1:400756 这个e(n)好像也能生成k这个参数

YC5201314 发表于 2023-8-8 19:08

都是大佬啊,牛

dychjyfgfda 发表于 2023-8-8 19:10

大佬优秀,我去试试其他站可不可以

kkoo 发表于 2023-8-8 20:44

感谢分享,其他的我也试试看行不行

xiaoshan208 发表于 2023-8-8 22:16

教程很详细,学习了

aonima 发表于 2023-8-8 23:34

这个容易黑号

zd53011 发表于 2023-8-9 07:54

值得学习的文章

qxt29680874 发表于 2023-8-9 10:23

这个感觉还阔以

空竹 发表于 2023-8-9 11:10

请问楼主,如果要扣这里的话,怎么扣和补环境?
页: [1] 2 3
查看完整版本: 【某点数据】榜单数据抓取