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

查看 75|回复 9
作者:LiSAimer   
声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请联系作者删除。
逆向目标
目标网站:
aHR0cHM6Ly93d3cuZGluZ3hpYW5nLWluYy5jb20vYnVzaW5lc3MvY2FwdGNoYQ==
关键要点:图像撕裂还原、轨迹、参数【lid, c, ac】
抓包分析
本篇文章以官网demo 5.1.53 版本进行分析,需要注意官网关键js文件每天会动态更新两次
进入网页,按F12打开调试窗口后刷新页面,建议每次都清理一下缓存或者直接使用无痕模式
刷新页面后定位到demo位置,点击滑动拼图查看发送请求


图片1.png (121.67 KB, 下载次数: 4)
下载附件
2025-8-13 16:25 上传

接口 a 请求载荷


图片2.png (72.3 KB, 下载次数: 4)
下载附件
2025-8-13 16:25 上传

这里只需要注意aid的生成
aid = 'dx-' + str(int(time.time() * 1000)) + '-' + str(random.randint(10000000, 99999999)) + '-3'
接口 a 响应内容


图片3.png (94.97 KB, 下载次数: 4)
下载附件
2025-8-13 16:25 上传

p1:背景图地址
p2:滑块图地址
sid 和 y 后面需要用到
当我们滑动滑块完成验证后会发送一个v1接口的请求


图片4.png (88.1 KB, 下载次数: 3)
下载附件
2025-8-13 16:25 上传

提示:本文截图中某些值可能和之前截图中的不一致,以文字说明为准,图知其意便可
ac:包含轨迹的加密参数
c:c1接口获取需要逆向
sid:a接口响应中获取
aid:这里aid要与a接口的aid一致
x:滑动距离
y:a接口响应中获取
v1接口响应中的token值是我们本次逆向的最终目标


图片5.png (39.22 KB, 下载次数: 3)
下载附件
2025-8-13 16:25 上传

流程梳理
1、第一次请求c1接口获取lid
2、第二次请求c1接口获取data值
3、请求a接口获取图片地址以及一些关键参数
4、请求v1接口完成验证获取token
图像还原
老样子打上canvas断点后点击验证滑块,断到如下图位置


图片6.png (66.08 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

分析入参
n:canvas对象
t:img对象
i:图片高度
e:图片高度
r:32位数组
分析代码
1、将一张图像完整绘制到canvas上
2、然后根据数组r的长度将图像水平分割成多个切片
3、再按照数组r的顺序重新排列这些切片绘制到canvas上
知道逻辑了那么就需要找到关键32位数组是如何生成的
继续往上跟栈找到生成位置


图片7.png (35.1 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

函数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)
        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 (15.85 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传



图片9.png (11.87 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片10.png (46.11 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传



图片11.png (40.61 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片12.png (109.6 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传



图片13.png (27.59 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片14.png (73.09 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

参数除lid外都是固定的
lid生成代码
l = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
lid = str(int(time.time() * 1000)) + ''.join(random.choice(l) for _ in range(32))
进入加密函数后可以看到是个控制流。


图片15.png (45.96 KB, 下载次数: 3)
下载附件
2025-8-13 16:26 上传

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


图片16.png (31.37 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片17.png (109.26 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

这次要加密的参数更多,所以加密后的Param长度也就更长了
ac 参数分析
给v1打上xhr断点,滑动滑块验证
跟栈跟栈
定位到一个Promise
代码往上看可以看到ac了,给打上断点再定位过去


图片18.png (63.9 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片19.png (62.19 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片20.png (27.65 KB, 下载次数: 2)
下载附件
2025-8-13 16:26 上传

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


图片21.png (69.63 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片22.png (17.71 KB, 下载次数: 3)
下载附件
2025-8-13 16:26 上传

都是一些检测然后进行加密的流程
加密也都是一些简单的运算直接算法拿下就行
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 (10.6 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片24.png (20.25 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片25.png (44.08 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

滑动距离识别随便找个开源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 (16.96 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片27.png (98.48 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片28.png (40.26 KB, 下载次数: 3)
下载附件
2025-8-13 16:26 上传

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


图片29.png (9.58 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片30.png (41.31 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片31.png (28.74 KB, 下载次数: 4)
下载附件
2025-8-13 16:26 上传

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


图片32.png (73.5 KB, 下载次数: 3)
下载附件
2025-8-13 16:27 上传

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

下载次数, 下载附件

131486352   

真牛逼,这都能逆向,太刁了大佬
loading2025   

第一次看到有人逆向这
ooorangeeeeee   

太帅了大佬
kongyueshang   

66666666666666666666666
ljl9090   

66666666666666
tianmiao   

大佬大佬upup牛
tian214216   

不明觉厉!!!
想喝饮料   

大佬!!!膜拜666
Xuan688   

感谢大佬分享
您需要登录后才可以回帖 登录 | 立即注册

返回顶部