吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6374|回复: 34
收起左侧

[Web逆向] 顶象滑块验证码纯算逆向分析

  [复制链接]
LiSAimer 发表于 2025-8-13 16:30

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请联系作者删除。

逆向目标

目标网站:
aHR0cHM6Ly93d3cuZGluZ3hpYW5nLWluYy5jb20vYnVzaW5lc3MvY2FwdGNoYQ==
关键要点:图像撕裂还原、轨迹、参数【lid, c, ac】

抓包分析

本篇文章以官网demo 5.1.53 版本进行分析,需要注意官网关键js文件每天会动态更新两次

进入网页,按F12打开调试窗口后刷新页面,建议每次都清理一下缓存或者直接使用无痕模式

刷新页面后定位到demo位置,点击滑动拼图查看发送请求

图片1.png

接口 a 请求载荷

图片2.png

这里只需要注意aid的生成

aid = 'dx-' + str(int(time.time() * 1000)) + '-' + str(random.randint(10000000, 99999999)) + '-3'

接口 a 响应内容

图片3.png

p1:背景图地址
p2:滑块图地址
sid 和 y 后面需要用到

当我们滑动滑块完成验证后会发送一个v1接口的请求

图片4.png

提示:本文截图中某些值可能和之前截图中的不一致,以文字说明为准,图知其意便可

ac:包含轨迹的加密参数
c:c1接口获取需要逆向
sid:a接口响应中获取
aid:这里aid要与a接口的aid一致
x:滑动距离
y:a接口响应中获取

v1接口响应中的token值是我们本次逆向的最终目标

图片5.png

流程梳理

1、第一次请求c1接口获取lid
2、第二次请求c1接口获取data值
3、请求a接口获取图片地址以及一些关键参数
4、请求v1接口完成验证获取token

图像还原

老样子打上canvas断点后点击验证滑块,断到如下图位置

图片6.png

分析入参
n:canvas对象
t:img对象
i:图片高度
e:图片高度
r:32位数组

分析代码
1、将一张图像完整绘制到canvas上
2、然后根据数组r的长度将图像水平分割成多个切片
3、再按照数组r的顺序重新排列这些切片绘制到canvas上

知道逻辑了那么就需要找到关键32位数组是如何生成的
继续往上跟栈找到生成位置

图片7.png

函数sn算法还原,传入a接口p1的值也就是背景图地址

def get_arr_32(self, img_url):
    n = img_url.split("/")[-1].split(".")[0]
    c = []
    for i in range(len(n)):
        u = ord(n[i])
        while u % 32 in c:
            u += 1
        c.append(u % 32)
    return c

图像还原算法

def draw_slices_from_array(self, image_content, output_path, arr):
    src_img = Image.open(BytesIO(image_content))
    src_width, src_height = src_img.size
    canvas_width = 32 * 12
    canvas_height = 200
    dest_img = Image.new("RGB", (canvas_width, canvas_height), "white")
    for index, x in enumerate(arr):
        r = x * 12
        if r + 12 > src_width:
            continue
        cropped = src_img.crop((r, 0, r + 12, 200))
        dest_img.paste(cropped, (index * 12, 0))
    dest_img.save(output_path)

还原结果

图片8.png

图片9.png

C 值分析

对v1接口的c值进行搜索后发现是在c1接口返回的
c1接口请求了两次
第一次是get请求,headers中有个短的加密参数Param
返回一个lid

图片10.png

图片11.png

第二次是post请求,表单中有个长的加密参数Param
返回一个data值也就是我们要的 c 值

图片12.png

图片13.png

第一次c1请求的 Param 分析

打上c1的xhr断点清缓存刷新页面
往上跟栈可定位到其生成位置在一个index.js文件中
注意,它有两个index.js文件,咱分析的这个是带?_t=***的

图片14.png

参数除lid外都是固定的
lid生成代码

l = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
lid = str(int(time.time() * 1000)) + ''.join(random.choice(l) for _ in range(32))

进入加密函数后可以看到是个控制流。

图片15.png

整个index.js文件也是存在大量的混淆,为方便读者阅读我这里用ast还原一下,代码就不公布了,写的比较渣,github上也有开源的。简单还原了一下大数组和一些拼接以及控制流这些,有兴趣的还是去找蔡老板进修一下

还原后看起来很清晰了,直接抠算法都很简单了

图片16.png

第二次c1请求的 Param 分析

和第一次一样的分析方式
或者直接断点在上面的加密位置上,两次的加密方法都是同一个
往上跟栈到如下位置

图片17.png

这次要加密的参数更多,所以加密后的Param长度也就更长了

ac 参数分析

给v1打上xhr断点,滑动滑块验证
跟栈跟栈
定位到一个Promise
代码往上看可以看到ac了,给打上断点再定位过去

图片18.png

