v5滑块验证逆向

查看 24|回复 2
作者:zzyzy   
本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!
目标:实现滑块验证,python纯算。
网站:
[color=]aHR0cHM6Ly93d3cudmVyaWZ5NS5jb20vZGVtbw==
1.开始今天的滑块分析,直接进官网中demo在线体验,找到滑块验证,先是分析完整流程,再逐步分析,验证成功后,可以发现和以往的协议不同,本网站是一个ws协议传输,发送了六条数据消息,接受三条数据消息,可以先了解一下ws协议,该网站没有参数上的加密只有消息内容做的一些加密处理。


image.png (122.13 KB, 下载次数: 0)
下载附件
2025-6-15 11:02 上传

WebSocket (ws) 协议是一种在单个TCP连接上进行全双工通信的协议。在JavaScript中,我们可以使用WebSocket API来创建WebSocket连接,实现客户端与服务器之间的实时通信。
下面是一个简单的WebSocket客户端案例,包括以下步骤:
  1.创建一个WebSocket对象,指定要连接的服务器地址(以"ws://"或"wss://"开头)。
  2.监听WebSocket事件,如onopen, onmessage, onerror, onclose。
  3.在连接建立后,通过send方法发送数据。
  4.在接收到消息时,处理消息数据(通常为字符串或Blob对象)。
你可以使用Node.js的ws库、Python的websockets库等创建服务器,或者使用在线的WebSocket测试服务器(如echo.websocket.org)进行测试。
2.那就从上到下逐步分析吧,看看是什么加密,首先找onopen,因为请求调用堆栈很少,可以直接跳转进去都打下断点,然后刷新页面验证滑块。


image.png (21.57 KB, 下载次数: 0)
下载附件
2025-6-15 11:18 上传

3.在最后一个断点可以看到,Ja是uqMKPtXJO9FELEwk,new一个WebSocket对象,后面会定义onopen, onmessage等,正好就在try catch下面, he是在上面定义的 搜索 he = 可以找到,进入到onopen里面。


image.png (57.22 KB, 下载次数: 0)
下载附件
2025-6-15 11:20 上传



image.png (28.77 KB, 下载次数: 0)
下载附件
2025-6-15 11:36 上传

4.在里面的调用a,在a里面调用g,进入到g中,往里面跟,上面是一些定义,主要看c[P][Ga]的调用,传入三个参数,打个断点进去。


image.png (12.83 KB, 下载次数: 0)
下载附件
2025-6-15 11:56 上传



image.png (14.8 KB, 下载次数: 0)
下载附件
2025-6-15 11:57 上传



image.png (28.01 KB, 下载次数: 0)
下载附件
2025-6-15 12:00 上传

5.一边分析一边往下跟,走到我断点的位置,E(b, a);的得到加密结果就是我们想要的发送请求的数据,然后push到数组c中,拼接成字符串k,逐步分析c中的三个数据。


image.png (71.49 KB, 下载次数: 0)
下载附件
2025-6-15 12:04 上传



image.png (86.56 KB, 下载次数: 0)
下载附件
2025-6-15 12:11 上传

6.在c定义为空数组的地方断点一下,可以看到,第一个数据是传参进来的,就是Ob,上面有定义,可以写死,第二个搜索一下就出来了,在页面返回里面对应的token值,主要是看第三个参数。


image.png (46.02 KB, 下载次数: 0)
下载附件
2025-6-15 12:16 上传



image.png (26 KB, 下载次数: 0)
下载附件
2025-6-15 12:17 上传



image.png (21.6 KB, 下载次数: 0)
下载附件
2025-6-15 12:19 上传

7其中a = Y(d, h); 是获得加密的key,在第一次发送消息时d h时一样的,进入到函数Y中,分析一下,O是charCodeAt  T 是length,可以看到根据token的最后一个字符的uniode编码值对2求余,实际上就是对字符串,单双数索引的提取,直接转python代码。


