飞道的博客

xhs-web校验流程分析

543人阅读  评论(0)

经测试,cookie中需携带gid和timestamp2。参数整理有点乱,仅供参考。

xhsFingerprintV3VERSION: ‘2.1.2’




流程概述

初次无cookie访问,先由 fe_api/burdock/v2/shield/registerCanvas?p=cc 接口返回 timestamp2。

gid 和 gid.sign 参数由 /fe_api/burdock/v2/shield/profile 接口返回。

此时的gid未经过验证校验,不可使用。

然后由 /ca/v1/register 接口注册验证码。

返回rid和验证码信息。

接着由 /ca/v2/fverify 接口提交验证信息。

验证后携带 cookie和 deviceId 信息,由 /fe_api/burdock/v2/shield/captcha?c=pp 接口认证设备和cookie信息。


验证成功后,携带gid和timestamp2可正常访问。


timestamp2

timestamp2和上个版本相同,不重复写了。

xhr定位即可。

sign = l(“G89CfW4k”, t,base64_t);

id = md5 ( sign + “RRq9y03tuV”)


滑块验证参数

Params

_0x225e91, _0xffa132 分别是DES加密时用到的 string 和 key

可复制到在线加密工具中测试,string为0.05,key为8bf2ae7b,加密结果和浏览器返回结果相同。

DES加密算法,模式ECB,填充 ZeroPadding,无iv。


轨迹FN

移动距离 253 。

轨迹参数用Des解密后,可发现最后一组数是拖动的距离。


captcha deviceId

最后 shield/captcha?c=pp 验证时需要用到 deviceId 。

SMSdk.getDeviceId()

getDeviceId()方法


可见_0x622d0e 还未加密,

在执行_0x645c91[_0x36bd7e(0x1c0)](_0x54a23b, _0x301c7f, _0x5deb6b);之后,返回了加密的 deviceId


ProfileData

shield/profile 注册gid时要用到profileData。

定位调试。

'{
   "x1":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36","x2":false,"x3":"zh-CN","x4":24,"x5":8,"x6":1,"x8":20,"x9":"1920;1080","x10":"1920;1040","x11":-480,"x12":"Asia/Shanghai","x13":true,"x14":true,"x15":true,"x16":false,"x17":true,"x18":"unknown","x19":"Win32","x20":"unknown","x21":"PDF Viewer,Portable Document Format,application/pdf,pdf,text/pdf,pdf;Chrome PDF Viewer,Portable Document Format,application/pdf,pdf,text/pdf,pdf;Chromium PDF Viewer,Portable Document Format,application/pdf,pdf,text/pdf,pdf;Microsoft Edge PDF Viewer,Portable Document Format,application/pdf,pdf,text/pdf,pdf;WebKit built-in PDF,Portable Document Format,application/pdf,pdf,text/pdf,pdf","x22":"10cfbbb02b2606dbc2ccb15a3cd2b558","x23":false,"x24":false,"x25":false,"x26":false,"x27":false,"x28":"0;false;false","x29":"4;7;8","x30":"swf object not loaded","x31":"124.04347527516074","x32":"id=;gid=a0034395f52573a18eca1fa7063d8fdcfc49bdd8deeb6a38a3db74db970fb504;audioinput;;id=;gid=a0034395f52573a18eca1fa7063d8fdcfc49bdd8deeb6a38a3db74db970fb504;audiooutput;","x33":0,"x34":0,"x35":0,"x36":1,"x37":"0|0|0|0|0|0|0|0|0","x38":"0|0|0|0|1|0|0|0|0|0"}'

代码比较简单,扣下来调用就行。

但是注意下提交的参数,x22是canvas,要和注册timestamp2的一致,x32是动态值。

x32的gid是指webRTC中本地媒体MediaDevice的groupId,和cookie中的gid不同。

查看chrome的enumerateDevices:

navigator.mediaDevices.enumerateDevices()
  .then((list) => {
   
    console.log(list);
  });

这个Id每次访问会变更,可以自己随机生成一串。

所在设备在被限制时,可更换canvasId和enumerateDevices的Id。


x-s-common

x-s-common用的地方比较多,注册和校验都需要。

x-s-common定位。

x-s-common的生成主要依赖cookie中的smidV2,其他参数如下。

x5: Dt.a.get(“a1”),
x8: localStorage.getItem(“b1”),
x9: fn(P()(t = P()(n = “”.concat®).call(n, o)).call(t, localStorage.getItem(“b1”)))


