【验证码逆向专栏】某盾 v2 滑动验证码逆向分析
字数 1939 2025-08-29 08:30:19
某盾v2滑动验证码逆向分析教学文档
1. 验证码概述
某盾v2滑动验证码是一种常见的反爬机制,通过滑块验证来区分人类用户和自动化程序。该验证码系统包含以下主要接口:
- 图片接口:获取验证码图片(背景图和滑块图)
- 验证接口:提交滑块位置进行验证
两个接口实际上是同一个接口,只是请求参数不同。
2. 关键参数分析
验证码涉及9个关键参数(P1-P9),下面详细分析每个参数的生成方式:
2.1 P1参数生成
p1 = oQO0Q0
oOoQQ0为固定值:"b37uCyfyme4S7TF/MVDRqSRxP4CB2BjsnDxr4bSxz0vSL/~hXNGID9Tr7vzaBm~F"window._fmOpt.token:由window._fmOpt.partner、时间戳和随机数拼接而成window._fmOpt.partner和window._fmOpt.appName:不同网站的标识oO0QQo.mfaId:通常为undefined,可忽略
2.2 P2参数生成
p2 = OoOQ0O
- 由
QQoooQ.blackBox+^^1^^1^^1生成(图片接口) - 验证接口时使用
^^3^^1^^1代替^^1^^1^^1
2.3 P3参数生成
p3 = oQOoO0(ooQQ0Q, QQQOQO)
ooQQ0Q=QOOO0O(p1 + p2) + QOOO0O(oOOoQO)oOOoQO:由固定值161155拼接时间戳构成,如"161155^^|^^|^^1739762660066"QOOO0O函数:标准MD5哈希算法
QQQOQO:var OOOQOQ = window._fmOpt.token.split('-'); var QoQQo0 = OOOQOQ[OOOQOQ.length - 2] + OOOQOQ[OOOQOQ.length - 1]; var QQQOQO = QQ00QO('stq67pv9') + QoQQo0.substring(10, 18);QQ00QO('stq67pv9')生成固定值rsp67ou9
oQOoO0:AES加密函数,参数:- IV:
Moa14C2uXpe8AUJ5 - 需要字符替换处理(q↔p,I↔J)
- IV:
Python实现示例:
from Crypto.Cipher import AES
import base64
def swap_characters(input_str):
return input_str.replace('q', 'tem1').replace('p', 'q').replace('tem1', 'p').replace('I', 'tem2').replace('J', 'I').replace('tem2', 'J')
def encrypt_aes_cbc(data, key):
iv = 'Mnz14C2tXod8AUJ5'
block_size = AES.block_size
pad = lambda s: s + (block_size - len(s) % block_size) * chr(block_size - len(s) % block_size)
data = pad(data)
cipher = AES.new(key.encode('latin-1'), AES.MODE_CBC, iv.encode('latin-1'))
encrypted = cipher.encrypt(data.encode('latin-1'))
return swap_characters(base64.b64encode(encrypted).decode('latin-1').swapcase())
2.4 P4参数生成
p4 = oQOoO0(QOo0Oo, QQQOQO)
oQOoO0:同上AES加密QOo0Oo:图片接口直接为"|"(固定值)QQQOQO:同上
2.5 P5参数生成
p5 = QoOO00
QQ00QO('xfc'):固定值"web"
2.6 P6参数生成
p6 = Qoo0Q0
QOQQO0 = o0QoQQ(8):取8位随机数Qoo0Q0 = oQOoO0(QOQQO0 + window.location.href, QQQOQO)oQOoO0:同上AES加密window.location.href:当前页面URL(可写死)QQQOQO:同上
2.7 P7参数生成
p7 = QOQ0QQ + o0QoQQ(32)
o0QoQQ(32):取32位随机数QOQ0QQ = QOOO0O(Qoo0Q0) + QOOO0O(oOOoQO)QOOO0O:标准MD5加密Qoo0Q0:P6值oOOoQO:同上
2.8 P8参数生成
p8 = QOQQO0
QOQQO0 = o0QoQQ(8):取8位随机数(与P6生成中的一致)
2.9 P9参数生成
p9 = oOOoQO
oOOoQO = oQOoO0(oOOoQO, QQQOQO)oQOoO0:同上AES加密oOOoQO:同上QQQOQO:同上
3. 图片还原算法
获取的验证码大图是乱序的,需要根据bgImageSplitSequence参数还原:
- 将图片上下平均分割成2层,每层水平分割成8张小图,共16张小图
- 根据
bgImageSplitSequence参数计算新的顺序 - 重新排序拼接
Python实现:
from io import BytesIO
from PIL import Image
def reconstruct_image(segment_sequence, image_binary):
# 加载图像
img_io = BytesIO(image_binary)
original_img = Image.open(img_io)
# 定义图像尺寸和分割参数
img_width, img_height = 320, 180
segment_width, segment_height = img_width // 8, img_height // 2
# 拆分图像
image_layers = [[None]*8 for _ in range(2)]
for layer in range(2):
y_start = layer * segment_height
for i in range(8):
x_start = i * segment_width
crop_box = (x_start, y_start, x_start + segment_width, y_start + segment_height)
image_layers[layer][i] = original_img.crop(crop_box)
# 创建新图像
new_image = Image.new('RGB', (img_width, img_height))
new_image_layers = [[None]*8 for _ in range(2)]
# 重新排序
for index, hex_value in enumerate(segment_sequence):
position = int(hex_value, 16)
layer, segment = divmod(position, 8)
original_layer = 1 if index >= 8 else 0
new_image_layers[layer][segment] = image_layers[original_layer][index % 8]
# 拼接图像
for layer in range(2):
for i in range(8):
new_image.paste(new_image_layers[layer][i], (segment_width * i, segment_height * layer))
# 转换为二进制数据
img_byte_arr = BytesIO()
new_image.save(img_byte_arr, format='PNG')
return img_byte_arr.getvalue()
4. 滑块识别算法
使用OpenCV进行模板匹配识别滑块位置:
Python实现:
import cv2
import numpy as np
def bytes_to_cv2(img):
# 将二进制数据转换为OpenCV图像
img_buffer_np = np.frombuffer(img, dtype=np.uint8)
img_np = cv2.imdecode(img_buffer_np, cv2.IMREAD_COLOR)
return img_np
def get_distance(bg, tp, save_path=None):
# 将二进制数据转换为OpenCV图像
bg_img = bytes_to_cv2(bg)
tp_img = bytes_to_cv2(tp)
# 转换为灰度图并进行高斯模糊
tp_gray = cv2.GaussianBlur(cv2.cvtColor(tp_img, cv2.COLOR_BGR2GRAY), (5, 5), 0)
bg_gray = cv2.GaussianBlur(cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY), (5, 5), 0)
# 使用Canny边缘检测
lower_threshold = 30
high_threshold = 100
tp_edge = cv2.Canny(tp_gray, lower_threshold, high_threshold)
bg_edge = cv2.Canny(bg_gray, lower_threshold, high_threshold)
# 使用模板匹配算法
result = cv2.matchTemplate(bg_edge, tp_edge, cv2.TM_CCORR_NORMED)
_, _, _, max_loc = cv2.minMaxLoc(result)
# 寻找滑块图像的轮廓
contours, _ = cv2.findContours(tp_edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
# 选择面积最大的轮廓
contour = max(contours, key=cv2.contourArea)
x, y, width, height = cv2.boundingRect(contour)
# 在背景图上绘制矩形标记滑块缺口位置
cv2.rectangle(bg_img, (max_loc[0] + x, max_loc[1] + y),
(max_loc[0] + x + width, max_loc[1] + y + height), (0, 255, 0), 2)
# 保存标记后的图片
if save_path:
cv2.imwrite(save_path, bg_img)
return {'x': max_loc[0] + x, 'y': max_loc[1] + y}
else:
return None
5. 验证接口特殊处理
验证接口的P1-P9参数生成与图片接口大部分相同,主要区别在于:
- P2参数:使用
^^3^^1^^1代替^^1^^1^^1 - P3参数:加密的明文多了:
validateCodeObj:图片接口返回的对象userAnswer:userAnswer = Math.round(QoO0Oo / Oo0OOo) + QQ00QO('|10|') + new Date().getTime()Math.round(QoO0Oq / Oo0OOo):滑块识别的距离QQ00QO('|10|'):固定值"|10|"
- P4参数:加密的明文多了
mouseInfo轨迹信息(可写死)
6. 验证结果处理
验证接口返回结果:
- 失败:
needValidateCode: true,返回图片接口信息 - 成功:
needValidateCode: false,返回validateToken
7. 总结
逆向某盾v2滑动验证码的关键点:
- 理解P1-P9参数的生成逻辑,特别是AES加密部分
- 掌握图片还原算法,正确处理
bgImageSplitSequence - 使用OpenCV准确识别滑块位置
- 验证接口与图片接口的参数差异处理
- 轨迹模拟需要合理生成
以上内容完整覆盖了某盾v2滑动验证码的逆向分析过程,包含了所有关键技术和实现细节。