该系列文章仅限于某验滑块研究,不会公开具体算法源码,欢迎讨论
本文关联文章:
纵观
入微
芥子
浩瀚
一. 观察verify请求
-
我们看一下verify请求的Initiator,可以观察到全是来自于gcaptcha4.js这个文件,所以该文件也是我们此次的目标所在
-
点开gcaptcha4.js这个文件,格式化完,13339行,嗯,真的是混淆他妈给混淆开门,混淆到家了
- 这时候我们有两种选择,硬刚或者AST,我这里用的是AST还原js文件,然后再使用chrome伤的reres插件把混淆的js替换掉,再来一步一步调试。这里的AST代码我放在了github上面,插件reres的安装以及使用各位自行谷歌。AST还原代码
-
做到下面这个效果,就算替换成功了
二. 定位w参数加密位置
- 搜索w,我们这里根据经验可以搜索以下关键词来定位
w 或 .w 或 'w' 或 "w"
-
这里搜索"w"有我们想要的返回,我们在2527行打上断点,可以看到w的值r在2525行定义,我们也打上断点,刷新一下,成功断上,至此我们已完成w参数的加密位置定位
三. 分析w参数加密算法
- 简化参数w的值r的定义
var r = (0, d["default"])(l["default"]["stringify"](e), a)
等价于,其原理是js的逗号操作符对它的每个操作对象求值(从左至右),然后返回最后一个操作对象的值
var r = d["default"](l["default"]["stringify"](e), a)
等价于,其原理均是js获取对象的属性
var r = d.default(l.default.stringify(e), a)
-
分析l.default.stringify(e)含义
可以看出此处就是对e进行json序列化操作
-
分析d.default(l.default.stringify(e), a)含义
跟进d函数
function a(e, t) {
var s = t["options"];
// !s["pt"] || "0" === s["pt"] 为false,恒不执行
if (!s["pt"] || "0" === s["pt"])
return r["default"]["urlsafe_encode"](e);
// "1" === s["pt"]为true,恒执行
if ("1" === s["pt"]) {
var n = (0,
c["guid"])()
, a = new _["default"]()["encrypt"](n);
// !a || 256 !== a["length"]为false,恒不执行
while (!a || 256 !== a["length"])
n = (0,
c["guid"])(),
a = new _["default"]()["encrypt"](n);
var o = i["default"]["encrypt"](e, n);
return (0,
c["arrayToHex"])(o) + a;
}
}
等价于
function a(e, t) {
var n = c["guid"])(),
a = new _["default"]()["encrypt"](n);
o = i["default"]["encrypt"](e, n);
return c["arrayToHex"])(o) + a;
}
我们到这里可以确定参数t并没有被使用,参数e就是w的明文,我们接着分析json序列化之前e的成分
- 继续分析e的组成
{
'device_id': 'A8A0', # 固定值
'em': {
'cp': 0,
'ek': '11',
'nt': 0,
'ph': 0,
'sc': 0,
'si': 0,
'wd': 1,
}, # 固定值
'ep': '123', # 固定值
'geetest': 'captcha', # 固定值
'fq6a': '1925502591', # 固定值
'lang': 'zh', # 固定值
'lot_number': '7e22264d4f3e4dd8a6ffbf6e82e1122d', # load请求返回值
'passtime': 166, # 通过时间
'pow_msg': '1|0|md5|2022-03-25T14:23:36.364152+08:00|24f56dc13c40dc4a02fd0318567caef5|7e22264d4f3e4dd8a6ffbf6e82e1122d||29f07cebf938aa4e', # load请求返回加上某算法返回值,后面再分析
'pow_sign': '2b47a3a9425dd19dd5abf902c8bb0763', # pow_msg的md5值,为何是md5,写在了pow_msg上
'setLeft': 88, # 滑动距离
'track': [[38, 18, 0], [1, 0, 33]......], # 轨迹
'userresponse': 87.47978686742837 # 某算法返回值
}
4.1 pow_msg和pow_sign我们放一起分析
u = n + "|" + a + "|" + s + "|" + o + "|" + t + "|" + e + "|" + r + "|"
# 上面n、a、s、o、t、e、r的值则来自load请求的response中
p = (0, b["guid"])()
g = u + p
pow_msg = u + p
pow_sign = md5(g)
我们还需要分析下b.guid()是什么算法,继续跟进
var a = function () {
function e() {
return (65536 * (1 + Math["random"]()) | 0)["toString"](16)["substring"](1);
}
return function () {
return e() + e() + e() + e();
};
}();
t["guid"] = a;
4.2. set_left、track、passtime、userresponseset_left为滑块移动距离取整
track为移动轨迹,从第二步开始,是上一步的相对移动距离(x, y, t)
passtime为总移动时间
userresponse为set_left / (0.8876 * 340 / 300)
-
到这里我们已经把w的明文分析出来了,又到了show code环节了
四. 结语
我们还剩set_left、track、passtime三部分,也就是滑块的缺口距离识别,滑动轨迹的构造,滑动耗时,这个我们放到下一篇文章再分析,当然也包括函数a(e, t)中的重头戏:c.guid()、_.encrypt()、i.encrypt()、c.arrayToHex()四个函数
芥子