小言_互联网的博客

快手指纹之十八罗汉

462人阅读  评论(0)

这里的十八罗汉是笔者给快手网页端指纹起的名字,用以记录和感叹。

起因在尝试解决风控时屡调不通,修改了各种参数,也对埋点日志进行了追踪,模拟后依旧无法完美解决。

于是回想起验证时的额外参数,比如下面的18个指纹参数,尽管有一半是重复的

指纹的重要性相信大家都明白,一套指纹用于一个单独的用户,如果某个参数和IP有关系,那切换代理也无用。

比如我当前环境中会出现验证码的重复校验,导致生成的did可用性很差。

除了上述18个指纹ID,还有时区、语言、字体、系统、驱动、内核、分辨率等检测。


指纹生成分析

由于偶尔通过校验并不能用于量级业务,所以有待进一步分析。

需要注意该JS仅在验证时可进入,并且该JS是webpack打包的。

这里有十八罗汉的生成方法。

现在还未形成33位的字符。

继续断点调试就能找到最终的值。

且在此处进行了赋值操作。


经过一阵子分析,找到对象中的关键词 info,然后通过搜素找到加密转换的位置。


其通过ec进行转换。


可在控制台调试。


本地指纹加密

把ec拿出来,以及ec中所调用的方法。

一些info的值太长了,我只截取了开头。

