本文最后更新于 2024-07-06,文章内容可能已经过时。

本来应该当天就搞出来的,结果因为上班感觉有点累,一直拖到现在。

算法和log验签差不多的,这次我分为了两个模块。

生成fingerprint

function q_() {
    var u = 'uct6d0jhqw';
    var c = V_(u, 6);
    var l = W_();
    var i = J_(u, c);
    var p = {};
    p.size = l, p.num = i;
    var h = K_(p) + c + K_({
        size: 9 - l, 
        num: i
    }) + l;
    var s = h.split("");
    var d = s.slice(0, 14);
    var a = s.slice(14);
    var f = [];
    for (; d.length > 0;) f.push((35 - parseInt(d.pop(), 36)).toString(36));
    f = f.concat(a);
    var v = f.join("");
    return v;
}

function W_() {
    return (Math.random() * 10) | 0
}

function V_(t, e) {
    var o, i = [], a = t.length, u = U_(t);
    try {
        for (u.s(); !(o = u.n()).done;) {
            var c = o.value;
            if (((Math.random() * a) < e) && (i.push(c), 0 == --e)) break;
            a--
        }
    } catch (t) {
        u.e(t)
    } finally {
        u.f()
    }
    var s = "";

    for (var l = 0; (l < i.length); l++) {
        var d = ((Math.random() * (i.length - l)) | 0);
        s += i[d], i[d] = i[((i.length - l) - 1)]
    }
    return s
}

function K_(t) {
    var r = t.size, o = t.num;
    for (var a = ""; r--;) a += o[((Math.random() * o.length) | 0)];
    return a
}

function J_(t, e) {
    for (var a = 0; a < e.length; a++) {
        var u = t.indexOf(e[a]);
        (u !== -1) && (t = t.replace(e[a], ""))
    }
    return t
}

function U_(t, e) {
    var p;
    if ((typeof Symbol === 'undefined') || null == t[Symbol.iterator]) {
        if (Array.isArray(t) || (p = G_(t)) || (e && t) && (typeof t.length === 'number')) {
            p && (t = p);
            var f = 0, l = function () {
            }, d = {};
            return d.s = l, d.n = function () {
                var e = {};
                if (e.done = !0, f >= t.length) return e;
                var r = {};
                return r.done = !1, r.value = t[f++], r
            }, d.e = function (t) {
                throw t
            }, d.f = l, d
        }
        throw new TypeError('Invalid attempt to iterate non-iterable instance.In order to be iterable, non-array objects must have a [Symbol.iterator]() method.')
    }
    var u, c = !0, s = !1;
    return {
        s: function () {
            p = t[Symbol.iterator]()
        }, n: function () {
            var t = p.next();
            return c = t.done, t
        }, e: function (t) {
            s = !0, u = t
        }, f: function () {
            try {
                !c && (p.return != null) && p.return()
            } finally {
                if (s) throw u
            }
        }
    };
}

module.exports.q_ = q_

生成H5ST并发送一个请求进行测试

const CryptoJS = require('crypto-js');
const promisify = require('util').promisify;
const request = promisify(require('request'));
const testFigerprint = require('./testFigerprint.js');

var _fingerprint = testFigerprint.q_();
var random = randomString(10, true);
var _version = '4.1';
var _token, __genKey, _isNormal = !1, _defaultToken = '';
var fv = 'v0.1.6';

// appId和main方法中的测试参数数组,应由用户自己传递进来
var _appId = "2a045";