image.png (17.75 KB, 下载次数: 0)
下载附件
2025-6-15 12:21 上传



image.png (15.66 KB, 下载次数: 0)
下载附件
2025-6-15 12:29 上传

8.接下来分析需要加密的明文,需要注意的有两个参数一个是requestId,就是req加上时间戳,一个是data中l的值是对应页面返回的token,其他都是浏览器指纹信息了,可以写死。


image.png (27.43 KB, 下载次数: 0)
下载附件
2025-6-15 12:33 上传

9.进入E函数中可以明显看到是一个aes加密,其中iv是函数q生成的,进入到q中,可以看到是对0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz随机取的值,注意的是返回结果是 iv加上加密结果,base64编码一下。


image.png (28.65 KB, 下载次数: 0)
下载附件
2025-6-15 12:36 上传



image.png (19.91 KB, 下载次数: 0)
下载附件
2025-6-15 12:38 上传

10.分析完k的由来后,继续往下走,可以看到根据k的长度,每1024为一段,在函数f,消息拼接一下标识参数,分成三段消息发送,转换成python代码。


image.png (63.45 KB, 下载次数: 0)
下载附件
2025-6-15 12:43 上传



image.png (41.65 KB, 下载次数: 0)
下载附件
2025-6-15 12:50 上传

11.使用python发送消息之后会得到一段加密的字符串,ws协议返回值的处理是在onmessage中,在刚才的open下面就有message 的定义。


image.png (42.03 KB, 下载次数: 0)
下载附件
2025-6-15 13:04 上传



image.png (35.56 KB, 下载次数: 0)
下载附件
2025-6-15 12:56 上传

12.在ge里面断点后,单步调试两下,进来分析解密流程,可以看到是对返回的结果,Y函数是获得加解密需要的key,可以看到返回的加密结果前32位结合token生成key,剩下位解密的密文。


image.png (40.33 KB, 下载次数: 0)
下载附件
2025-6-15 13:13 上传

13.进入到函数ja中解密,其中偏移量是剩下密文中取前32位,还是aes解密,注意一下密文base64解码。


image.png (34.84 KB, 下载次数: 0)
下载附件
2025-6-15 13:25 上传

14.以下是加解密的python代码。
[Python] 纯文本查看 复制代码def aes_ctr_encrypt(plaintext: str, key: str, iv: str) -> str:
    """
    AES CTR模式加密,等效于CryptoJS的AES.encrypt(..., mode=CTR, padding=NoPadding)
    并将IV与密文拼接后Base64编码输出。
    参数:
    - plaintext: 明文,bytes类型
    - key: 密钥,bytes类型
    - iv: 初始化向量,bytes类型(CryptoJS中iv长度通常为16字节)
    返回:
    - Base64编码的字符串,内容为 iv + ciphertext
    """
    plaintext = plaintext.encode('utf-8')
    key = key.encode('utf-8')
    iv = iv.encode('utf-8')
    iv_int = int.from_bytes(iv, byteorder='big')
    ctr = Counter.new(128, initial_value=iv_int)
    # 创建AES-CTR加密器
    cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
    # 加密明文
    ciphertext = cipher.encrypt(plaintext)
    encrypted = iv + ciphertext
    return base64.b64encode(encrypted).decode('utf-8')
def aes_ctr_decrypt(ciphertext: str, key: str) -> str:
    """
    对应JavaScript的AES CTR解密函数,等效于CryptoJS实现。
    参数:
    - ciphertext: Base64字符串,包含加密数据
    - key: 密钥(Utf8字符串)
    返回:
    - 解密后的明文字符串
    """
    # 1. Base64解码输入密文
    decoded = base64.b64decode(ciphertext).hex()
    iv = bytes.fromhex(decoded[:32])  # 前32位hex字符作为IV
    cipher_text = base64.b64encode(bytes.fromhex(decoded[32:])).decode('utf-8')
    # 2. 初始化CTR模式
    iv_int = int.from_bytes(iv, byteorder='big')
    ctr = Counter.new(128, initial_value=iv_int)
    # 3. 创建AES-CTR解密器
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CTR, counter=ctr)
    # 4. 解密并尝试解码为UTF-8字符串
    decrypted = cipher.decrypt(base64.b64decode(cipher_text))
    try:
        return decrypted.decode('utf-8')
    except UnicodeDecodeError:
        raise ValueError("Token is wrong or expired.")
