这里的十八罗汉是笔者给快手网页端指纹起的名字,用以记录和感叹。
起因在尝试解决风控时屡调不通,修改了各种参数,也对埋点日志进行了追踪,模拟后依旧无法完美解决。
于是回想起验证时的额外参数,比如下面的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》一起交流和学习!