async function getAlgoBody() {
    var r = {
        "ai": _appId,
        "av": "5.0;appBuild/98730;ef/1;ep/%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1684847465219%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22sv%22%3A%22CJO%3D%22%2C%22ad%22%3A%22DJdtZNdtZNY1DJYmCJDuCq%3D%3D%22%2C%22od%22%3A%22DwO5DJrtZwG5ZNCyDJu3ZK%3D%3D%22%2C%22ov%22%3A%22CzK%3D%22%2C%22ud%22%3A%22DJdtZNdtZNY1DJYmCJDuCq%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D;jdSupportDarkMode/0;Mozilla/5.0 (Linux; Android 11; Redmi K20 Pro Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.77 Mobile Safari/537.36",
        "fp": _fingerprint,
        "h": 700,
        "l": "zh-CN",
        "ls": "zh-CN,zh",
        "ml": 0,
        // "og": "https://wbbny.m.jd.com",
        "oh": 700,
        "ow": 400,
        "pf": undefined,
        "pl": 0,
        "pm": {
            "ps": "prompt",
            "np": "default"
        },
        // "pp": {
            // "p3": pin
        // },
        "pp1": "",
        "pr": "1",
        "random": random,
        "re": "",
        "referer": "",
        "sua": "Linux; Android 11; Redmi K20 Pro Build/RKQ1.200826.002; wv",
        "ua": "jdapp;android;11.8.0;;;M/5.0;appBuild/98730;ef/1;ep/%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1684847465219%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22sv%22%3A%22CJO%3D%22%2C%22ad%22%3A%22DJdtZNdtZNY1DJYmCJDuCq%3D%3D%22%2C%22od%22%3A%22DwO5DJrtZwG5ZNCyDJu3ZK%3D%3D%22%2C%22ov%22%3A%22CzK%3D%22%2C%22ud%22%3A%22DJdtZNdtZNY1DJYmCJDuCq%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D;jdSupportDarkMode/0;Mozilla/5.0 (Linux; Android 11; Redmi K20 Pro Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.77 Mobile Safari/537.36",
        // "url": "https://wbbny.m.jd.com/pb/014710620/mTPLZGkAcayB5UvZ6uZCtL3M6ca/index.html?from=home&babelChannel=jdfuceng&sid=43db4ecf1c01943617abc42910d3d0aw#/pages/home/index/index",
        "v": fv,
        "w": 400,
        "wc": 0,
        "wd": 0
    }
    let s = CryptoJS.AES.encrypt(JSON.stringify(r, null, 2), CryptoJS.enc.Utf8.parse('wm0!@w-s#ll1flo('), {
        iv: CryptoJS.enc.Utf8.parse('0102030405060708'),
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
    }).ciphertext.toString();

    var g = {
        "fingerprint": _fingerprint,
        "appId": _appId,
        "version": _version,
        "timeout": 5,
        "env": s
    };
    return g;
}

async function __requestAlgorithm(body) {
    var i = body.fingerprint,
        a = body.appId,
        u = body.version,
        s = body.env;
    let result = await request({
        url: 'https://cactus.jd.com/request_algo',
        method: 'POST',
        json: true,
        body: {
            'version': u,
            'fp': i,
            'appId': a,
            'timestamp': Date.now(),
            'platform': 'web',
            'expandParams': s,
            'fv': fv
        }
    })
    if (result.body.status === 200 && result.body.data && result.body.data.result) {
        // console.log(`获取Algo成功:${JSON.stringify(result.body)}`)
        var c = result.body.data.result,
            s = c.algo,
            p = c.tk,
            h = c.fp;
        if (s && p) {
            var v = {
                algo: s,
                token: p,
                fp: h
            };
            await tt(v)
        } else {
            throw new Error('data.result format error.');
        }
        return result.body;
    }
    // console.log(result)
    throw new Error('获取Algo失败');
}

async function tt(t) {
    var o = t.algo,
        i = t.token;
    await __parseAlgorithm(i, o);
}

async function __parseAlgorithm(t, n) {
    if (_token = (t || ""), __genKey = n && new Function("return ".concat(n))() || null, _token && __genKey) {
        _isNormal = !0;
    }
}

async function __genDefaultKey() {
    console.log('__genDefaultKey')
}

function yx() {
    var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : Date.now(),
        e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "yyyy-MM-dd", n = new Date(t),
        r = e, o = {
            "M+": n.getMonth() + 1,
            "d+": n.getDate(),
            "D+": n.getDate(),
            "h+": n.getHours(),
            "H+": n.getHours(),
            "m+": n.getMinutes(),
            "s+": n.getSeconds(),
            "w+": n.getDay(),
            "q+": Math.floor((n.getMonth() + 3) / 3),
            "S+": n.getMilliseconds()
        };
    return /(y+)/i.test(r) && (r = r.replace(RegExp.$1, "".concat(n.getFullYear()).substr(4 - RegExp.$1.length))), Object.keys(o).forEach((function (t) {
        if (new RegExp("(".concat(t, ")")).test(r)) {
            var e = "S+" === t ? "000" : "00";
            r = r.replace(RegExp.$1, 1 == RegExp.$1.length ? o[t] : "".concat(e).concat(o[t]).substr("".concat(o[t]).length))
        }
    })), r
}

function __genSign(t, n) {
    var u = n.map((function (t) {
        return t.key + ":" + t.value
    })).join("&");
    var s = CryptoJS.MD5(t + u + t).toString(CryptoJS.enc.Hex);
    return s
}

function __genSignParams(t, e, n, r) {
    return ["".concat(n), "".concat(_fingerprint), "".concat(_appId), "".concat(_isNormal ? _token : _defaultToken), "".concat(t), "".concat(_version), "".concat(e), "".concat(r)].join(";")
}

