数美滑块的js逆向分析
一、引言
1.1 声明
声明:
本文章仅供学习交流使用,不提供完整代码,严禁用于商业用途和非法用途,否则由此产生的一切后果均与本人无关,请各位自觉遵守相关法律法规。
本文章未经许可禁止转载,禁止任何二次修改(加工)后的传播;若有侵权,联系删除。
交流、合作请留言,24小时内回复!
1.2 简介
测试用例地址:aHR0cHM6Ly93d3cuaXNodW1laS5jb20vbmV3L3Byb2R1Y3QvdHcvY29kZQ==
放一张验证通过的截图,然后开始进入逆向。
1.3 待办
js代码会更新,但是频率不高,因此还未将js解析实现自动化,目前只是手动还原了一版。
二、验证步骤
通过在浏览器中的操作,可以看出 验证码的生成、验证分为两个步骤。
register请求 获取验证码图片信息 ----> 移动缺口图片到正确位置 发起fverify请求 进行结果验证。
2.1 register请求,获取图片地址
========================载荷参数解释========================
(绝大多数参数都可固定)
"channel": "DEFAULT",
"model": "slide",
"organization": "d6tpAY1oV0Kv5jRSgxQr",
"captchaUuid": "20230811181758TpK3YwejeykFnbZcF6", // 日期时间 + 字符串(未研究)
"lang": "zh-cn",
"appId": "default",
"sdkver": "1.1.3",
"callback": "sm_1691749124176", // sm_ + 时间戳
"rversion": "1.0.4",
"data": "{}"
========================响应数据解释========================
"code":1100
"message":"success"
"requestId":"24e34f217f0a496b49623449ddcbba52"
"riskLevel":"PASS",
"score": 0
"detail":{
// 此处返回的信息在验证时需要使用
bg: "/crb/set-000006/v2/106b23d54fc7789a4ab53f1b5f632694_bg.jpg", // 背景图
fg: "/crb/set-000006/v2/106b23d54fc7789a4ab53f1b5f632694_fg.png", // 缺口图
k: "0QeT1t/wBSI=",
l: 8,
rid: "20230811181847df3fc1677a95a41138",
}
2.2 fverify请求,滑块验证
验证成功返回 {riskLevel:“PASS”},验证失败返回 {riskLevel:“REJECT”}。
数美滑块在验证时提交的参数居然多达20个😟
三、过程解析
数美的滑块 相对来说不难,直接查看fverify的启动器,可以从上到下点击,进入js页面。
😆直接点第一个就行了
3.1 参数定位
进入到js源代码页面后,尝试搜索请求参数中的字段,看是否有结果。本次搜索以"hd"为例,共有28个匹配结果。可以一个个查看,确认是否为请求参数中的"hd",由图可知,本页面中的第22个"hd"附近还存在 与其他请求参数相同的字段信息。
在此下断点,滑动滑块,可以成功断住,记录此处生成的值,与fverify验证请求中的参数值对比可知,两者一致,此处正是 需要逆向分析的地方😎。
关键的东西就在此处了,可以慢慢地、一步步的调试,执行到此处可以发现请求参数 分散在两个变量中的,所以接下来需要分析这两个变量的生成过程。
多滑动几次滑块,可以发现,有些参数的值是固定的,在此也可以先跳过分析这些固定的参数。
3.2 动态参数
最终可知,变化的参数为 ml、ee、ra,通过跟踪代码可以发现三个参数的加密方向点如下:
- ml 移动距离x加密
- ee 移动轨迹数组加密
- ra 时间戳差值加密
参数都为,des加密,每个参数对应一个密钥
3.3 图片缩放
请求到的图片尺寸为600✖300,前端渲染的图片尺寸为300✖150,识别缺口距离的时候需要修正。
四、扩展内容
4.1 conf请求,获取js版本信息
数美的js版本过段时间会更新,导致请求参数的名称和值也会改变,如果想要实现自动化,就需要考虑到更全面的问题。
验证参数中有两个参数 rversion 、protocol 是和js版本有关的,因此这里首先分析js的版本如何确定的问题。
通过在浏览器中抓包可以发现,有个conf请求,返回的是js版本的信息。
这个请求具有如下参数,需要还原的是captchaUuid(也可以固定)
"channel": "DEFAULT", // 固定
"appId": "default", // 固定
"sdkver": "1.1.3", // sdk版本,captcha-sdk.min.js中固定的
"captchaUuid": "20230812091027KWJN8kH2nAdPGrGTpQ", // 待分析(其实也可以固定)
"callback": "sm_1691802630571", // sm_ + 时间戳
"rversion": "1.0.4", // captcha-sdk.min.js的大版本号
"model": "slide", // 验证模式,slide代表滑块
"lang": "zh-cn", // 语言(简体中文)
"organization": "d6tpAY1oV0Kv5jRSgxQr" // 企业标识(可以固定)
查看conf的启动器,点击进去第一个cmsp.min.js,搜索 captchaUuid,共有9个结果。
其中有个方法叫做 getCaptchaUuid,在此打上断点(其他可能的地方,也一并打上断点)。
刷新网页、选择滑块,会发现成功的在此断住,在return处打断点,直接运行到return处。
得到一串字符串,抓包对比,可知此处的getCaptchaUuid就是需要的captchaUuid参数生成的地方。
扣出 getCaptchaUuid方法,js做了混淆,结合浏览器进行调试。可以得到简化后的版本:
'generateTimeFormat': function _0x377dfb() {
var _0x43e7ea = _0x59b24f
, _0xf7a45e = new Date()
, _0x2257b4 = function _0x51d189(_0x2bb897) {
var _0x2b86fd = _0x22ab;
return _0xc00995[_0x2b86fd(0x1b6)](+_0x2bb897, 0x2f * -0x22 + -0xdf6 * 0x2 + 0x111a * 0x2) ? _0xc00995[_0x2b86fd(0x65a)]('0', _0x2bb897) : _0x2bb897['toString']();
};
return _0xc00995['ZmJjz'](_0xc00995[_0x43e7ea(0x1c6)](_0xc00995['aqwMQ'](_0xf7a45e[_0x43e7ea(0x612)]()[_0x43e7ea(0x1a0)](), _0xc00995[_0x43e7ea(0x65e)](_0x2257b4, _0xc00995[_0x43e7ea(0x1c6)](_0xf7a45e['getMonth'](), -0xfaa + 0x17ac + 0x1 * -0x801))) + _0xc00995[_0x43e7ea(0x65e)](_0x2257b4, _0xf7a45e[_0x43e7ea(0x39a)]()), _0xc00995['nIfRW'](_0x2257b4, _0xf7a45e[_0x43e7ea(0x6d7)]())), _0x2257b4(_0xf7a45e[_0x43e7ea(0x3a9)]())) + _0xc00995[_0x43e7ea(0x65e)](_0x2257b4, _0xf7a45e[_0x43e7ea(0x39f)]());
},
'getCaptchaUuid': function _0x1b55e2() {
var _0x1aa18f = _0x59b24f
, _0x2468bc = _0xc00995[_0x1aa18f(0x150)][_0x1aa18f(0x2b6)]('|')
, _0x22d1a6 = -0x2237 + -0xa * 0x6f + 0x8b * 0x47;
while (!![]) {
switch (_0x2468bc[_0x22d1a6++]) {
case '0':
var _0x3c1b53 = _0x5b1e90[_0x1aa18f(0x213)];
continue;
case '1':
for (var _0x41ee97 = -0x85b * 0x3 + -0x1d6e + 0x367f; _0xc00995[_0x1aa18f(0x1b6)](_0x41ee97, -0xdce + -0x232 * -0x11 + -0x1772 * 0x1); _0x41ee97++) {
_0x1c9d91 += _0x5b1e90[_0x1aa18f(0x11d)](Math[_0x1aa18f(0x624)](_0xc00995[_0x1aa18f(0x6af)](Math[_0x1aa18f(0x204)](), _0x3c1b53)));
}
continue;
case '2':
var _0x5b1e90 = _0x1aa18f(0x695);
continue;
case '3':
return _0xc00995[_0x1aa18f(0x3c3)](this[_0x1aa18f(0x4c1)](), _0x1c9d91);
case '4':
var _0x1c9d91 = '';
continue;
}
break;
}
}
简化之后,很明显可以看出 captchaUuid 是 当前日期时间 + 18位随机字符串。
generateTimeFormat = function () {
var current_time = new Date()
, _0x2257b4 = function (_0x2bb897) {
return +_0x2bb897 < 10 ? '0' + _0x2bb897 : _0x2bb897['toString']();
};
return current_time['getFullYear']()['toString']()
+ _0x2257b4(current_time['getMonth']() + 1)
+ _0x2257b4(current_time['getDate']())
+ _0x2257b4(current_time['getHours']())
+ _0x2257b4(current_time['getMinutes']())
+ _0x2257b4(current_time['getSeconds']());
}
getCaptchaUuid= function () {
var random_str = '';
var base_str = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
var basestr_length = base_str['length'];
for (var i = 0; i < 18; i++) {
random_str += base_str['charAt'](Math['floor'](Math['random']() * basestr_length));
}
return generateTimeFormat() + random_str;
}
console.log(getCaptchaUuid());
注意:虽然 register 和 fverify 参数中也有 captchaUuid 值,但是后面两个请求的captchaUuid 与 conf 中的 captchaUuid 值是一样的,captchaUuid 值是在 conf请求前生成的,因此 若是在 register 和 fverify 中 定位 captchaUuid 时,才在 getCaptchaUuid 处打断点,是无法断住的。
4.2 文字点选验证
按照指定顺序点击图中文字
4.3 图标点选验证
按照指定顺序点击图中图标
4.4 语序点选验证
按照正常的成语语序 点选文字
4.5 空间推理验证
按照指示文字 完成空间推理