2980邮箱多种类验证码逆向
字数 1666 2025-08-18 17:33:13

2980邮箱多种类验证码逆向分析教学文档

一、目标分析

目标网站:2980邮箱登录页(已脱敏处理)
验证码类型:滑块、点选、旋转、拼图乱序、钟表等多种验证码
主要分析对象:滑块、点选、旋转验证码的逆向过程

二、反调试处理

网站包含两处debugger反调试:

  1. 首页加载时
  2. 验证码加载时

解决方案

方法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}

八、总结

  1. 该验证码系统采用了多种反调试手段,需要先处理debugger
  2. 关键加密使用SHA256和AES(ECB模式)
  3. 不同验证码类型的区别主要在于请求负载的构造
  4. 滑块验证码需要额外的图片还原处理
  5. 验证请求需要构造复杂的签名参数

通过分析加密逻辑和参数构造方式,可以完整实现该验证码系统的自动化识别。

2980邮箱多种类验证码逆向分析教学文档 一、目标分析 目标网站 :2980邮箱登录页(已脱敏处理) 验证码类型 :滑块、点选、旋转、拼图乱序、钟表等多种验证码 主要分析对象 :滑块、点选、旋转验证码的逆向过程 二、反调试处理 网站包含两处debugger反调试: 首页加载时 验证码加载时 解决方案 方法1:HOOK Function.prototype.constructor 方法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保持一致 解密结果示例 : 4. 滑块图片还原 五、验证请求分析 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计算代码 : 2. 滑块验证码 关键参数 : slide :滑动轨迹 portion :计算后的滑块距离 滑动距离计算函数 : 3. 旋转验证码 关键参数 : portion :识别角度 slide :旋转滑动轨迹(根据滑动条滑动距离计算) 滑动距离计算 : 轨迹生成函数 : 七、验证结果 成功响应 : {"message":{"pass":"cd5201bdb47748fe8b9114769d7122cb"},"code":1} 失败响应 : {"message":"not match","code":0} 八、总结 该验证码系统采用了多种反调试手段,需要先处理debugger 关键加密使用SHA256和AES(ECB模式) 不同验证码类型的区别主要在于请求负载的构造 滑块验证码需要额外的图片还原处理 验证请求需要构造复杂的签名参数 通过分析加密逻辑和参数构造方式,可以完整实现该验证码系统的自动化识别。