可以看到在这里用getUA方法提取z里的ua的值,ua就是ac,早已生成了
聪明的我们往下看可以看到有个reload()方法,进去看看

图片19.png

进入reload方法后又是一个新的js文件greenseer.js
关键的算法都在这个文件中代码每天动态更新两次

图片20.png

又是大量的混淆,混淆方式和index.js一样,再解混淆方便各位阅读

图片21.png

清晰了,又可以愉快地分析了
分析reload方法可知这是在初始化z里的参数
最后再调用start方法
进start再看看

图片22.png

都是一些检测然后进行加密的流程
加密也都是一些简单的运算直接算法拿下就行

getTM:tm时间的加密
getBR:系统、浏览器版本的加密
getLO:document.referrer、location.href加密
getCF:随机数加密
getDI:判断window.top是否等于window.self以及window窗口宽高
getEM:自动化特征检测
getJSV:js版本号的加密
getTK:sid加密
getSC:window.screen加密
bindDomEvents:监听轨迹加密

所有方法最终都会调用app这个方法

图片23.png

进入app方法中可以看到最终ua的生成逻辑
将加密的参数追加到_ua中再将_ua经过btoa方法得到ua

图片24.png

进入bindDomEvents方法前已经生成了一个短的ua了
下面就是将轨迹加进去了

图片25.png

滑动距离识别随便找个开源ocr或者用cv2,注意图像的缩放
轨迹检测也不严格,随便网上找个或者模拟写个和浏览器上差不多的就行
轨迹算法

def get_track(self, distance):
    def __ease_out_expo(sep):
        if sep == 1:
            return1
        else:
            return1 - math.pow(2, -10 * sep)

    def random_randint(min, max):
        range_val = max - min
        rand = random.random()
        num = min + round(rand * range_val)
        return int(num)

    slide_track = [[755, 325, 3526]]
    count = 30 + distance // 2
    t = random_randint(50, 100)
    _x = 0

    for i in range(1, count + 1):
        sep = i / count
        x = round(__ease_out_expo(sep) * distance)
        t += random_randint(30, 50)

        if x == _x:
            continue

        slide_track.append([755 + x, random_randint(320, 330), 3526 + t])
        _x = x

    return slide_track

轨迹加密共有三处
其中1和2不是强校验可以不处理不追加进ua
1、点击事件轨迹,将点击验证出现滑块图前的点击轨迹加密进去
2、移动事件轨迹,将点击验证出现滑块图前的移动轨迹加密进去
3、移动事件轨迹,将滑动滑块时的轨迹加密进去

这里只分析移动滑块轨迹的加密逻辑
进入recordSA方法
分析可知这里获取了pageX、pageY、轨迹点时间,加密后存入_sa数组

图片26.png

需要注意,滑动距离是有偏移计算的

图片27.png

recordSA方法之后需要进入sendSA方法处理_sa数组

图片28.png

sendSA方法就是遍历_sa数组调用app更新ua

图片29.png

sendSA完事后进入最后的sendTemp方法

图片30.png

传入一个xpath、x距离、y是获取图片地址的a接口响应中的参数
获取html标签中的一些信息再拼接上传入的参数进行加密更新ua

图片31.png

现在的ua就是我们需要的最终结果了
以上就是全部ac的逻辑了

结果验证

图片32.png

算法不难但建议找个第三方固定js的网站去练习
官网每天早上10点和晚上7点更新js(实际会延迟1个小时)
建议先用补环境方法理解全部过程后再抠算法

免费评分

参与人数 13吾爱币 +13 热心值 +10 收起 理由
jiajia005200 + 1 牛哇
lucifer11 + 1 + 1 谢谢@Thanks!
yuchen123 + 1 + 1 用心讨论,共获提升!
fiscivaj + 1 鼓励转贴优秀软件安全工具和文档!
JiangtaoChiu + 1 + 1 谢谢@Thanks!
anyiy + 1 + 1 谢谢@Thanks!
silenter6speake + 1 热心回复!
KOCBT + 1 + 1 用心讨论,共获提升!
fengbolee + 2 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
user23456 + 1 + 1 谢谢@Thanks!
liuxuming3303 + 1 + 1 谢谢@Thanks!
helian147 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

tian214216 发表于 2025-8-17 18:59
不明觉厉!!!
131486352 发表于 2025-8-14 16:13
loading2025 发表于 2025-8-15 01:24
kongyueshang 发表于 2025-8-16 18:56
66666666666666666666666
ljl9090 发表于 2025-8-16 21:36
66666666666666
tianmiao 发表于 2025-8-17 10:38
大佬大佬upup牛
想喝饮料 发表于 2025-8-17 19:11
大佬!!!膜拜666
Xuan688 发表于 2025-8-20 09:22
感谢大佬分享
bighero006 发表于 2025-8-20 09:53
牛,开始学习
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-2-16 00:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表