【验证码逆向专栏】安某客滑块逆向
字数 757 2025-08-11 08:35:55
安某客滑块验证码逆向分析教学文档
一、目标分析
目标网站:安某客滑动验证码系统
验证类型:滑块验证码
主要流程:
- 请求首页获取sessionId
- 请求getInfoTp获取图片信息和responseId
- 请求checkInfoTp校验滑动结果
- 涉及加密参数:dInfo和data,以及info的解密
二、加密参数分析
1. dInfo参数生成
位置:getInfoTp请求的Form Data中
加密方式:AES加密
加密内容:包含设备信息的JSON对象
密钥生成:从sessionId中提取奇数位字符作为AES的key和iv
JavaScript实现代码:
function AESEncrypt(_cRV, _2undefinedp) {
_2undefinedp = _2undefinedp.split("").reduce(function(_PUi, _JrX, _JP9) {
return _JP9 % 2 == 0 ? _PUi : _PUi + _JrX;
}, "");
_2undefinedp = CryptoJS.enc.Utf8.parse(_2undefinedp);
_cRV = "string" == typeof _cRV ? _cRV : JSON.stringify(_cRV);
_cRV = CryptoJS.AES.encrypt(_cRV, _2undefinedp, {
iv: _2undefinedp,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encodeURIComponent(_cRV.toString());
}
function getDInfo(sessionId) {
const deviceInfo = {
"sdkv": "3.0.1",
"busurl": "https://目标网站.com/captcha-verify/",
"useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"clienttype": "1"
};
return AESEncrypt(deviceInfo, sessionId);
}
Python实现代码:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
import json
from urllib.parse import quote_plus
class AESAlgorithm:
@staticmethod
def encrypt(aes_key_iv, text):
cipher = AES.new(
key=bytes(aes_key_iv, encoding='utf-8'),
mode=AES.MODE_CBC,
iv=bytes(aes_key_iv, encoding='utf-8')
)
result = base64.b64encode(cipher.encrypt(pad(text.encode('utf-8'), 16))).decode('utf-8')
return quote_plus(result)
def get_aes_key_iv(session_id):
return ''.join([char for idx, char in enumerate(session_id) if idx % 2 != 0])
def get_d_info(session_id):
sdk_info = {
"sdkv": "3.0.1",
"busurl": "https://目标网站.com/captcha-verify/",
"useragent": "Mozilla/5.0...",
"clienttype": 1
}
aes_key_iv = get_aes_key_iv(session_id)
return AESAlgorithm().encrypt(aes_key_iv, json.dumps(sdk_info))
2. info参数解密
位置:getInfoTp接口返回的info字段
解密方式:AES解密
密钥:与dInfo相同,使用sessionId生成的key和iv
JavaScript解密代码:
function AESDecrypt(encryptedData, sessionId) {
const key_iv = sessionId.split("").reduce((acc, char, idx) => {
return idx % 2 === 0 ? acc : acc + char;
}, "");
const key = CryptoJS.enc.Utf8.parse(key_iv);
const iv = key;
const decrypted = CryptoJS.AES.decrypt(
decodeURIComponent(encryptedData),
key,
{ iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
);
return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8));
}
3. data参数生成
位置:checkInfoTp请求的Form Data中
组成:
- x:水平滑动距离
- track:滑动轨迹
- p:固定值
加密方式:AES加密,与dInfo相同
轨迹生成方法
def generate_track(distance):
"""基于样本轨迹生成新轨迹"""
ratio = distance / 126 # 样本距离为126
new_track = []
base_track = [
[29,11,0], [29,11,11], [29,11,26], [33,11,56],
# 更多轨迹点...
[155,20,1717]
]
for point in base_track:
new_x = int(point[0] * ratio)
new_time = int(point[2] * ratio)
new_track.append(f"{new_x},{point[1]},{new_time}")
return "|".join(new_track)
三、关键实现步骤
1. 获取sessionId
import requests
from lxml import etree
def get_session_id():
url = "https://目标网站.com/captcha-verify/"
response = requests.get(url)
html = etree.HTML(response.text)
return html.xpath("//input[@name='sessionId']/@value")[0]
2. 获取滑块图片信息
def get_slide_info(session_id):
url = "https://目标网站.com/api/getInfoTp"
d_info = get_d_info(session_id)
response = requests.post(url, data={"dInfo": d_info})
encrypted_info = response.json()["info"]
# 解密info获取图片URL等信息
aes_key_iv = get_aes_key_iv(session_id)
slide_info = AESAlgorithm().decrypt(aes_key_iv, encrypted_info)
return json.loads(slide_info), response.json()["responseId"]
3. 识别缺口距离
import cv2
import numpy as np
def get_slide_distance(bg_url, slice_url):
# 下载背景图和滑块图
bg_img = download_image(bg_url)
slice_img = download_image(slice_url)
# 缩放图片到原始尺寸(480×270)
bg_img = cv2.resize(bg_img, (480, 270))
slice_img = cv2.resize(slice_img, (480, 270))
# 使用模板匹配识别缺口位置
result = cv2.matchTemplate(bg_img, slice_img, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
# 计算实际滑动距离(考虑渲染缩放比例0.5833)
distance = max_loc[0] * 0.5833
return distance
4. 提交验证
def submit_verify(session_id, response_id, distance):
url = "https://目标网站.com/api/checkInfoTp"
# 生成轨迹
track = generate_track(distance)
# 构造请求数据
request_data = {
"x": distance,
"track": track,
"p": 1
}
# AES加密
aes_key_iv = get_aes_key_iv(session_id)
encrypted_data = AESAlgorithm().encrypt(aes_key_iv, json.dumps(request_data))
# 提交验证
response = requests.post(url, data={
"data": encrypted_data,
"sessionId": session_id,
"responseId": response_id
})
return response.json()
四、注意事项
- 图片缩放处理:原始图片尺寸480×270px,渲染后280×158px,比例约1:0.5833
- 轨迹生成:可以使用缩放法,基于真实轨迹样本调整
- 加密一致性:所有加密使用相同的AES参数(CBC模式,PKCS7填充)
- 密钥生成:从sessionId中提取奇数位字符作为key和iv
- 请求顺序:必须按sessionId→getInfoTp→checkInfoTp顺序调用
五、完整流程示例
# 1. 获取sessionId
session_id = get_session_id()
# 2. 获取滑块信息
slide_info, response_id = get_slide_info(session_id)
bg_url = slide_info["bg"]
slice_url = slide_info["slice"]
# 3. 识别缺口距离
distance = get_slide_distance(bg_url, slice_url)
# 4. 生成轨迹并提交验证
result = submit_verify(session_id, response_id, distance)
if result.get("success"):
print("验证成功")
else:
print("验证失败")