async function __makeSign(t, n) {
    var v = "";
    var _ = Date.now();
    var s =  yx(_, "yyyyMMddhhmmssSSS");
    var h = s + "04";
    var f = _token;
    var l = _defaultToken;
    var d = _fingerprint;
    var p = _appId;
    if (_isNormal) {
        v = __genKey(f, d, h, p, CryptoJS).toString() || ""
    } else {
        if (l) {
            v = __genDefaultKey(l, d, h, p)
        } else {
            _defaultToken = dE(_fingerprint); 
            v = __genDefaultKey(_defaultToken, d, h, p);
        }
    }
    var k = {};
    if (!v) {
        if (f || l) {
            // var g = {
            //     code: bx.GENERATE_SIGNATURE_FAILED, 
            //     message: 'generate key failed'
            // };
            // this._onSign(g)
        } else {
            // var m = {
            //     code: bx.TOKEN_EMPTY, 
            //     message: 'token is empty'
            // };
            // this._onSign(m)
        }
        return k
    }
    var x = __genSign(v, t);
    var w = t.map((function (t) {
        return t.key
    })).join(",");
    var S = 1;
    var A = __genSignParams(x, _, s, n);
    return {
        _stk: w, 
        _ste: S, 
        h5st: A
    };
}

async function __collect(t) {
    var i, a;
    var t = {
        'fp': _fingerprint,
        // 'pp':{
            // 'p3': pin
        // }, 
        'random': random, 
        'referer': "", 
        'sua': "Linux; Android 11; Redmi K20 Pro Build/RKQ1.200826.002; wv", 
        'v': fv
    }
    t.fp = _fingerprint;
    var i = JSON.stringify(t, null, 2);
    var a = CryptoJS.AES.encrypt(i, CryptoJS.enc.Utf8.parse('HL4|FW#Chc3#q?0)'), {
        iv: CryptoJS.enc.Utf8.parse('0102030405060708'),
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return a.ciphertext.toString();
}

async function main() {
    try {
        var time = new Date().getTime();
        var i = [{
            "key": "appid",
            "value": "signed_wh5"
        }, {
            "key": "body",
            "value": CryptoJS.SHA256('{}').toString(CryptoJS.enc.Hex)
        }, {
            "key": "client",
            "value": "wh5"
        }, {
            "key": "clientVersion",
            "value": "1.0.0"
        }, {
            "key": "functionId",
            "value": "promote_getHomeData"
        }, {
            "key": "t",
            "value": time
        }]

        var algoBody = await getAlgoBody();
        await __requestAlgorithm(algoBody);
        var a = await __collect();
        var u = await __makeSign(i, a);
        console.log(`获取h5st: ${u.h5st}`)
        await test(time, u.h5st);
    } catch (e) {
        console.error(e);
    }
}

main()

/**
 * 2023 618 拆快递 测试接口,不需要填写cookie,环境异常会直接提醒
 * @param {*} time 
 * @param {*} h5st 
 */
async function test(time, h5st) {
    let result = await request({
        url: `https://api.m.jd.com/`,
        method: 'post',
        headers: {
            'cookie': '',
            'user-agent': 'jdapp;android;11.8.0;;;M/5.0;appBuild/98730;ef/1;ep/%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1684847465219%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22sv%22%3A%22CJO%3D%22%2C%22ad%22%3A%22DJdtZNdtZNY1DJYmCJDuCq%3D%3D%22%2C%22od%22%3A%22DwO5DJrtZwG5ZNCyDJu3ZK%3D%3D%22%2C%22ov%22%3A%22CzK%3D%22%2C%22ud%22%3A%22DJdtZNdtZNY1DJYmCJDuCq%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D;jdSupportDarkMode/0;Mozilla/5.0 (Linux; Android 11; Redmi K20 Pro Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.77 Mobile Safari/537.36',
            'origin': 'https://wbbny.m.jd.com'
        },
        form: {
            'functionId': 'promote_getHomeData',
            'body': '{}',
            'appid': 'signed_wh5',
            'client': 'wh5',
            'clientVersion': '1.0.0',
            'screen': '400*0',
            'wqDefault': 'false',
            't': time,
            'h5st': h5st
        }
    })
    console.log(result.body)
}

/**
 * 2022 扎年兽扣的随机字符串, 2023 618拆快递不是这个方法,但是可以正常使用
 * @param {*} t 
 * @param {*} e 
 * @returns 
 */
function randomString(t, e) {
    var a = "", u = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for (var s = 0; s < t; s++) {
        var c = u;
        if (s === 0 && e) {
            c = u.slice(1);
        }
        var f = Math.round((Math.random() * (c.length - 1)));
        a += c.substring(f, (f + 1))
    }
    return a
}

1685844045721.png

题外话,ChatGPT是真的好用啊,自己处理字符串混淆还原后,其实代码还是比较难看得懂,结果可以直接丢给GPT,如图,上面注释的是原代码,下面是GPT处理后的代码。

1685844522703.png