x5生成

x5依赖cookie的a1参数

document = {
   }
document.cookie = 'smidV2=202210181839545813c4484407e8fd7dec32a81165dfce00c99e85a19023ff0; a1=1843b69be841gkjrtg498n4cr8tw29sxm8l4mk2cs00000809560; gid=yY4qDKjDSq68yY4qDKjDdYTJY4ykTVF9k4jY1SMI4SFY9488EJjMC6888Y8j2K88JfjSJK2y'

n = {
   
    read: function(e) {
   
        return '"' === e[0] && (e = e.slice(1, -1)),
        e.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
    },
    write: function(e) {
   
        return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, decodeURIComponent)
    }
}
function x5_get(e) {
   
    if ("undefined" != typeof document && (!arguments.length || e)) {
   
        for (var t = document.cookie ? document.cookie.split("; ") : [], r = {
   }, o = 0; o < t.length; o++) {
   
            var i = t[o].split("=")
              , a = i.slice(1).join("=");
            try {
   
                var s = decodeURIComponent(i[0]);
                if (r[s] = n.read(a, s),
                e === s)
                    break
            } catch (c) {
   
                console.log(c)
            }
        }
        return e ? r[e] : r
    }
}

console.log("x5:",x5_get("a1"))

 

x8生成

定值 localStorage.getItem(“b1”)

x9生成

x9简化之后就是 fn(localStorage.getItem(“b1”))

主要生成代码如下:(部分太长的代码段没贴,如tn、cn、rn、en)

var fn = function(e) {
   
        for (var t, n, r = 240, o = 253, i = 258, a = 197, s = 264, c = 230, u = 209, l = 262, f = 237, p = 261, d = en, h = {
   
            zDTTc: function(e, t) {
   
                return e === t
            },
            nrqZm: d(251),
            MyaJd: function(e, t) {
   
                return e < t
            },
            fqoGO: function(e, t) {
   
                return e ^ t
            },
            qcRcu: function(e, t) {
   
                return e ^ t
            },
            yhYKD: function(e, t) {
   
                return e & t
            },
            qxGVA: function(e, t) {
   
                return e >>> t
            },
            jTaFf: function(e, t) {
   
                return e ^ t
            },
            kQlol: function(e, t) {
   
                return e >>> t
            }
        }, v = 256, m = []; v--; m[v] = t >>> 0)
            for (n = 8,
            t = v; n--; )
                t = 1 & t ? 3988292384 ^ h['kQlol'](t, 1) : h['kQlol'](t, 1);
        return function(e) {
   
            var t = d;
            if (h['zDTTc']('string', h['nrqZm'])) {
   
                for (var n = 0, r = -1; h['MyaJd'](n, e['length']); ++n)
                    r = h['fqoGO'](m[h['qcRcu'](h['yhYKD'](r, 255), e['charCod' + 'eAt'](n))], h['qxGVA'](r, 8));
                return h['qcRcu'](h['jTaFf'](r, -1), 3988292384)
            }
            for (n = 0,
            r = -1; n < e['length']; ++n)
                r = m[h['jTaFf'](255 & r, e[n])] ^ h['qxGVA'](r, 8);
            return -1 ^ r ^ 3988292384
        }
    }()

var x9 = fn("8utvIv7eYlPiIvcmIk0sxn5e6D3s0PwfQ0MAHZFRICH0+VthIhDaNom3nqt9aPwjI3LdKU7e3BkLmbPH+LENaqwMIkveTb5sVBAefVt18WukpqtVeVt4muw4IC6sx7YEIE3e0/Kekf6e0WPP87MfPMdsxVw/IhWNICu=")
console.log("x9:",x9)

 

smidV2

hook cookie。hook代码可到 工具站 查找。


定位分析,

在注册设备id过程中,_0x622d0e时生成了smidV2

这样看,deviceId 和x-s-common参数有关联关系。


a1

生成 x-s-common 时需要a1,注册sig时也需要。

a1用hook插件hook。


a1 = M

M = P()(S = “”.concat(I)).call(S, N).substring(0, 52)

格式化一下

var j, A, k, C, S, R = 0, E = "000", 
    I = P()(j = P()(A = P()(k = P()(C = "".concat((+new Date).toString(16))).call(C, zt(30))).call(k, R)).call(A, 0)).call(j, E), 
    N = sn(I), 
    M = P()(S = "".concat(I)).call(S, N).substring(0, 52);

