赞
踩
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
想做快手的数据采集需要解决一个 did
,这个 did
会由请求页面直接返回,但是却不能直接使用,需要后续发包激活。而只要滑过验证码,携带返回的 captchaToken
去请求数据,不仅能够拿到正确结果,还能激活 did 进行后续使用。所以,做ks数据采集,过验证码还是很有必要的,且验证码逆向难度不是很大,也可以当做练手项目来学习。
拿到一个视频链接,打开后清缓存,刷新就会出现滑块。抓包随便滑一下,可以看到验证接口为 captcha/sliding/kSecretApiVerify
此接口只有一个参数 verifyParam
,那么本次目标就是分析此参数是如何生成的。
通过搜索 verifyParam
可以快速定位到加密点。
可以看到加密点在一个平坦流中,那么我们的断点可以打在 switch 语句上,一步步进行调试。
首先进入分支 0,分支 0 中执行了一个语句 i[Jt("0x34")](Tt["a"], c);
其中 Tt["a"]
是一个函数,c
则是一个对象,里面有许多明文数据。我们先分析 c
的内容
{
"captchaSn": "Cgp6dC5jYXB0Y2hhEtgCyWw9CbLiBYsQuiKZDriENd0wgRuOcpjp18c0mLiC-KGoQc4cg8PMrLW7UnOEVg1xHeCTP6m6R2PreatN3qSRwN3OF2u50S0b4WdZjTHjP6zRH6fJSEuif21DnFRelxFCKqEosJLc1bCykcCrp2dEAm6oMofzkY8hSHhlNHmObaey7pz9bf6OELLjWYTwsEXo4SwX7oRxYqAEldaioef8sf9RIRcvEaBOy8I_Jr2Cug91PkogroYtHG5SQBk_kbjH4w8vSnJ06Lj3k0zTjbPYmVwk5hsbpzpARp7o7vjWf_ef1t9eisjPreKz0x2LZVMogVcFXwIDcnRr4tKyTZgu7SkHjKHEioot-J6F2mnWkR7eo7bqGVen4sYA7nCWL_B0EWrGxVbPMQ_RDwKql4113uC6mCEr-STsJcuXlbTYFO1rgTc327bsqvhADEKW48a1p7QEGOZ-OCcaEleIHYY8IfeWArTHzyF818xZgigFMAI",
"bgDisWidth": 316,
"bgDisHeight": 184,
"cutDisWidth": 56,
"cutDisHeight": 56,
"relativeX": 165,
"relativeY": 58,
"trajectory": "3|24|0,7|24|14,10|24|15,14|24|17,18|24|22,21|24|23,25|24|26,28|24|31,32|24|32,225|112|35,39|24|39,43|24|41,47|24|44,50|24|51,54|24|52,54|23|55,57|23|55,61|23|62,341|111|64,68|23|69,72|23|78,76|23|80,79|23|82,83|23|88,86|23|90,90|23|92,94|23|98,469|111|100,101|23|103,105|23|111,108|23|112,112|23|113,115|23|123,119|23|127,123|23|134,126|23|177,601|111|178,134|23|180,137|23|185,141|23|187,144|23|188,144|24|192,148|24|192,152|24|194,155|24|195,717|112|197,163|24|198,166|24|200,170|24|202,173|24|203,177|24|204,181|24|207,188|24|208,192|24|210,861|112|212,199|24|213,202|24|215,206|24|216,210|24|217,213|24|218,217|25|221,221|25|222,224|25|224,993|113|224,231|25|226,235|25|228,239|25|230,242|25|231,246|25|234,250|25|234,253|25|235,257|25|237,1121|113|239,264|25|239,268|25|242,271|25|242,275|25|244,278|25|246,282|25|248,286|25|248,289|25|250,1253|113|250,297|25|252,300|25|254,304|26|254,307|26|256,311|26|258,318|26|259,322|26|261,326|26|262,1397|114|263,333|26|264,340|26|264,347|26|267,351|26|267,355|26|269,362|26|270,369|26|272,373|26|272,1585|114|274,384|26|274,387|26|276,391|26|277,398|26|278,402|26|279,409|26|280,413|26|281,420|26|282,1773|114|282,431|26|284,434|26|285,442|26|286,445|26|287,452|26|287,456|26|288,460|26|290,467|26|291,1965|114|291,474|26|294,485|26|295,489|26|296,496|26|298,500|26|298,503|26|299,507|26|300,510|26|301,2153|114|303,521|26|304,525|26|304,528|26|305,532|25|306,539|25|309,543|25|310,550|25|313,554|25|315,2309|113|318,561|25|320,565|25|323",
"gpuInfo": "{\"glRenderer\":\"WebKit WebGL\",\"glVendor\":\"WebKit\",\"unmaskRenderer\":\"ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)\",\"unmaskVendor\":\"Google Inc. (NVIDIA)\"}",
"captchaExtraParam": "{\"ua\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36\"xxx"
}
c
的值比较重要,我们逐个讲解。
captchaSn
captchaSn
的值是由接口 captcha/sliding/config
返回的,该接口包含了验证码图片信息。
而请求该接口也需要提交一个参数 captchaSession
。不用担心,此参数也是在该请求之前的接口中返回的。不过此参数包含一个小坑,很有迷惑性,我们下文再讲。
bgDisWidth|bgDisHeight|cutDisWidth|cutDisHeight
这个值分别为验证码背景图片长宽以及缺口的长宽,为固定值。
relativeX|relativeY
relativeX 为滑动距离,relativeY 为 captcha/sliding/config
接口中 disY
的值乘以 0.46 得来。
trajectory
这个很明显就是滑动轨迹了,由 x轴|y轴|时间 组成。滑动轨迹的生成也是比较棘手的,下文再讲。
gpuInfo|captchaExtraParam
这两个为设备指纹,可以直接固定。
我们接着分析加密流程。
分支 0 中会进入到 return e.Ueadz(n, t)
中,我们接着往下跟,会进入另一个函数中。i[Jt("0x34")](Tt["a"], c);
实际为 Tt["a"](c);
继续往下跟,会进到一个新的平坦流中。
这部分主要是对 c
的值进行序列化、转 Uint8Array。
继续往下跟会进到一个 Promise
中,也是本次逆向最关键的地方。
new Promise((function(t, c) {
Jose[r("0x2a")](n[r("0x28")], [e, l, {
suc: t,
err: c
}])
}
))
这段代码的作用是创建一个 Promise 对象,并通过调用 Jose
对象的方法,将传递给 Promise 构造函数的成功和失败处理函数 t
和 c
作为参数传递给该方法。
其中 n[r("0x28")]
和 l
的值为固定的,e
则是上面对 c
处理后的值。
我们继续往下走的话会进到另一个文件中。到这里后,跟值就变得比较麻烦了。我们观察代码结构可以发现这是一个经过 webpack
打包后的代码,那么我们就可以使用最简单的方法,全局导出来解决这个问题。
我们知道最终加密参数的值由 Jose[r("0x2a")]
生成,所以我们需要拿到 Jose
。通过搜索关键字我们可以找到
观察代码结构,这是一个自执行函数。结构如下
!function(r, o) {
"object" == ("undefined" === typeof exports ? "undefined" : n(exports)) && "object" == n(t) ? t.exports = o() : "function" == typeof define && e("3c35") ? define([], o) : "object" == ("undefined" === typeof exports ? "undefined" : n(exports)) ? exports.Jose = o() : r.Jose = o()
}(window, (function() {xxx})
exports.Jose = o()
,Jose
的值为参数 o
,而 o
为一个方法。通过执行这段代码,就可以得到 Jose
。这里我们将代码修改一下,为:
window = {}
!function(r, o) {
window.Jose = o()
}(window, (function() {xxx})
这样我们执行完后就可以直接通过 window.Jose
来调用加密方法。当我们执行时会报一个错误 。ReferenceError: n is not defined
我们缺了一个 n
方法,将它补上即可。
最后调整一下调用代码:
async function encrypt(e) {
var l = "c7b645db-65e8-401f-b38c-4c07c5fff247";
var n = {};
console.log(e)
return n.uNXrD = "$encrypt",
new Promise((function (t, c) {
window.Jose.call(n.uNXrD, [e, l, {
suc: t,
err: c
}])
}
))
}
到这里,最关键的部分就基本结束了,这段异步代码会返回一个长度与 e
相近的数组。
然后就是新平坦流剩余的部分。
这部分是对生成的数组进行处理,将数组转为 Uint8Array
,然后对每个元素进行按位与操作。
var sent_ = new Uint8Array(sent_length)
for (var c = 0; c < sent_length; c += 1)
sent_[c] = 255 & sent[c];
最后将新数组进行 base64 编码,到这里整个加密流程就结束了。
上文中我们提到了获取 captchaSession
时有一个小坑,就是这个参数值的获取有两种情况。
一般 captchaSession
可以从评论数据等接口中拿到,/graphql
。
但是这个接口会有两种响应信息,如下:
{‘errors’: [{‘message’: ‘Need captcha’…
{‘data’: {‘result’: 400002,
两种响应内容中都包含 captchaSession
,但是只有第一种的 captchaSession
是有效的。如果使用的是 400002 的 captchaSession
,即使过了滑块,拿到的 captchaToken
也是无法使用的。
轨迹应该是ks最难处理的问题了,代码模拟随机生成的轨迹基本无法使用,重放轨迹验证,同一坐标相同的轨迹也无法使用,也就是一个轨迹只能用一次。
350014 基本上就是轨迹问题。
轨迹生成我尝试过多种方法:
随机生成:普通生成相似轨迹,基本无法使用,通过率10%以下。
缩放法:取一个真实轨迹,再根据当前缺口位置进行缩放,通过率30%左右。
缓动函数:通过率90%左右
自建轨迹库:手动录入真实轨迹,再对真实轨迹进行随机偏移,通过率80%左右,也是我在使用的方法。
关于轨迹可用率很低这种情况,我也怀疑是自己遗漏了某些细节导致。
成功:
{'result': 1, 'desc': 'ok', 'unifiedType': 2, 'captchaToken': 'HEADCgp6dC5jYXB0Y2hhEskC0WTsg4gi79naGkKCVH985uiGGP-nkHVb8r3mqAFKXFntcP42UDrKFfe2QlEOWjDJAUXDPU4MO960Yi1joyLWHT14972aSw_lbfYnr3NbY7HmYPLzwnsMg95Q5NASCXHSaTpzyyNt_MJLmIbkzg_8Wm3dvia82kNiUjbacF8gXthNo1XzmTlxcSDbT-2vsEmtC1sIP9PKx6OL105PVHCwELO8-Ax2RP3kfHfH3uBXg3P0CmFlXiLuSRpM0xB41RXuuznO6bw6Dox4LZkPGvijL5YtPZOBCgAlykW8J7Y1fh-JNt79WKIN4A16oS89sLr8jt6tIdnmFNncnmjGKya1MkCtEQ7qHcP_uyrwaV3k5Is9Cra3gnslM1huvA2x8GD521Sn-dXNsjRkb2dG6cPbre2lk0N8JURgItzl2bjjKKqCimS0-VjszscaElTx5qdOA_0N2qfb3EUSeB2MrygFMAITAIL'}
轨迹问题:
{'result': 350014, 'desc': 'anti check err, try to get a new captchaSN to verify', 'unifiedType': 2}
加密参数生成错误:
{'result': 350005, 'desc': 'captchaSN err', 'unifiedType': 2}
缺口识别问题:
{'result': 350002, 'desc': 'verify err.try again', 'unifiedType': 2}
常见响应信息就是以上几种。
基本流程如下:
1、请求内容页获取 did
2、graphql 通过该接口获取验证码相关信息,可以从评论中获取。
3、请求验证接口,获取
captchaToken
4、携带
captchaToken
,请求一次内容接口
完成这几步后 did
就处于激活状态了,可以重复使用一段时间。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。