本次逆向接口为:L2FwaS9waGFudG9tL3hnL3BmYi9hNA==
经常爬数据的同学都知道,电商一向是令人头疼的存在,而作为电商头部行业,风控更是强的一批。我们在获取数据的时候,发现如果请求头里面不带上某些特定的ck是获取不到数据的,下面是某个api的携带ck情况
里面很多参数都是网站所谓的风控参数,其中verifyAuthToken是验证码通过后的凭证,本次我们暂时不讨论这个值,_nano_fp 参数大家是不是很眼熟,做过anti-content参数逆向的同学也一定知道,这个参数也参与了这个值的计算,这些ck都是比较重要的ck,但是,要想获取商品的数据,_bee 才是问题的重点!
本文我们将讨论这个参数是如何生成的,并且在本地进行纯算计算。
我们首先要知道这个ck是在什么地方给我们设置起来的,方法有很多,你也可以hook setCookie这个方法,也可以做其他很好的操作。
在这里,我们采用全局搜索方法,搜索这个参数在哪获取的:
不搜不知道,这个结果居然是接口返回的,这个时候我们就要去看一看这个接口是怎么请求的了。
通过查看数据包我们可以发现,这个接口需要我们传一些奇奇怪怪的值,一个巨长无比的data,一个sign,一个时间戳,这个时候我们通过追栈,查看到参数生成的位置。
很显然,是在这里发包的,可以很明显看到每个参数的生成。
柿子先调软的捏,我们首先看这个短小的sign值,这里括号里面的操作明显就是做一些加法,把fe拼接一个参数然后凭借时间戳和data,做一个签名("HJ6793TJDI86DLS9D")。
现在我们需要知道的就是这个签名算法是什么。我们观察这个参数的长度,嗯,是40位,常见的40位的hash算法有什么呢,显然是sha1,在这里,我们猜测是不是标准算法,我们传入一个特定的值去计算,看他的结果和官方的结果是不是一样的:
很明显,这个参数的值是一样的,好了,这就是一个标准版的sha1.
jsconst crypto = require('crypto');
function sha1(input) {
return crypto.createHash('sha1').update(input).digest('hex');
}
破解完sign值后,我们可以查看data是怎么生成的了。
我们在代码中可以看到,data这个值是他在用Ne.es这个方法,把字符串变成数组,我们可以选取一个查看一下:
可以看到,这个方法是问题的关键之一,我们进入这个方法查看:
可以看到这个方法是在一个巨大的对象里面,这个对象是不是很眼熟,没错,这个和anti-content计算过程中的那个对象是同一个对象,我们可以用同一套方案把它抠出来:
jsconst 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)
}
))
},
}
把这个对象扣好之后,我们可以去进一步改写一下丑陋的原始代码,把恶心的三元表达式给替换掉:
jsfunction 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是版本号
jslet an = "2.4.7"
temu的风控是很厉害的,就算我们一套逻辑全部都对,可能也过不了他的风控,而且风控点经常会变,所以呀,这是一个长期对抗的过程~
本文作者:回锅炒辣椒
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!