快手指纹之十八罗汉

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

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

于是回想起验证时的额外参数,比如下面的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》一起交流和学习!

点赞

发表回复