function ec(n) {
   
    var t = n.error
      , e = n.version
      , r = n.info;
    return n.info ? "".concat(e).concat(tc(r)) : "E".concat(e).concat(tc(t || "UNKNOWN"))
}
function Pr(n, t) {
   
    n = [n[0] >>> 16, 65535 & n[0], n[1] >>> 16, 65535 & n[1]],
    t = [t[0] >>> 16, 65535 & t[0], t[1] >>> 16, 65535 & t[1]];
    var e = [0, 0, 0, 0];
    return e[3] += n[3] + t[3],
    e[2] += e[3] >>> 16,
    e[3] &= 65535,
    e[2] += n[2] + t[2],
    e[1] += e[2] >>> 16,
    e[2] &= 65535,
    e[1] += n[1] + t[1],
    e[0] += e[1] >>> 16,
    e[1] &= 65535,
    e[0] += n[0] + t[0],
    e[0] &= 65535,
    [e[0] << 16 | e[1], e[2] << 16 | e[3]]
}
function Lr(n, t) {
   
    n = [n[0] >>> 16, 65535 & n[0], n[1] >>> 16, 65535 & n[1]],
    t = [t[0] >>> 16, 65535 & t[0], t[1] >>> 16, 65535 & t[1]];
    var e = [0, 0, 0, 0];
    return e[3] += n[3] * t[3],
    e[2] += e[3] >>> 16,
    e[3] &= 65535,
    e[2] += n[2] * t[3],
    e[1] += e[2] >>> 16,
    e[2] &= 65535,
    e[2] += n[3] * t[2],
    e[1] += e[2] >>> 16,
    e[2] &= 65535,
    e[1] += n[1] * t[3],
    e[0] += e[1] >>> 16,
    e[1] &= 65535,
    e[1] += n[2] * t[2],
    e[0] += e[1] >>> 16,
    e[1] &= 65535,
    e[1] += n[3] * t[1],
    e[0] += e[1] >>> 16,
    e[1] &= 65535,
    e[0] += n[0] * t[3] + n[1] * t[2] + n[2] * t[1] + n[3] * t[0],
    e[0] &= 65535,
    [e[0] << 16 | e[1], e[2] << 16 | e[3]]
}
function Kr(n, t) {
   
    return t %= 64,
    32 === t ? [n[1], n[0]] : t < 32 ? [n[0] << t | n[1] >>> 32 - t, n[1] << t | n[0] >>> 32 - t] : (t -= 32,
    [n[1] << t | n[0] >>> 32 - t, n[0] << t | n[1] >>> 32 - t])
}
function qr(n, t) {
   
    return t %= 64,
    0 === t ? n : t < 32 ? [n[0] << t | n[1] >>> 32 - t, n[1] << t] : [n[1] << t - 32, 0]
}
function $r(n, t) {
   
    return [n[0] ^ t[0], n[1] ^ t[1]]
}
function nc(n) {
   
    return n = $r(n, [0, n[0] >>> 1]),
    n = Lr(n, [4283543511, 3981806797]),
    n = $r(n, [0, n[0] >>> 1]),
    n = Lr(n, [3301882366, 444984403]),
    n = $r(n, [0, n[0] >>> 1]),
    n
}
function tc(n, t) {
   
    n = n || "",
    t = t || 0;
    var e, r = n.length % 16, c = n.length - r, i = [0, t], a = [0, t], o = [0, 0], u = [0, 0], x = [2277735313, 289559509], s = [1291169091, 658871167];
    for (e = 0; e < c; e += 16)
        o = [255 & n.charCodeAt(e + 4) | (255 & n.charCodeAt(e + 5)) << 8 | (255 & n.charCodeAt(e + 6)) << 16 | (255 & n.charCodeAt(e + 7)) << 24, 255 & n.charCodeAt(e) | (255 & n.charCodeAt(e + 1)) << 8 | (255 & n.charCodeAt(e + 2)) << 16 | (255 & n.charCodeAt(e + 3)) << 24],
        u = [255 & n.charCodeAt(e + 12) | (255 & n.charCodeAt(e + 13)) << 8 | (255 & n.charCodeAt(e + 14)) << 16 | (255 & n.charCodeAt(e + 15)) << 24, 255 & n.charCodeAt(e + 8) | (255 & n.charCodeAt(e + 9)) << 8 | (255 & n.charCodeAt(e + 10)) << 16 | (255 & n.charCodeAt(e + 11)) << 24],
        o = Lr(o, x),
        o = Kr(o, 31),
        o = Lr(o, s),
        i = $r(i, o),
        i = Kr(i, 27),
        i = Pr(i, a),
        i = Pr(Lr(i, [0, 5]), [0, 1390208809]),
        u = Lr(u, s),
        u = Kr(u, 33),
        u = Lr(u, x),
        a = $r(a, u),
        a = Kr(a, 31),
        a = Pr(a, i),
        a = Pr(Lr(a, [0, 5]), [0, 944331445]);
    switch (o = [0, 0],
    u = [0, 0],
    r) {
   
    case 15:
        u = $r(u, qr([0, n.charCodeAt(e + 14)], 48));
    case 14:
        u = $r(u, qr([0, n.charCodeAt(e + 13)], 40));
    case 13:
        u = $r(u, qr([0, n.charCodeAt(e + 12)], 32));
    case 12:
        u = $r(u, qr([0, n.charCodeAt(e + 11)], 24));
    case 11:
        u = $r(u, qr([0, n.charCodeAt(e + 10)], 16));
    case 10:
        u = $r(u, qr([0, n.charCodeAt(e + 9)], 8));
    case 9:
        u = $r(u, [0, n.charCodeAt(e + 8)]),
        u = Lr(u, s),
        u = Kr(u, 33),
        u = Lr(u, x),
        a = $r(a, u);
    case 8:
        o = $r(o, qr([0, n.charCodeAt(e + 7)], 56));
    case 7:
        o = $r(o, qr([0, n.charCodeAt(e + 6)], 48));
    case 6:
        o = $r(o, qr([0, n.charCodeAt(e + 5)], 40));
    case 5:
        o = $r(o, qr([0, n.charCodeAt(e + 4)], 32));
    case 4:
        o = $r(o, qr([0, n.charCodeAt(e + 3)], 24));
    case 3:
        o = $r(o, qr([0, n.charCodeAt(e + 2)], 16));
    case 2:
        o = $r(o, qr([0, n.charCodeAt(e + 1)], 8));
    case 1:
        o = $r(o, [0, n.charCodeAt(e)]),
        o = Lr(o, x),
        o = Kr(o, 31),
        o = Lr(o, s),
        i = $r(i, o)
    }
    return i = $r(i, [0, n.length]),
    a = $r(a, [0, n.length]),
    i = Pr(i, a),
    a = Pr(a, i),
    i = nc(i),
    a = nc(a),
    i = Pr(i, a),
    a = Pr(a, i),
    ("00000000" + (i[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (i[1] >>> 0).toString(16)).slice(-8) + ("00000000" + (a[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (a[1] >>> 0).toString(16)).slice(-8)
}

var canvasGraph = {
   
    error: "",
    info: "data:image/png;base64,iVBORw0KGgoAAA",
    name: "canvasGraph",
    version: 1
}
console.log("canvasGraph:",ec(canvasGraph))

var canvasTextZh = {
   
    error: "",
    info: "data:image/png;base64,iVBORw0KGgoAA",
    name: "canvasTextZh",
    version: 1
}
console.log("canvasTextZh:",ec(canvasTextZh))

var webglGpu = {
   
    error: "",
    info: "\"{\"glRenderer\":\"WebKit WebGL\",\"glVendor\":\"WebKit\",\"unmaskRenderer\":\"ANGLE (NVIDIA, NVIDIA GeForce GT 710 Direct3D11 vs_5_0 ps_5_0, D3D11)\",\"unmaskVendor\":\"Google Inc. (NVIDIA)\"}\"",
    name: "webglGpu",
    version: 1
}
console.log("webglGpu:",ec(webglGpu))

运行后可以和开头的指纹对比一下,结果是相同的。


canvasGraph

另外说一下 canvas 这种图片内容的生成,给大家扣了一个。

该部分执行后会返回一段字符串,就是canvasGraph对应的 n.info,再用ec进行加密就是canvasGraph指纹了。

本地node生成的话可以看之前的文章《浏览器指纹解读》

function Ur(n) {
   
            var t = document.createElement("canvas")
              , e = t.getContext(n);
            return {
   
                canvas: t,
                context: e
            }
        }
function _r(n) {
   
            return n.toDataURL()
        }
function canvasGraph2() {
   
    var n = Ur("2d")
      , t = n.canvas
      , e = n.context;
        var r = e;
        r.globalCompositeOperation = "multiply";
        for (var c = 0, i = [["#f2f", 40, 40], ["#2ff", 80, 40], ["#ff2", 60, 80]]; c < i.length; c++) {
   
        var a = i[c]
          , o = a[0]
          , u = a[1]
          , x = a[2];
        r.fillStyle = o,
        r.beginPath(),
        r.arc(u, x, 40, 0, 2 * Math.PI, !0),
        r.closePath(),
        r.fill()
        }
        return r.fillStyle = "#f9c",
        r.arc(60, 60, 60, 0, 2 * Math.PI, !0),
        r.arc(60, 60, 20, 0, 2 * Math.PI, !0),
        r.fill("evenodd"),
        _r(t)
        }
canvasGraph2()

备注

特征一共有这些参数:userAgent、timeZone、language、cpuCoreCnt、platform、riskBrowser、webDriver、exactRiskBrowser、webDriverDeep、exactRiskBrowser2、webDriverDeep2、resolution、pixelDepth、colorDepth、plugins、canvasGraphFingerPrint、canvasTextEn、canvasTextFingerPrintEn、canvasTextZh、canvasTextFingerPrintZh、webglGraphFingerPrint、webglGpu、webglGPUFingerPrint、fontListEn、cssFontFingerPrintEn、fontListZh、cssFontFingerPrintZh、voiceFingerPrint、audioTriangle、nativeFunc。

所以我们可以根据生成规则去创建一些指纹用于校验。

关注公众号《Pythonlx》一起交流和学习!


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