2980邮箱多种类验证码逆向
字数 1666 2025-08-18 17:33:13
2980邮箱多种类验证码逆向分析教学文档
一、目标分析
目标网站:2980邮箱登录页(已脱敏处理)
验证码类型:滑块、点选、旋转、拼图乱序、钟表等多种验证码
主要分析对象:滑块、点选、旋转验证码的逆向过程
二、反调试处理
网站包含两处debugger反调试:
- 首页加载时
- 验证码加载时
解决方案
方法1:HOOK Function.prototype.constructor
(()=>{
Function.prototype.__constructor = Function.prototype.constructor;
Function.prototype.constructor = function(){
if(arguments && typeof arguments[0]==='string'){
if("debugger"===arguments[0]){
return
}
return Function.prototype.__constructor.apply(this,arguments)
}
}
})()
方法2:文件替换(推荐)
使用浏览器替换功能将debugger相关代码替换为空函数。
三、验证码接口分析
1. 图片请求接口
- 返回数据:包含加密的mes字段
- 验证码类型标识:
- 21: 滑块
- 23: 点选
- 16: 旋转
2. 请求参数
- token
- appid
- k值(需要逆向)
3. 关键接口
getBehaviorRegister接口返回token和appid
四、关键值逆向分析
1. sign值生成
- 加密方式:SHA256
- 定位方法:通过启动器查找或XHR断点
- 明文构造:相对简单,具体逻辑略
2. k值生成
- 加密方式:AES-ECB
- key生成:
(_0x37cb01 + _0x134810).substring(8, 24)_0x37cb01:JS文件中固定值_0x134810:时间戳(与图片接口的t参数对应)
- 明文:指纹信息加密生成(
fingerPrinterList[0x2]和fingerPrinterSon)
3. mes值解密
- 解密方式:AES
- key:与k值加密的key保持一致
- 解密结果示例:
{
"dif":"0",
"answer":["9","未","雨"],
"bg":"https://csmoss.duoyi.com/19/124bf7b8f82292",
"num":3,
"sn":"9a363bc74a8a",
"type":23,
"list":[]
}
4. 滑块图片还原
def split_and_reorder_image(image_path, reorder_array, split_ratios=(10, 1)):
"""
还原乱序的滑块图片
:param image_path: 乱序的图片路径
:param reorder_array: 滑块图片接口返回的mes解密后的list
:param split_ratios: 分割比例
"""
from PIL import Image
original_image = Image.open(image_path)
width, height = original_image.size
# 分割图片
split_width = width // split_ratios[0]
split_height = height // split_ratios[1]
images = [(i * split_width, j * split_height,
(i + 1) * split_width, (j + 1) * split_height)
for i in range(split_ratios[0])
for j in range(split_ratios[1])]
images = [original_image.crop(box) for box in images]
# 重新排序
reordered_images = ["" for _ in range(len(images))]
for i, new_index in enumerate(reorder_array):
reordered_images[new_index] = images[i]
# 拼接还原
new_width = split_width * split_ratios[0]
new_height = split_height * split_ratios[1]
new_image = Image.new('RGB', (new_width, new_height))
for i, img in enumerate(reordered_images):
x = (i % split_ratios[0]) * split_width
y = (i // split_ratios[0]) * split_height
new_image.paste(img, (x, y))
new_image.save(image_path)
五、验证请求分析
1. 请求参数
- token、appid:从接口获取
- portion、cn、timestamp、signature:需要逆向
- sn:图片接口解密后返回
- 请求负载:不同验证码类型有差异
2. 参数生成逻辑
portion = Math.random().toFixed(2)cn = md5('num' + parseInt(10000000 + Math.random() * 1000000) + 'time' + new Date().getTime())timestamp = new Date().getTime()signature = md5(md5(portion + ':' + timestamp + ':' + token) + sn + ':1:' + cn + ':auth' + ":5b350044ac092f7bf3c2bc791638ca2f")
3. 请求负载生成
- 加密方式:AES
- 明文:环境信息+验证码相关信息(JSON格式)
- key:固定值 + signature,截取8到24位
六、不同类型验证码参数差异
1. 点选验证码
- 关键参数:
slide:点选轨迹click_behavior:点选坐标+时间+顺序portion:计算后的点选坐标(与图片宽高的比例)
portion计算代码:
# center_points 点选坐标
portion = []
for i in range(len(center_points)):
portion.append([
str(format(center_points[i][0]/320, '.5f')),
str(round((center_points[i][1]+0.83332824707031)/200, 5))
])
2. 滑块验证码
- 关键参数:
slide:滑动轨迹portion:计算后的滑块距离
滑动距离计算函数:
function get_huadongtiao(x, startposition) {
var _0x3c1ee0 = 72;
var _0x3d74b9 = 1.11;
var _0x4b4e13 = 0.74;
var _0x1fda62 = startposition; // 图片接口返回的startPosition参数
var _0x101145 = 1;
var _0x3d3596;
for (var i = 0; i < 999; i++) {
_0x101145++;
_0x3d3596 = Math.pow(_0x3c1ee0 * _0x101145, 0.4 * Math.abs(Math.sin(0.025 * _0x101145))) +
Math.pow(_0x101145, _0x3d74b9) * _0x4b4e13 + _0x1fda62;
if (Math.abs(_0x3d3596 - x) <= 1) {
break;
}
}
var portion = (Math.pow(_0x3c1ee0 * _0x101145, 0.4 * Math.abs(Math.sin(0.025 * _0x101145))) +
Math.pow(_0x101145, _0x3d74b9) * _0x4b4e13) / 320;
return {
"portion": portion,
"value": _0x101145
};
}
3. 旋转验证码
- 关键参数:
portion:识别角度slide:旋转滑动轨迹(根据滑动条滑动距离计算)
滑动距离计算:
滑动距离 = int(portion * 0.6)
轨迹生成函数:
import random
def __ease_out_expo(sep):
"""缓动函数 easeOutExpo"""
if sep == 1:
return 1
else:
return 1 - pow(2, -10 * sep)
def get_slide_track(distance):
"""
根据滑动距离生成滑动轨迹
:param distance: 需要滑动的距离
:return: 滑动轨迹[[x,y,t], ...]
"""
if not isinstance(distance, int) or distance < 0:
raise ValueError(f"distance类型必须是大于等于0的整数: distance: {distance}")
# 初始化轨迹列表
slide_track = [[0, 0, -(distance * 20)]]
# 共记录count次滑块位置信息
count = 50 + int(distance / 2)
# 初始化滑动时间
t = slide_track[0][2]
# 记录上一次滑动的距离
_x = 0
_y = slide_track[0][1]
for i in range(count):
_y -= 1 if i % 9 == 0 else 0
# 已滑动的横向距离
x = slide_track[0][0] + round(__ease_out_expo(i / count) * distance)
# 滑动过程消耗的时间
t -= random.randint(10, 20)
if x == _x:
continue
slide_track.append([x, _y, t])
_x = x
slide_track.append(slide_track[-1])
return slide_track
七、验证结果
- 成功响应:
{"message":{"pass":"cd5201bdb47748fe8b9114769d7122cb"},"code":1} - 失败响应:
{"message":"not match","code":0}
八、总结
- 该验证码系统采用了多种反调试手段,需要先处理debugger
- 关键加密使用SHA256和AES(ECB模式)
- 不同验证码类型的区别主要在于请求负载的构造
- 滑块验证码需要额外的图片还原处理
- 验证请求需要构造复杂的签名参数
通过分析加密逻辑和参数构造方式,可以完整实现该验证码系统的自动化识别。