2024-07-12
js逆向
00
请注意,本文编写于 88 天前,最后修改于 88 天前,其中某些信息可能已经过时。

目录

temu参数破解 _bee参数
前情提要
参数定位
sign值逆向
data参数破解
后记

temu参数破解 _bee参数

本次逆向接口为:L2FwaS9waGFudG9tL3hnL3BmYi9hNA==

前情提要

经常爬数据的同学都知道,电商一向是令人头疼的存在,而作为电商头部行业,风控更是强的一批。我们在获取数据的时候,发现如果请求头里面不带上某些特定的ck是获取不到数据的,下面是某个api的携带ck情况

temuck.jpg

里面很多参数都是网站所谓的风控参数,其中verifyAuthToken是验证码通过后的凭证,本次我们暂时不讨论这个值,_nano_fp 参数大家是不是很眼熟,做过anti-content参数逆向的同学也一定知道,这个参数也参与了这个值的计算,这些ck都是比较重要的ck,但是,要想获取商品的数据,_bee 才是问题的重点!

本文我们将讨论这个参数是如何生成的,并且在本地进行纯算计算。

参数定位

我们首先要知道这个ck是在什么地方给我们设置起来的,方法有很多,你也可以hook setCookie这个方法,也可以做其他很好的操作。

在这里,我们采用全局搜索方法,搜索这个参数在哪获取的:

temu定位.jpg

不搜不知道,这个结果居然是接口返回的,这个时候我们就要去看一看这个接口是怎么请求的了。

temu接口.jpg

通过查看数据包我们可以发现,这个接口需要我们传一些奇奇怪怪的值,一个巨长无比的data,一个sign,一个时间戳,这个时候我们通过追栈,查看到参数生成的位置。

temuhook1.jpg

很显然,是在这里发包的,可以很明显看到每个参数的生成。

sign值逆向

柿子先调软的捏,我们首先看这个短小的sign值,这里括号里面的操作明显就是做一些加法,把fe拼接一个参数然后凭借时间戳和data,做一个签名("HJ6793TJDI86DLS9D")。

现在我们需要知道的就是这个签名算法是什么。我们观察这个参数的长度,嗯,是40位,常见的40位的hash算法有什么呢,显然是sha1,在这里,我们猜测是不是标准算法,我们传入一个特定的值去计算,看他的结果和官方的结果是不是一样的:

temusha1.jpg

temusha.jpg

很明显,这个参数的值是一样的,好了,这就是一个标准版的sha1.

js
const crypto = require('crypto'); function sha1(input) { return crypto.createHash('sha1').update(input).digest('hex'); }

data参数破解

破解完sign值后,我们可以查看data是怎么生成的了。

我们在代码中可以看到,data这个值是他在用Ne.es这个方法,把字符串变成数组,我们可以选取一个查看一下:

image.png

可以看到,这个方法是问题的关键之一,我们进入这个方法查看:

image.png

可以看到这个方法是在一个巨大的对象里面,这个对象是不是很眼熟,没错,这个和anti-content计算过程中的那个对象是同一个对象,我们可以用同一套方案把它抠出来:

js
const zlib = require('zlib'); let zli = { deflate: zlib.deflateSync, } let De = { pako: function (t) { return zli.deflate(Buffer.from(t)) }, base64: function (t) { for (var e, n, r, o = "", i = t.length, a = 0, u = 3 * parseInt(i / 3); a < u;) e = t[a++], n = t[a++], r = t[a++], o += Me[e >>> 2] + Me[63 & (e << 4 | n >>> 4)] + Me[63 & (n << 2 | r >>> 6)] + Me[63 & r]; var c = i - u; return 1 === c ? (e = t[a], o += Me[e >>> 2] + Me[e << 4 & 63] + "==") : 2 === c && (e = t[a++], n = t[a], o += Me[e >>> 2] + Me[63 & (e << 4 | n >>> 4)] + Me[n << 2 & 63] + "="), o.replace(/[+\/=]/g, (function (t) { return Ie[t] } )) }, charCode: function (t) { for (var n = [], r = 0, o = 0; o < t.length; o += 1) { var i = t.charCodeAt(o); i >= 0 && i <= 127 ? (n.push(i), r += 1) : (i >= 2048 && i <= 55295 || i >= 57344 && i <= 65535) && (r += 3, n.push(224 | 15 & i >> 12), n.push(128 | 63 & i >> 6), n.push(128 | 63 & i)) } for (var a = 0; a < n.length; a += 1) n[a] &= 255; return r <= 255 ? [0, r].concat(n) : [r >> 8, 255 & r].concat(n) }, es: function (t) { t || (t = "undefined"); var n = [] , r = this.charCode(t).slice(2) , o = this.enn(r.length); return n = n.concat(this.enn(241), o, r) }, enn: function (t) { t || (t = 0); for ( var n = parseInt(t), r = n << 1 ^ n >> 31, o = r.toString(2), i = [], a = (o = Te(o, 7 * Math.ceil(o.length / 7), "0")).length; a >= 0; a -= 7 ) { var u = o.substring(a - 7, a); if (!(-128 & r)) { i.push("0" + u); break } i.push("1" + u), r >>>= 7 } return i.map((function (t) { return parseInt(t, 2) } )) }, }

把这个对象扣好之后,我们可以去进一步改写一下丑陋的原始代码,把恶心的三元表达式给替换掉:

js
function get_a4_payload(fp, header_catch, config) { const c = config.collectEvent; const x = config.isInterval || false; const l = fp.rawData || {}; const v = fp.localIp || 'undefined'; const p = an; const g = fp.app || 'h5Market'; const b = fp.FKGJ || 'undefined'; const S = fp.uid || 'undefined'; const A = fp.moveData || []; const T = fp.clickData || []; const C = fp.inputData || []; const M = fp.blurData || []; const D = fp.pasteData || "0"; const N = fp.hasSensor; const H = fp.isFront; const B = fp.webGLInfos; const P = fp.windowSize; const k = fp.winSelenium; const X = fp.chromium; const L = fp.headlessByProperties; const F = fp.languages; const j = fp.consoleLied; const U = fp.chromeExtensionScripts; const G = fp.extensionImgs; const W = fp.hookFuncs; const J = fp.frontReferer; const V = fp.elements || {}; const q = fp.clientIp; const Z = fp.outterJs || JSON.stringify({}); const Q = fp.cssFeatures; const $ = fp.webglFt; const tt = fp.performanceTime; const et = fp.emptyEvalLength; const nt = fp.errorFF; const rt = header_catch; const ot = fp.fonts; const it = fp.h5Features; const at = String(Date.now()); const ut = [ ...De.es("isInterval"), ...De.es(String(!!x)), ...De.es('rawData'), ...De.es(JSON.stringify(l)), ...De.es('localIp'), ...De.es(v), ...De.es("reportTimestamp"), ...De.es(at), ...De.es('version'), ...De.es(p), ...De.es('app'), ...De.es(g), ...De.es('FKGJ'), ...De.es(b), ...De.es("uid"), ...De.es(S), ...De.es('hasCdc'), ...De.es('false'), ...De.es('electronCef'), ...De.es(Dn()), ...De.es('frontReferer'), ...De.es(J) ]; const ct = c ? [ ...De.es("moveData"), ...De.es(JSON.stringify(A)), ...De.es('clickData'), ...De.es(JSON.stringify(T)), ...De.es('inputData'), ...De.es(JSON.stringify(C)), ...De.es('blurData'), ...De.es(JSON.stringify(M)), ...De.es('pasteData'), ...De.es(D) ] : []; const xt = N ? [...De.es('hasSensor'), ...De.es(N)] : []; const st = H ? [...De.es("isFront"), ...De.es(H)] : []; const ft = B ? [...De.es("webGLInfos"), ...De.es(B)] : []; const lt = P ? [...De.es('windowSize'), ...De.es(P)] : []; const dt = X ? [...De.es('chromium'), ...De.es(X)] : []; const vt = L ? [...De.es('headlessByProperties'), ...De.es(L)] : []; const ht = k ? [...De.es("winSelenium"), ...De.es(k)] : []; const pt = F ? [...De.es('languages'), ...De.es(F)] : []; const mt = j ? [...De.es("consoleLied"), ...De.es(j)] : []; const gt = U ? [...De.es('injectScripts'), ...De.es(U)] : []; const Et = G ? [...De.es("extensionImgs"), ...De.es(G)] : []; const bt = W ? [...De.es("hookFuncs"), ...De.es(W)] : []; const yt = V ? [...De.es('elements'), ...De.es(JSON.stringify(V))] : []; const St = q ? [...De.es('frontClientIp'), ...De.es(q)] : []; const _t = [...De.es('outterJs'), ...De.es(Z)]; const At = Q ? [...De.es('cssFeatures'), ...De.es(Q)] : []; const wt = $ ? [...De.es("webglFt"), ...De.es($)] : []; const Tt = tt ? [...De.es('performanceTime'), ...De.es(tt)] : []; const Ot = et ? [...De.es("emptyEvalLength"), ...De.es(et)] : []; const Ct = nt ? [...De.es("errorFF"), ...De.es(nt)] : []; const Rt = rt ? [...De.es('headerCache'), ...De.es(rt)] : []; const Mt = ot ? [...De.es('fonts'), ...De.es(ot)] : []; const It = it ? [...De.es('h5Features'), ...De.es(it)] : []; const Dt = [ ...ut, ...ct, ...xt, ...st, ...ft, ...lt, ...dt, ...vt, ...ht, ...pt, ...mt, ...gt, ...Et, ...bt, ...yt, ...St, ..._t, ...At, ...wt, ...Tt, ...Ot, ...Ct, ...Rt, ...Mt, ...It ]; const result = { timeStamp: at, result: "0a" + De.base64(De.pako(Dt)) }; const sign = sha1('feHJ6793TJDI86DLS9D' + at + result.result) return { data: result.result, timestamp: at, appKey: "fe", sign: sign } }

这样,代码一下子就清晰很多了,我们可以看到就是对一个巨大的Ki对象的值进行处理,现在问题的关键就到了如何获取这些对象的值,经过分析我们可以得知这些值是我们浏览器的一些环境值,我们拿到他就可以进行参数生产然后获取ck。

当然,这里还有一个header_catch,这个是其他接口返回的一个缓存key,可以很轻松的获取到,这里就不再赘述。

里面还有些小的函数,也是很简单就可以扣下来的,篇幅原因就不带着大家去扣代码了,an是版本号

js
let an = "2.4.7"

后记

temu的风控是很厉害的,就算我们一套逻辑全部都对,可能也过不了他的风控,而且风控点经常会变,所以呀,这是一个长期对抗的过程~

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:回锅炒辣椒

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!