15.第一次返回值会的到一个u,这是接下来用到的,结合token 生成加解密的key,其实也就是返回值的前32位,接下来就是第二次的消息发送了,和第一次消息的区别就是明文和key了,参数基本相同只需要改requestid 和data中l对应token值,还有就是消息头标识是1|1|开头的。


image.png (133.12 KB, 下载次数: 0)
下载附件
2025-6-15 13:29 上传



image.png (67.23 KB, 下载次数: 0)
下载附件
2025-6-15 13:37 上传

16.收到第二次消息后还是结合u生成的key解密流程和之前一样,这样就可以得到需要验证图片的url,返回结果还有和m值,和u一样,m是下面加密轨迹发送第三次请求用到的,可能是因为demo吧,u、m都是一样的。


image.png (43.3 KB, 下载次数: 0)
下载附件
2025-6-15 13:44 上传

17.滑块的识别还是使用的ddddocr,代码如下。
[Python] 纯文本查看 复制代码def get_distance(bg, tp, save_path=None):
    det = DdddOcr(det=False, ocr=False, show_ad=False)
    res = det.slide_match(tp, bg, simple_target=True)
    if save_path is not None:
        # 将背景图片的二进制数据加载为Pillow Image对象
        left, top, right, bottom = res['target'][0], res['target'][1], res['target'][2], res['target'][3]
        bg_image = Image.open(io.BytesIO(bg))
        draw = ImageDraw.Draw(bg_image)
        draw.rectangle([left, top, right, bottom], outline="red", width=2)
        bg_image.save(save_path)
        print(f"已保存标注后的图片到: {save_path}")
        return res
    return res
18.剩下就是第三次的消息了,是对轨迹的加密,需要的参数,requestId command 和data中s 和f,f是token,requestId时间戳组合,主要看轨迹s,前两个是时间戳,后面三个为一组,滑动时间,滑动距离x,y偏移量等,我这直接给出我使用轨迹生成的算法了。


image.png (55.58 KB, 下载次数: 0)
下载附件
2025-6-15 14:22 上传

19.python轨迹代码。
[Python] 纯文本查看 复制代码def generate_slider_track(target_distance):
    track = []
    start_time = int(time.time() * 1000)
    current_distance = 0
    current_time = 0
    y_offset = 0
    track.extend([str(start_time)])
    while current_distance  10:
            x_step = random.randint(4, 10)
        else:
            x_step = random.randint(1, 4)
        current_distance += x_step
        if current_distance > target_distance:
            current_distance = target_distance
        # 纵向浮动在-8到8之间,模拟抖动
        y_offset = random.randint(-8, 8)
        track.extend([str(current_time), str(current_distance), str(y_offset)])
    end_date = start_time + current_time + random.randint(100, 500)
    track.extend([str(end_date)])
    track_str = ",".join(track)
    return track_str
20.好啦,基本所有流程都分析完了,结果是true,如果轨迹或者requestid的时间戳是过去的,会是false,完结撒花,这个demo分析完了,感谢大佬们观看。


image.png (214.49 KB, 下载次数: 0)
下载附件
2025-6-15 14:29 上传

下载次数, 下载附件

hellotoday   

前排沙发,楼主牛啊
zzyzy
OP
  


hellotoday 发表于 2025-6-15 14:45
前排沙发,楼主牛啊

嘿嘿,俺也是小白
您需要登录后才可以回帖 登录 | 立即注册

返回顶部