代码段翻译一下,先生成I,I = “”.concat((+new Date).toString(16)) + 随机30个字符 + 00000
再生成N, N = sn(I)
最后M = (I +N).substring(0, 52)

a1生成代码

function rn() {
   
  var e = ["bdXdq", "length", "susu", "zDTTc", "q42KWYj", "xLgpd", "0DSfdik", "nFsLu", "30jemUol", "fHjkg", "saFEz", "hECvuRX", "HjBDl", "yhYKD", "x3VT16I", "EYvYO", "pKSXB", "bnEJJ", "pngG8yJ", "oHQtNP+", "rQkvJ", "ETash", "suu", "5843672pCKTGz", "ZmserbB", "3044202AJYnvI", "8973318KqrDXr", "HQdfx", "bYHmD", "14nymtFk", "lGBrI", "sussusu", "join", "charCod", "qcRcu", "FoGzb", "XitqP", "JPbAI", "1171539qrVTtq", "ZuEEa", "BeOkX", "qxGVA", "alAuw", "qTmwS", "kQlol", "WeSiH", "auZwG", "BcSKa", "99515NEInyQ", "lUAFM97", "mVZzE", "284tzkRbL", "wOcza/L", "IXpoi", "nxSCy", "string", "Xcesv", "nrqZm", "6045636qIaDIN", "fromCha", "HWcOv", "2MGcsTE", "MyaJd", "99oQgoiy", "350367CQNruQ", "jTaFf", "eAt", "pbhrw", "fqoGO", "rCode", "lJSzd", "NshPR", "LREye", "push", "HyBFO", "charAt", "ZcEyb", "Mduoa"];
  return e
}

function en(e, t) {
   
        var n = rn();
        return (Yt = function(e, t) {
   
            return n[e -= 191]
        }
        )(e, t)
}

var sn = function(e) {
   
    for (var t, n = 268, r = 194, o = 233, i = 197, a = en, s = {
   
        nxSCy: function(e, t) {
   
            return e & t
        },
        LREye: function(e, t) {
   
            return e ^ t
        },
        ZcEyb: function(e, t) {
   
            return e >>> t
        },
        JPbAI: function(e, t) {
   
            return e >>> t
        },
        alAuw: function(e, t) {
   
            return e ^ t
        }
    }, c = [], u = 0; u < 256; u++) {
   
        t = u;
        for (var l = 0; l < 8; l++)
            t = s['nxSCy'](1, t) ? s['LREye'](3988292384, s['ZcEyb'](t, 1)) : s['JPbAI'](t, 1);
        c[u] = t
    }
    for (var f = -1, p = 0; p < e['length']; p++)
        f = s['alAuw'](s['JPbAI'](f, 8), c[s['nxSCy'](255, s['alAuw'](f, e['charCod' + 'eAt'](p)))]);
    return s['JPbAI'](-1 ^ f, 0)
};

var lx1 = '';
  for (i = 0; i < 30; i++) {
   
    lx1 += "abcdefghijklmnopqrstuvwxyz1234567890"[Math.floor(36 * Math.random())]
  }

var I = "".concat((+new Date).toString(16))+lx1+"00000"

console.log(I)
console.log(sn(I))
console.log((I+sn(I)).substring(0, 52));

 

x-b3-traceid

.
翻译为python代码。

import random,math
e = ""
for t in range(16):
    e+="abcdef0123456789"[math.floor(16 * random.random())]
print(e)

x-sign

目前来看同url的x-sign是固定的

‘https://www.xiaohongshu.com/fe_api/burdock/v2/shield/captcha?c=pp’

def get_xsign(url):
    screen_key = "WSUDD"
    _st = url.split(".com")[-1] + screen_key
    import hashlib
    m = hashlib.md5()
    m.update(_st.encode(encoding='UTF-8'))
    md5String = m.hexdigest()
    return "X" + md5String

gid

gid 和 注册时的 IP 绑定。需要x-s-common和profileData

通过 /fe_api/burdock/v2/shield/profile接口注册。

先本地生成 smidV2和a1。
然后生成headers中的 x-s-common 、 x-b3-traceid 、x-sign。
接着生成profileData。
最后构建请求注册gid。


转载:https://blog.csdn.net/weixin_43582101/article/details/127619659
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场