极验4滑块验证码逆向与纯算实现
在当今自动化测试和数据采集的攻防战场上,验证码早已不再是简单的“看图识字”游戏。随着极验(Geetest)第四代滑块验证码在金融、社交、电商等高安全场景中的广泛部署,其背后复杂的加密链路和动态混淆机制,正成为爬虫工程师面前的一道技术高墙。
尤其是v4版本引入了混合加密体系——AES 与 RSA 联合加持,配合前端高度混淆的 JS 代码和 PoW 挑战机制,使得传统 Selenium 或 Puppeteer 等浏览器驱动方案不仅效率低下,还极易被检测封禁。真正的突破点,不在于模拟点击,而在于协议层的精准还原:能否脱离浏览器环境,在本地完成所有关键参数的计算?
本文将带你深入极验4滑块验证码的核心逻辑,从接口行为分析入手,逐步拆解w参数的生成机制,最终通过 Python 实现完整的“纯算法”构造流程。无需加载任何 JavaScript 引擎,仅靠原生密码学库即可生成合法请求,真正实现轻量级、高性能的协议级突破。
整个验证流程由两个核心接口串联而成:
- load 接口:初始化会话,获取背景图、滑块图、token、加密公钥等必要信息。
- verify 接口:提交用户滑动结果及加密参数,完成校验。
抓取load接口返回如下:
{ "status": "success", "data": { "lot_number": "80ff732c046e471286a596376f23e6ba", "captcha_type": "slide", "slice": "pictures/v4_pic/slide_2024_09_02/e9d1dec400/slide/da4cb40154354b98b0aabc59a516407b.png", "bg": "pictures/v4_pic/slide_2024_09_02/e9d1dec400/bg/da4cb40154354b98b0aabc59a516407b.png", "ypos": 88, "arrow": "arrow_1", "js": "/js/gcaptcha4.js", "css": "/css/gcaptcha4.css", "static_path": "/v4/static/v1.8.9-2c5e49", "gct_path": "/v4/gct/gct4.5a2e755576738ba0499d714db4f1c9e0.js", "pow_detail": { "version": "1", "bits": 0, "datetime": "2025-06-14T14:46:04.908506+08:00", "hashfunc": "md5" }, "payload": "AgFD8gWUUuHFx-XvpP7J2eXmFGG988RJA9XWIifIhVfD_3urF6TwX10q6TrWNlmoLrmnDZXhh-Ds0tvYuF6iz5alWQDqnpl0rqqdGpTnlPBV0BX8BWJ5g0YFxJt1IOoQVcRgUKRcBVWhr05ylkkJZXmzCjWL1dDhSlKM126f2CZbUhZx485twyTTIgg6TxkHp6RFiEexFFTfAtnhmMzgKU23kd_SmUIpkhKKF4bwPjaDya1ARBt-LMTEJSFl0XZKUXBONNi2GkE0BIbNVEDSLi4QK7Zj55XdDCTUuBkmYr5YxPXUERlbVg3UZyx0Y9EXEHMgaUL7CySS6wrF3_lx4x_5wMrC7FQY9RMZ_mIGUpjk3HIc5OQIQlOZEVDlvEIm6dwESxLJZha5UI92v7w-w9ceoaQGo1oBJ1uqRmPxQsHpmDS77OCNf8r2ymn4nt-mtL4ee2U0yyMX1py5BM1gNWR8kAEVvFWcwwWObMFjMtWSuP4yT193BgbmfCwK_N1dnpCcMCVppkdkfgUQ_Eay_oiTfpR17q7G1gGG52U-NLWM5wgGVTNzmw83Js2pPfl9zQB3By9VIzmwvNqJzsQUg3H_2WyII9YZ5BPAxqe6j3W3-uL1UCyrx4UoYY-Pp5JugjrxQ26vBWE7B5cWc3poPHwTa5nXmyiS3QbZYc_qw3K_YyF96yTL2AVq2TzR6DOiqM1cb6HloK2rVD8mRquqbXXA7rvdKTlYswSxJXSDD6K-bg8CMQ_br4AnxmfC1U_blfAwWSZnqSyoHb9g8X3ejeeW1qxkEYIAcG6bmKhb8gMWTSV1I3WtU7sBLqc5o4O4Qm4q3Xl3gN8bPsh4RSQXJ5M8QudVdBWid0iR5afT2-aL0ZAGOb7a1ooFhrpFndEkT7cHFPG-mMp-HHjY8giZwK8minfPqGKxdOfsQbUqnP8=", "process_token": "233085ee7f960fae79d07dcd40515833e0367c2e4edc1ba4bc5b530dc5e7600b", "payload_protocol": 1 } }从中可提取出多个关键字段:
| 字段 | 含义 |
|---|---|
lot_number | 本次会话唯一标识符 |
bg/slice | 背景图与滑块切片路径 |
ypos | 缺口 Y 坐标(固定值) |
pow_detail | PoW 挑战信息,用于客户端耗时证明 |
payload | 加密负载,包含 RSA 公钥信息 |
紧接着观察verify接口提交的数据包:
{ "lot_number": "80ff732c046e471286a596376f23e6ba", "pass_token": "xxx", "gen_time": "1750000000", "captcha_output": "yyy", "w": "encrypted_data_here" }其中最神秘也最关键的便是w参数——它并非简单签名,而是由明文数据经多重加密后拼接而成的复合密文。想要破解这一关卡,必须定位其生成源头。
通过浏览器调试器搜索"w":并回溯调用栈,很快就能锁定一段高度混淆的 JS 代码。变量名如_ᖆᕶᖙᖁ、_ᕴᖁᕹᖄ显然是经过 AST 混淆处理的结果。尽管阅读困难,但函数结构依然清晰:
function _ᖆᕶᖙᖁ(_ᖘᖄᖁᕿ, _ᖁᖆᕸᕸ) { var _ᖃᕾᕶᖂ = _ᖘᖈᖙᖃ.$_CL , _ᖁ婫ᕹᖄ = ["$_DCAAk"].concat(_ᖃᕾᕶᖂ) , _ᕹᖃᕶᕿ = _ᖁ婫ᕹᖄ[1]; // ... if (_ᖂᖗᕿᖁ[_ᖃᕾᕶᖂ(123)](_ᖆᖘᕶᕹ[_ᖃᕾᕶᖂ(604)])) { var o = _ᖃᕾᕶᖂ(873) === _ᖆᖘᕶᕹ[_ᖃᕾᕶᖂ(604)] , a = _ᖆᖘᕶᕹ[_ᖃᕾᕶᖂ(604)] , _ = _ᖄᖈᕸᖃ[a][_ᕹᖃᕶᕿ(938)][_ᖃᕾᕶᖂ(911)](_ᖀ符合条件); // RSA加密key while (o && (!_ || 256 !== _[_ᖃᕾᕶᖂ(64)])) _ᖀ符合条件 = (0, _<tool_call>ᕸᖉ</tool_call>[_ᖃᕾᕶᖂ(93)])(), _ = (new RSAEncryptor)[_ᖃᕾᕶᖂ(911)](_ᖀ符合条件); var u = _ᖄᖈᕸᖃ[a][_ᖃᕾᕶᖂ(934)][_ᖃᕾᕶᖂ(911)](_ᖘᖄᖁᕿ, _ᖀ符合条件); // AES加密主数据 return (0, _<tool_call>ᕸᖉ<tool_call>[_ᖃᕾᕶᖂ(5)])(u) + _ } }这段代码揭示了一个重要的设计模式:混合加密体制。具体来说:
w的构成是:AES加密数据(hex)+RSA加密key(hex)- 明文
_ᖘᖄᖁᕿ是一个 JSON 字符串,包含滑动距离、时间戳等信息 - AES 使用 CBC 模式,IV 固定为
0000000000000000 - 密钥
key是一个随机生成的 16 字节字符串(即 32 位 hex) - RSA 公钥来自
payload解析后的 ASN.1 结构
进一步追踪key的生成方式,发现其依赖一个看似简单的函数:
function e() { return (65536 * (1 + Math.random()) | 0).toString(16).substring(1); } // 调用四次拼接成 16 位字符串 let key = e() + e() + e() + e(); // 如: b9acf19ce1a635a9该方法每次生成 4 位十六进制数,共执行 4 次,组合成一个 16 字节的密钥,符合 AES-128 要求。虽然这种生成方式存在熵不足的风险,但在当前上下文中已被广泛接受。
接下来我们来看看参与 AES 加密的原始 JSON 数据结构:
{ "pow_sign": "a689bc86ae4eee78c2cf11a946b1b741", "ep": "123", "passtime": 796, "biht": "1426265548", "pow_msg": "1|0|md5|2025-06-14T19:18:12.048835+08:00|4f70e8a42240f8f809bea8382e738e53|df5ed6e3f65e49709e9dcbd4d595f199||a6ae0baf4ca6c7d1", "lot_number": "df5ed6e3f65e49709e9dcbd4d595f199", "em": { "wd": 1, "sc": 0, "ek": "11", "nt": 0, "ph": 0, "cp": 0, "si": 0 }, "geetest": "captcha", "setLeft": 146, "5ed6e3f6": "e9dcbd", "userresponse": 147.1369191209607, "device_id": "", "lang": "zh", "w22T": "72PZ" }逐项解析:
pow_msg格式为<version>|<difficulty>|<hashfunc>|<datetime>|<captcha_id>|<lot_number>|<extra1>|<extra2>,其中<datetime>来自load接口响应。pow_sign是对pow_msg执行 MD5 哈希的结果。setLeft是缺口 X 坐标,需通过图像识别获得。userresponse在setLeft基础上加上一个小浮点偏移,模拟人为误差:js userresponse = setLeft + parseFloat((Math.random() * 1.2).toFixed(14));- 动态键值对如
"5ed6e3f6": "e9dcbd",规则为: - 取
lot_number的第2~9位作为 key - 取后续6位作为 value
⚠️ 注意:该命名策略可能随版本更新而变化,建议动态提取全局函数进行映射。
那么,如何准确识别滑块缺口的位置?这是整个流程中不可跳过的一环。
我们可以借助 OpenCV 实现自动化检测:
import cv2 import numpy as np def get_slider_offset(bg_img: bytes, slider_img: bytes) -> int: """ 使用OpenCV识别滑块缺口X坐标 :param bg_img: 背景图二进制 :param slider_img: 滑块图二进制 :return: 缺口X坐标 """ # 转灰度图 bg_gray = cv2.imdecode(np.frombuffer(bg_img, np.uint8), cv2.IMREAD_GRAYSCALE) slide_gray = cv2.imdecode(np.frombuffer(slider_img, np.uint8), cv2.IMREAD_GRAYSCALE) # 图像预处理:边缘检测 bg_edge = cv2.Canny(bg_gray, 100, 200) slide_edge = cv2.Canny(slide_gray, 100, 200) # 模板匹配 result = cv2.matchTemplate(bg_edge, slide_edge, cv2.TM_CCOEFF_NORMED) _, _, _, top_left = cv2.minMaxLoc(result) return top_left[0]💡 提示技巧:
- 可加入cv2.GaussianBlur提升抗噪能力
- 若匹配精度不够,尝试 Sobel 或 Laplacian 算子增强轮廓
- 多次识别取平均值可减少误判
现在进入最核心的部分:密码学还原。
首先处理 RSA 加密部分。我们需要从payload中提取公钥模数(modulus)和指数(exponent)。Base64 解码后可见其为标准 ASN.1 编码的 DER 格式公钥。简化处理如下:
MODULUS_HEX = ( "00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74" "C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F" "09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B5970" "6592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81" ) EXPONENT_HEX = "10001" # 即 65537Python 实现 RSA 加密(使用 PKCS#1 v1.5 填充):
import binascii from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 def rsa_encrypt_key(aes_key: str, modulus_hex: str, exponent_hex: str) -> str: n = int(modulus_hex, 16) e = int(exponent_hex, 16) key = RSA.construct((n, e)) cipher = PKCS1_v1_5.new(key) plaintext = bytes.fromhex(aes_key) ciphertext = cipher.encrypt(plaintext) return binascii.hexlify(ciphertext).decode() # 示例 aes_key = "b9acf19ce1a635a9" encrypted_rsa_part = rsa_encrypt_key(aes_key, MODULUS_HEX, EXPONENT_HEX)注意:极验并未采用更安全的 OAEP 填充,因此此处选择兼容性更强的PKCS1_v1_5。
接着处理 AES-CBC 加密部分:
from Crypto.Cipher import AES from Crypto.Util.Padding import pad import json import binascii def aes_cbc_encrypt_json(data_dict: dict, key_hex: str, iv_hex: str = "0"*32) -> str: key = bytes.fromhex(key_hex) iv = bytes.fromhex(iv_hex) plaintext = json.dumps(data_dict, separators=(',', ':'), ensure_ascii=False) cipher = AES.new(key, AES.MODE_CBC, iv) padded_data = pad(plaintext.encode('utf-8'), AES.block_size) encrypted = cipher.encrypt(padded_data) return binascii.hexlify(encrypted).decode()关键细节包括:
- 必须使用separators=(',', ':')去除空格以保证序列化一致性
- UTF-8 编码前需填充至块大小(16 字节)
- IV 固定为全零字符串
最终,将两段密文拼接即得完整w参数:
w = encrypted_aes_part + encrypted_rsa_part至此,我们已完全掌握极验4滑块验证码的关键参数生成逻辑。整套流程不再依赖任何浏览器环境或 JS 引擎,全部基于 Python 原生库实现,具备高并发、低延迟、易集成等优势。
这种协议级突破能力的应用远不止于爬虫领域。例如在自动化测试平台中,它可以快速批量生成验证请求;在反欺诈系统中,可用于构建对抗样本集;结合轨迹模拟算法,甚至能训练 AI 模型生成更自然的人类行为特征。
未来挑战仍在继续:极验已在部分节点启用 WebAssembly 模块进行加密运算,对抗手段日益复杂。但只要我们坚持从底层协议出发,理解每一段数据的真实含义,就总能找到那条通往本质的路径。
技术的本质不是对抗,而是理解。唯有深入肌理,方能游刃有余。
👉欢迎关注我的技术专栏:
📚《AI语音合成实战》 —— 探索 B 站 IndexTTS 2.0、零样本克隆、情感解耦等前沿技术
📚《爬虫JS逆向实战》 —— 深入验证码、加密流量、AST混淆等核心技术