找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 33|回复: 0

temu风控破解之_bee参数纯算破解思路

[复制链接]

2万

主题

137

回帖

13万

积分

管理员

积分
139372
发表于 2024-8-9 14:52:14 | 显示全部楼层 |阅读模式 IP:山东省青岛市 联通

登录后更精彩...O(∩_∩)O...

您需要 登录 才可以下载或查看,没有账号?立即注册

×
temu风控破解之_bee参数纯算破解思路

temu参数破解 _bee参数
[Bash shell] 纯文本查看 复制代码
本次逆向接口为:/api/phantom/xg/pfb/a4


前情提要
经常爬数据的同学都知道,电商一向是令人头疼的存在,而作为电商头部行业,风控更是强的一批。我们在获取数据的时候,发现如果请求头里面不带上某些特定的ck是获取不到数据的,下面是某个api的携带ck情况
里面很多参数都是网站所谓的风控参数,其中verifyAuthToken是验证码通过后的凭证,本次我们暂时不讨论这个值,_nano_fp 参数大家是不是很眼熟,做过anti-content参数逆向的同学也一定知道,这个参数也参与了这个值的计算,这些ck都是比较重要的ck,但是,要想获取商品的数据,_bee 才是问题的重点!
本文我们将讨论这个参数是如何生成的,并且在本地进行纯算计算。

参数定位
我们首先要知道这个ck是在什么地方给我们设置起来的,方法有很多,你也可以hook setCookie这个方法,也可以做其他很好的操作。
在这里,我们采用全局搜索方法,搜索这个参数在哪获取的:
不搜不知道,这个结果居然是接口返回的,这个时候我们就要去看一看这个接口是怎么请求的了。
通过查看数据包我们可以发现,这个接口需要我们传一些奇奇怪怪的值,一个巨长无比的data,一个sign,一个时间戳,这个时候我们通过追栈,查看到参数生成的位置。
很显然,是在这里发包的,可以很明显看到每个参数的生成。

sign值逆向
柿子先调软的捏,我们首先看这个短小的sign值,这里括号里面的操作明显就是做一些加法,把fe拼接一个参数然后凭借时间戳和data,做一个签名("HJ6793TJDI86DLS9D")。
现在我们需要知道的就是这个签名算法是什么。我们观察这个参数的长度,嗯,是40位,常见的40位的hash算法有什么呢,显然是sha1,在这里,我们猜测是不是标准算法,我们传入一个特定的值去计算,看他的结果和官方的结果是不是一样的:
很明显,这个参数的值是一样的,好了,这就是一个标准版的sha1.

[AppleScript] 纯文本查看 复制代码
const crypto = require('crypto');

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


data参数破解

破解完sign值后,我们可以查看data是怎么生成的了。
我们在代码中可以看到,data这个值是他在用Ne.es这个方法,把字符串变成数组,我们可以选取一个查看一下:
可以看到,这个方法是问题的关键之一,我们进入这个方法查看:
可以看到这个方法是在一个巨大的对象里面,这个对象是不是很眼熟,没错,这个和anti-content计算过程中的那个对象是同一个对象,我们可以用同一套方案把它抠出来:
[JavaScript] 纯文本查看 复制代码
const zlib = require('zlib');[/size]

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)
            }
        ))
    },
}


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

[JavaScript] 纯文本查看 复制代码
function get_a4_payload(fp, header_catch, config) {[/size]
    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是版本号

[JavaScript] 纯文本查看 复制代码
let an = "2.4.7"


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


完整版博客可见:'https://xsblog.site/post/5'


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|时间戳|加密|CTF WiKi|CTF平台汇总|CTF show|ctfhub|棱角安全|rutracker|攻防世界|php手册|peiqi文库|CyberChef|猫捉鱼铃|手机版|小黑屋|cn-sec|IOTsec-Zone|在线工具|分享屋 ( 鲁ICP备2021028754号 )

GMT+8, 2024-9-19 10:10

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表