【验证码逆向百例】某手滑块算法分析

E9J639.png

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

前言

之前私信、星球提问中,有不少小伙伴咨询了某手滑块验证码的相关问题,该验证码整体难度不算高,不过有些细节会导致最终验证的成功率。该站过验证码其实就是激活 cookie 中 did 的过程,流程中存在问题,还可能出现过了验证码,但是 did 仍不可用的情况,ip 够纯的话,不会触发验证码风控(个人主页)。非业务项目,本文仅对个人视频主页的滑块验证码进行逆向分析,别处的风控未做研究,仅供学习交流:

E9nhKc.png

E9n5N5.png

逆向目标

  • 目标:某手 web 端滑块验证码
  • 网址:aHR0cHM6Ly93d3cua3VhaXNob3UuY29tL3Byb2ZpbGUvM3hnbWFhdjI5NjhiaDdj

抓包分析

清除缓存,刷新网页,就能触发滑块验证码风控(没触发就反复操作几次):

E9nAam.png

前言中提到,过验证码是激活 cookie 中 did 的过程,did 是个人主页接口响应返回的(不要写成随机数):

E9nOkH.png

/captcha/sliding/config 接口返回背景图、滑块图链接等,其中 disY 会参与到关键加密参数的生成:

E9nVI4.png

disY 以及滑动距离需要进行缩放处理,原图大小为 686x400,网页为 316x312,缩放比例为 0.46 即可:

E9n1Fq.png

/config 接口有个请求参数 captchaSession,是下图接口返回的,不同的 /graphql 接口,请求参数 operationName 不一样,这也是个关键接口,did、session 等会导致响应结果不一样,不过都能提取到 captchaSession 参数:

