freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

【验证码逆向专栏】某盾 v2 滑动验证码逆向分析
K哥爬虫 2025-03-03 17:56:32 7861
所属地 湖北省

7Gn9G6.png

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

前言

之前分析了某盾 Blackbox 的指纹算法, 这次再来做做它的验证码,该指纹算法在验证码参数里面也会有用到,

详细查看往期文章 :

【JS逆向百例】某盾 Blackbox 算法逆向分析:https://mp.weixin.qq.com/s/ueWVmlpLOljOLb1a7vEBag

逆向目标

目标:某盾 v2 滑动验证码

网站:aHR0cHM6Ly9sb2dpbi5kb3NzZW4uY29tL3Nzby9jaGVja0xvZ2lu

7WIyK7.png

抓包分析

抓包分析,发现 图片接口 和 验证接口 是同一个接口只是请求参数不同:

7WIH1I.png

需要分析的参数有 P1 ~ P9看着多,我们慢慢来。

验证结果:

  • 失败:

    • needValidateCode:true

    • 继续返回图片接口信息

7WIK6V.png

  • 成功:

    • needValidateCode:false

    • 返回 validateToken

7WIgNL.png

逆向分析

我们先来看图片接口的 P1 ~ P9生成,通过堆栈即可定位目标参数生成位置:

7WIhvJ.png

点击进入:

7WI8IG.png

非常明显,我们依次对 P1 ~ P9进行分析,其中 QQoooQ.blackBox就是我们前言提到的 某盾 Blackbox 的指纹算法,也可暂时写死:

  • p1 = oQO0Q0

    7WIx0B.png

    • oOoQQ0为固定值:

    "b37uCyfyme4S7TF/MVDRqSRxP4CB2BjsnDxr4bSxz0vSL/~hXNGID9Tr7vzaBm~F"
    • window._fmOpt.token,搜索定位,由 window._fmOpt.partner、时间戳和随机数拼接而成:

    7WI5xt.png

    • window._fmOpt.partnerwindow._fmOpt.appName:不同网站的标识;

    • oO0QQo.mfaIdundefined不用管。

  • p2 = OoOQ0O:

    7WIA4b.png

    • 调试分析可知,由 QQoooQ.blackBox + ^^1^^1^^1生成。

  • p3 = oQOoO0(ooQQ0Q, QQQOQO)

    7WIW9e.png

    • ooQQ0Q,调试分析可知:

      var ooQQ0Q = QOOO0O(p1 + '^^' + p2) + '^^|^^|^^|' +QOOO0O(oOOoQO)

      其中oOOoQO,由固定值 161155拼接时间戳构成:

      "161155^^|^^|^^1739762660066"

      还差 QOOO0O函数,我们进到该函数中去:

      7WIcKP.png

      常数特征很明显,我们也可以问下 deepseek

      7WIvPw.png

      验证之后为标准的 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);

      window._fmOpt.tokenp1分析过;

    QQ00QO('stq67pv9'):生成固定值 rsp67ou9

    • oQOoO0最后的加密函数,我们同样先跟进去观察,然后单步走,就定位到如下 return的位置,发现关键字 AESiv= Moa14C2uXpe8AUJ5

      跟我们分析某盾 Blackbox 的 DES3加密,大差不差,需要自己处理一下:

    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().replace('+', '~'))


    print(encrypt_aes_cbc('d2f551c596fe634b5d7956250a1d5274^^|^^|^^|e0315c2431445fccb801d5a1349aa50f', 'rsp67ou9626-390c'))
  • p4 = oQOoO0(QOo0Oo, QQQOQO)

    7WIONO.png

    • oQOoO0:就是上面的 AES加密,iv不变;

    • QOo0Oo:图片接口直接 "|^^|^^|"写死即可;

    • QQQOQO:同上,key一致。

  • p5 = QoOO00

    • QQ00QO('xfc'):"web",写死。

  • p6 = Qoo0Q0

    var QOQQO0 = o0QoQQ(8);
    var Qoo0Q0 = oQOoO0(QOQQO0 + window.location.href, QQQOQO)
    • o0QoQQ函数进去调式发现,就是取几位随机数:

    7WISaQ.png

    • oQOoO0:上面的 AES加密, iv不变;

    • window.location.href:不同网站不同,写死;

    • QQQOQO:同上,key一致。

  • p7 = QOQ0QQ + o0QoQQ(32)

    • o0QoQQ(32):取 32 位随机数;

    • QOQ0QQ = QOOO0O(Qoo0Q0) + QOOO0O(oOOoQO)

      QOOO0O:标准 MD5加密;

      Qoo0Q0p6值;

      oOOoQO:上面分析过了。

  • p8 = QOQQO0

    • QOQQO0 = o0QoQQ(8):取 8 位随机数,与 p6生成中的要一致。

  • p9 = oOOoQO

    • oOOoQO = oQOoO0(oOOoQO, QQQOQO)

      oQOoO0:上面的 AES加密,iv不变;

      oOOoQO:上面分析过了;

      QQQOQO:同上,key一致。

