原创文章,请勿转载!
本文内容仅限于安全研究,不公开具体源码。维护网络安全,人人有责。
本文关联文章超链接:
本篇文章篇幅较长,但是讲的特别细,小伙伴们耐心看完哦 ~
https://www.geetest.com/adaptive-captcha-demo
与极验三代滑块验证码相比,极验四代简化了验证过程,加密参数w的生成也变简单了。和极验三代验证码一样的分析流程,撸就完了。
请求介绍
极验第四代验证码测试主页,主要是获取下个请求中的url(这个url是动态变化的,所以这个步骤必须要)
请求参数
没啥参数
请求响应
响应为html文档,通过正则匹配下个步骤的请求url
href="(.\*?adaptive-captcha-demo\.js)"
请求介绍
获取w参数加密需要的参数captchaId
请求参数
实际破解滑块过程中,此请求可忽略。
没啥参数
请求响应
响应为js文件,通过正则匹配captchaId参数的值
captchaId:"([0-9a-z]+)"
请求介绍
获取验证码信息,包括:验证码类型、验证码背景图、验证码滑块图、lot_number参数、静态文件url等
请求参数
captcha_id: 24f56dc13c40dc4a02fd0318567caef5 // 上个请求中获取
challenge: f8beca82-84a4-4b32-a01d-dae1697f1236 // 由js代码生成,下面会详细讲解生成过程
client_type: web // 固定值
risk_type: slide // 验证码类型
lang: zh // 固定值
callback: geetest_1641878914316 // 当前时间戳
请求响应
响应中的c、s在后续无感验证生成w参数时需要使用,其中c为定值,s为变化值。
{
"status": "success",
"data": {
"lot_number": "c574cd8c30a541b28597fb4582542c61",
"captcha_type": "slide",
"js": "/js/gcaptcha4.js",
"css": "/css/gcaptcha4.css",
"static_path": "/v4/static/v1.4.4",
"slice": "pictures/v4_pic/slide_2021_07_14/Group81/slide/019d7acaf9aa4f488a332b6baff7176b.png",
"bg": "pictures/v4_pic/slide_2021_07_14/Group81/bg/019d7acaf9aa4f488a332b6baff7176b.png",
"ypos": 116,
"gct_path": "/v4/gct/gct4.5258a91d0f5f0bb73c65d4d18d48d93f.js",
"arrow": "arrow_1",
"show_voice": false,
"feedback": "https://www.geetest.com/Helper",
"logo": true,
"pt": "1",
"captcha_mode": "risk_manage",
"language": "zh",
"custom_theme": {
"_style": "stereoscopic",
"_color": "hsla(224,98%,66%,1)",
"_gradient": "linear-gradient(180deg, hsla(224,98%,71%,1) 0%, hsla(224,98%,66%,1) 100%)",
"_hover": "linear-gradient(180deg, hsla(224,98%,66%,1) 0%, hsla(224,98%,71%,1) 100%)",
"_brightness": "system",
"_radius": "4px"
}
}
}
请求介绍
该请求是极验验证请求,gcaptcha4.js收集滑动轨迹,与上个请求中的lot_number参数,加密生成w参数。
请求参数
captcha_id: 24f56dc13c40dc4a02fd0318567caef5 // 与上个请求中的captcha_id参数相同
challenge: e29f82f7-78db-42de-913f-fb1b01d3e30b // 与上个请求中的challenge参数相同
client_type: web // 固定值
lot_number: c574cd8c30a541b28597fb4582542c61 // 上个请求的响应中lot_number参数值
risk_type: slide // 验证码类型
pt: 1 // 上个请求的响应中pt参数值
w: c742e66584e3b20ad523c2ddff... // gcaptcha4.js收集滑动轨迹加密生成
callback: geetest_1641878916958 // 当前时间戳
请求响应
验证成功,拿到seccode。
{
"status": "success",
"data": {
"lot_number": "5b79a07bfb1640c1955ef28fbe28bef0",
"result": "success",
"fail_count": 0,
"seccode": {
"lot_number": "5b79a07bfb1640c1955ef28fbe28bef0",
"pass_token": "f5b3b3d7664e032bc06730d56f83433046af98878bfa796d5e4b5b5f48904e40",
"gen_time": "1641880037",
"captcha_output": "2W2T6RrNJ8qVlCuIQxrHVp0imaZt_LrywRPCvYEbTHwQyoZwHIYvpYM5zF0-qSl8LQF_m8ggUDGiA0b8IDdrjji1YjjbEERRWAP9SxWj-G090QRaou4m8NnZL0NVmBie"
},
"score": "1"
}
}
通过AST语法树将混淆的gcaptcha4.js文件还原,具体还原方式请看 极验第四代滑块验证码破解(一):AST还原混淆JS
在反混淆后的gcaptcha4.js文件中搜索 “w”,即可找到今天破解的主角,并加入一行debugger。
配置chrome插件reres,将网页加载的js文件替换成对应的反混淆后的js文件。
打开开发者工具,刷新网页,触发滑块操作。然后,就可以任意打断点分析啦。
经过测试,下面有两种方式生成challenge
import uuid
challenge = uuid.uuid1().__str__()
第二步:刷新网页,进入断点分析,找到构造challenge的函数入口
第三步:进入uuid函数,这个函数特别简单,直接改成py
import random
def uuid():
"""
var uuid = function () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0;
var v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
"""
def __random(c):
r = int(random.random() * 16)
v = r if c == 'x' else (r & 0x3 | 0x8)
return hex(v)[2:]
string = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
ret = ''
for i in string:
if i in 'xy':
i = __random(i)
ret += i
return ret
滑动滑块,触发debugger,从w生成位置开始往上一步一步分析,看看生成w参数需要哪些关键函数。
通多上面对w参数的分析,可以得出:w参数=加密函数(序列化函数(e对象)
下面是e对象中各参数的详解
{
"setLeft": 99, // 滑块滑动距离
"track": [[38, 15, 0], ... ], // 滑块滑动轨迹
"passtime": 146, // 滑块滑动过程时长
"userresponse": 98.41476022585691, // 通过滑块滑动距离计算得到
"lot_number": "4b4ef3e583444e0fb...", // load响应中的lot_number参数
"device_id": "A59C", // 可为定值,可省略此参数
"geetest": "captcha", // 可为定值,可省略此参数
"lang": "zh", // 可为定值,可省略此参数
"ep": "123", // 可为定值,可省略此参数
"nz8c": "255401529", // 可为定值,可省略此参数
"em": {"ph": 0, ...} // 可为定值,可省略此参数
}
断点进入d[“default”][“stringify”]函数,发现此函数中有外部函数调用,所以将外部函数整个抠出来
断点进入h[“default”]函数,这里需要注意:此函数中对第二个入参中的值进行了很多判断,实际没有进行运算,这里我们修改下函数,直接绕过判断
e对象作为入参,因此需要通过调用栈,跳转到上层函数调用,分析e对象中每个参数的构造函数
小伙伴们看这个哦: 极验第四代滑块验证码破解(三):滑块轨迹构造
滑动消耗时长就直接从滑动轨迹中累加了
function get_passtime(track) {
var passtime = 0;
for (i = 0; i < track.length; i++) {
passtime += track[i][2]
}
return passtime
}
滑动距离也从滑动轨迹中累加吧
function get_setLeft(track) {
var setLeft = 0;
for (i = 1; i < track.length; i++) {
setLeft += track[i][0]
}
return setLeft
}
从上文知道 userresponse = setLeft / t["KaTeX parse error: Can't use function '\]' in math mode at position 7: _BGBb"\̲]̲,那就分析一下t\["_BGBb"]函数
function get_userresponse(setLeft, captcha_width) {
var e = 340
var i = .8876 * e / captcha_width
return setLeft / i
}
感谢小伙伴们看了这么久,直接上干货吧。w参数破解的全量代码奉上,其中只有3个函数需要大家抠出来(原封不动的抠出来就行,太长了没法粘贴进去)
var window = {
"navigator": {
"appName": "Netscape"
},
"crypto": {
"getRandomValues": getRandomValues
}
}
var navigator = window["navigator"]
function randoms(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
function getRandomValues(buf) {
var min = 0,
max = 255;
if (buf.length > 65536) {
var e = new Error();
e.code = 22;
e.message = 'Failed to execute \'getRandomValues\' : The ' + 'ArrayBufferView\'s byte length (' + buf.length + ') exceeds the ' + 'number of bytes of entropy available via this API (65536).';
e.name = 'QuotaExceededError';
throw e;
}
if (buf instanceof Uint16Array) {
max = 65535;
} else if (buf instanceof Uint32Array) {
max = 4294967295;
}
for (var element in buf) {
buf[element] = randoms(min, max);
}
return buf;
}
function get_passtime(track) {
var passtime = 0;
for (i = 0; i < track.length; i++) {
passtime += track[i][2]
}
return passtime
}
function get_userresponse(setLeft, captcha_width) {
var e = 340
var i = .8876 * e / captcha_width
return setLeft / i
}
function get_setLeft(track) {
var setLeft = 0;
for (i = 1; i < track.length; i++) {
setLeft += track[i][0]
}
return setLeft
}
function get_w(track, captcha_width, lot_number) {
var d = function () {
// 内容需要自己抠出来
}();
d["default"] = d;
function get_h(e, t) {
var _ = function () {
// 内容需要自己抠出来
}();
_["default"] = _;
var guid = function () {
function e() {
return (65536 * (1 + Math["random"]()) | 0)["toString"](16)["substring"](1);
}
return function () {
return e() + e() + e() + e();
}
;
}();
var i = function () {
// 内容需要自己抠出来
}();
i["default"] = i;
function arrayToHex(e) {
for (var t = [], s = 0, a = 0; a < 2 * e["length"]; a += 2)
t[a >>> 3] |= parseInt(e[s], 10) << 24 - a % 8 * 4,
s++;
for (var o = [], n = 0; n < e["length"]; n++) {
var r = t[n >>> 2] >>> 24 - n % 4 * 8 & 255;
o["push"]((r >>> 4)["toString"](16)),
o["push"]((15 & r)["toString"](16));
}
return o["join"]("");
}
var a = guid(),
o = new _["default"]()["encrypt"](a);
var n = i["default"]["encrypt"](e, a);
return arrayToHex(n) + o;
}
var passtime = get_passtime(track),
setLeft = get_setLeft(track),
userresponse = get_userresponse(setLeft, captcha_width);
var e = {
"setLeft": setLeft,
"track": track,
"passtime": passtime,
"userresponse": userresponse,
"device_id": "A59C",
"lot_number": lot_number,
"geetest": "captcha",
"lang": "zh",
"ep": "123",
"nz8c": "255401529",
"em": {
"ph": 0,
"cp": 0,
"ek": "11",
"wd": 1,
"nt": 0,
"si": 0,
"sc": 0
}
}
return get_h(d["default"]["stringify"](e))
}
console.log('====================== 测试区 ======================')
var track = [[47, 17, 0],],
captcha_width = 300,
lot_number = "13e35cf1c2834cb9afd01b89006c8f41";
var w = get_w(track, captcha_width, lot_number)
console.log(w)
极验第四代滑块验证码破解系列文章就到此结束啦,后期更新更多爬虫知识与破解技术。点赞加关注,免得找不到路。。。