E9nc0h.png

  • {'data': {'result': 400002, 'jsSdkUrl':环境、轨迹校验较严,后续环境不对,会触发二次验证(350029,文字点选、旋转验证),或一次验证通过,但无法获取到主页数据;
  • {'errors': [{'message': 'Need captcha':环境、轨迹校验较松,一次验证通过,能获取到主页数据。

/captcha/sliding/kSecretApiVerify 为校验接口,请求参数 verifyParam 加密了环境、滑动距离以及轨迹等参数,风控点,响应如下则代表验证通过:

E9nUPU.png

某手的滑块验证码抽象就抽象在,调试的时候会发现,手动滑过了验证码,也渲染不出主页,没过有时候反而出来了,当然,这就不深究了,脚本的实现效果是正常的。

经过测试,kSecretApiVerify 接口还包括但不限于如下的一些响应情况:

  • 轨迹、verifyParam 为空:{“result”:350001,“desc”:“params err. check it”,“unifiedType”:2};
  • 缺口识别错误:{“result”:350002,“desc”:“verify err.try again”,“unifiedType”:2};
  • verifyParam 中的 captchaSn 错误:{‘result’:350005,‘desc’:‘captchaSN err’,‘unifiedType’:2};
  • 验证时间过长,captchaSn 失效:{“result”:350009,“desc”:“captchaSN expired, try to get a new captchaSN to verify”,“unifiedType”:2};
  • 轨迹、环境错误:{“result”:350014,“desc”:“anti check err, try to get a new captchaSN to verify”,“unifiedType”:2};
  • 参数失效:{“result”:350017,“desc”:“verify err. try again”,“unifiedType”:2};
  • 触发文字点选:{‘result’: 350029, ‘captchaSession’: ‘xxx’, ‘type’: ‘4’, ‘configPath’: ‘/rest/zt/captcha/wordClick/config’};
  • 触发旋转验证:{‘result’: 350029, ‘captchaSession’: ‘xxx’, ‘type’: ‘2’, ‘configPath’: ‘/rest/zt/captcha/rotating/config’}。

E9nYaa.png

逆向分析

建议整个指纹浏览器调试,便于对比分析。

从验证接口 kSecretApiVerify 的堆栈中跟进到 index.28acbed8.js 文件处,该文件并非固定链接,域名是动态的,可以考虑固定一套。直接 ctrl f 局部搜索加密参数 verifyParam,可以定位到下图处,下断后滑动缺口即可断住:

EpzxXI.png

由上图可知,这块是个 switch-case 条件控制语句,流程执行到 case 10 时,verifyParam 也就是 a 参数,已经生成了。我们向上回溯,发现,整体的执行流程,是按照 case 0、3、10、end 这个顺序的,verifyParam 从 0 到 3 就生成了,证明加密部分在 Object(Ht["a"])(c) 中异步实现的。

跟栈分析前,我们先来看看 c 是什么,下面逐一分析下:

Ep49Es.png

  • bgDisHeight:定值,背景图片原图高 * 0.46;
  • bgDisWidth:定值,背景图片原图宽 * 0.46;
  • captchaExtraParam:关键参数,浏览器环境、指纹,ua、canvas、时区以及电池电量等信息;
  • captchaSn:/graphql 接口响应返回;
  • cutDisHeight:定值,滑块图片原图高 * 0.46;
  • cutDisWidth:定值,滑块图片原图宽 * 0.46;
  • gpuInfo:浏览器环境,WebGL、显卡等信息;
  • relativeX:滑动距离;
  • relativeY:disY * 0.46;
  • trajectory:转换处理后的轨迹。

再具体的跟下 c,先 ctrl f 局部搜索 trajectory,即可定位到上述参数生成的位置,轨迹 trajectory 也就是 a 参数切掉 , 后得到的:

EpXUPQ.png

a 的具体转换位置就在上面 case 0 处,c 就是轨迹的起始时间戳,可以直接用 python 复现,轨迹算法自写、AI 调优或者寻找资源都行,轨迹算法不正确,会导致以下情况出现(后文环境参数指 canvasGraphFingerPrint 和 canvasGraph,环境黑的时候,需要注意 ip 是否也一起黑了):

  • 虽然 y 轴校验的并不严格,不过若写成固定值,也可能会导致二次验证或 350014,ip 被风控;
  • t 轴算法不正确,会导致环境参数直接黑掉一段时间,从而一直需要二次验证(350029);起始点不对,会导致 350014。

EpX16f.jpg

n = {
	"trajectory": track
}

c = n["trajectory"][0][2]  # 基准时间

trajectory = ",".join(
	f"{point[0]}|{point[1]}|{point[2] - c}"
	for point in n["trajectory"]
)

接下来主要的就是 captchaExtraParam 参数,和 trajectory 同在 case 4 中,其由 JSON["stringify"](i) 生成,i 中包含一堆环境参数:

EpXRNc.png

直接搜索 key35,定位到后会发现,key1 到 key39 都是从 n 对象中取到的值,向上跟,captchaExtraParam 中的参数,全在这一块生成的,是一些环境、指纹参数,包括 userAgent、language、canvas、did 以及鼠标事件等等。有些参数不同浏览器环境,是一致的,不过有的不能写成固定值,比如 key2 复用,同样会导致环境参数直接黑掉一段时间,一直触发二次验证(350029),ip 也有关系,剩下的可以自行研究下,替换测试就知道校验哪些了:

ELGssU.png

最后,重新回到 Object(Ht["a"])(c) 处,分析下 verifyParam 参数是如何加密生成的。从此处,单步向上跟,一会儿就能跟到下图所示的,加密参数生成的位置,也可以直接搜索 regeneratorRuntime[r 快速定位:

ELaTCq.png

这里对 c 对象进行序列化、转 Uint8Array、s[r("0x2d")](x, o) 异步处理后生成了 n 数组,最后经过 base64 编码得到的 verifyParam 参数值,a.a[r("0x34")](c) 处写法可参考:

'&'.join(f'{quote(str(k), safe="")}={quote(str(v), safe="")}' for k, v in c.items())

从 case 0 处单步向上跟,进到 s[r("0x2d")](x, o) 中,会跟到下图所示位置,这里创建了一个 Promise 对象,l 为定值,控制台打印这部分,可以看到,PromiseResult 就是 n 数组:

ENOheY.jpg

Jose.call("$encrypt", [...]) 就是关键方法,选中 Jose[r("0x21")] 跟到 encrypt.ee7d2a41.js 文件中去,搜索 Jose,可以定位到 exports.Jose = o() 处,代码拿到本地,直接 window.Jose = o() 导出调用,缺补多删即可:

async function encrypt(e) {
    var l = "c7b645db-65e8-401f-b38c-4c07c5fff247";
    return new Promise((function (t, c) {
            window.Jose.call("$encrypt", [e, l, {
                suc: t,
                err: c
            }])
        }
    ))
}

function h(e) {
    for (var t = e["length"], c = new Uint8Array(t), a = 0; a < t; a++)
        c[a] = e["charCodeAt"](a);
    return c
}

async function asyncVerifyParam(dataArray) {
    try {
        let encryptedData = await encrypt(h(dataArray));
        let verifyParam = new Buffer.from(encryptedData).toString('base64');
        console.log(verifyParam);
    } catch (error) {
        console.error(error);
    }
}

二次验证

有很多小伙伴都会疑问,为何自己的验证结果一直是 350029,这就是触发二次验证了,二次验证有两种类型的验证码,分别是文字点选(type 4)和旋转(type 2),根据前文的内容,想必大伙也知道了部分会导致二次验证的原因,文末至此总结一下,当然,可能会有遗漏,欢迎评论区补充交流:

  • captchaExtraParam 中的 key2 复用,时间戳未动态生成;
  • 环境参数 canvasGraphFingerPrint 和 canvasGraph 黑了,导致黑的原因前文有讲;
  • ip 黑了。

结果验证

EqEdVm.jpg

EqwRvZ.jpg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值