组包后,请求图片接口数据,发现大图是乱序的,需要还原:

7WIUIf.png

通过加载的事件断点,即可定位到图片还原的代码:

7WIP0c.png

整体逻辑就是按上下 2层平均分割成 16张小图,然后通过图片接口返回的 bgImageSplitSequence参数,计算新的顺序,再进行排序拼接,转换为 python代码如下:

from io import BytesIO
from PIL import Image

def reconstruct_image(segment_sequence, image_binary):
 """
重新构建图像,将输入图像拆分为8x2的网格并按照指定的顺序重新排列。
 
:param segment_sequence: bgImageSplitSequence 参数,16进制字符串列表,表示重新排列的顺序
:param image_binary: 二进制图像数据
:return: 重新排序后的图像二进制数据
"""
 # 加载图像
 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 = [{}, {}]
 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 = [{}, {}]
 
 # 重新排序
 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()

以及滑块识别代码:

import cv2
import numpy as np

def bytes_to_cv2(img):
 """
将二进制数据转换为 OpenCV 图像。

参数:
img (bytes): 读取的二进制图片数据。

返回:
numpy.ndarray: OpenCV 格式的 BGR 图像。
"""
 # 将二进制数据转换为 NumPy 数组
 img_buffer_np = np.frombuffer(img, dtype=np.uint8)
 # 解码为 OpenCV 图像格式
 img_np = cv2.imdecode(img_buffer_np, cv2.IMREAD_COLOR)
 return img_np


def get_distance(bg, tp, save_path=None):
 """
计算滑块验证码缺口的位置,并在背景图上标记。

参数:
bg (bytes): 背景图片的二进制数据。
tp (bytes): 滑块图片的二进制数据。
save_path (str, 可选): 若提供路径,则保存标记后的图片。

返回:
dict: 缺口位置的坐标 {'x': x 坐标, 'y': y 坐标},若未找到则返回 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)

 # 使用模板匹配算法 (TM_CCORR_NORMED) 计算滑块与背景的最佳匹配位置
 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)  # 绿色矩形框,线宽 2

     # 如果提供了保存路径,则保存标记后的图片
     if save_path:
         cv2.imwrite(save_path, bg_img)

     # 返回缺口的 x, y 坐标
     return {'x': max_loc[0] + x, 'y': max_loc[1] + y}
 else:
     return None  # 未找到匹配的缺口

最后再来看验证接口的 P1 ~ P9生成,我们只讲不同的地方:

  • p2,由 QQoooQ.blackBox+ '^^3^^1^^1'生成;

  • p3,加密的明文多了:

    • validateCodeObj:图片接口返回的 validateCodeObj对象;

    • userAnswer

      userAnswer = Math.round(QoO0Oo / Oo0OOo) + QQ00QO('|10|') + new Date().getTime()

      Math.round(QoO0Oo / Oo0OOo):滑块识别的距离;

      QQ00QO('|10|'):固定值 "|10|"

  • p4:加密的明文多了个 mouseInfo轨迹信息,经过测试写死即可。

Python算法的源码,会分享到知识星球当中,需要的小伙伴自取,仅供学习交流。

结果验证

7WIR53.png

# 验证码破解 # 逆向分析 # Python爬虫 # javascript技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 K哥爬虫 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
K哥爬虫 LV.7
欢迎关注微信公众号:K哥爬虫,分享 JS/APP/小程序逆向、数据加密解密、高级爬虫进阶知识!
  • 84 文章数
  • 55 关注者
【JS逆向百例】某盾 Blackbox 算法逆向分析
2025-01-13
【APP 逆向百例】某瓣 app 逆向分析
2025-01-06
【JS逆向百例】某江 Hospital 逆向分析
2024-12-30
文章目录