文章日期:2025.04.26
使用工具:python3.12.7
本章知识:逆向分析还原顶象的滑块背景图和点选背景图
jsv: 5.1.49
version:2.5.1 (window.core.version)
文章难度:简单
文章全程已做去敏处理!!! 【需要做的可联系我】
AES解密处理(直接解密即可)(crypto-js.js 标准算法):在线AES加解密工具
注意:仅供学习!!仅供交流!!仅供测试!!
文章末尾附上源码,如果用不了了,可能是官方更新了,请谅解!!!
先看个小视频
1、打开某某网站(使用文章开头的AES在线工具解密): UGXl3oWuv0ITRPtnTNRRSJ2LR0XN9STAMTBHcK5qBmY=
2、打开网站后,使用选择账号密码登录,然后随便输入测试参数后,点击登录,会看到弹出了这个窗口,我们先分析滑块,如果出现的是点选,我们就不停的刷新,直到出现滑块为止
3、我们找到事件监听器,选择画布断点,然后刷新滑块,当滑块的图片进行渲染的时候,是需要调用画布去还原图片的,所以我们让他断住后,我们就可以知道他是怎么还原的,他的算法是什么,我们都可以去分析
4、断住后,我们需要在这里确认一下是不是滑块,如果不是滑块,那我们就进行刷新,直到出现滑块为止,因为他的点选图片和滑块的图片大小不一样,会影响我们分析的结果,所以我们就先固定分析一种类型,那就是从滑块先入手。
5、如果是滑块图片,那我们就回到已经断住的地方,我们开始逐步分析,他有流程的,【0132】,【01】就是用来创建并设置画布的大小,【3】就是计算混淆的背景图里的单个碎片的宽度,【2】是从混淆的背景图里抽取碎片并拼接在一起。可以大致看一下流程
6、然后来分析一下这个混淆的图片,我们要知道混淆的图片的宽高,包括他是多少层碎片,如下图所示,这个是单层的碎片,这种相对简单些
7、我们对这个【2】打上断点,跟过来,接下来我们单步进去看看是怎么回事
8、单步发现进不去,我们就手动进去这个【o】参数,然后打断点,进去
9、我们打完断点后,进来发现他运算的很简单,可以看一下我下面的图,有点乱,将就一下,然后我们现在知道了每个参数是怎么来的,那我们就用python去模拟一下。
【定值】 = 一串数组,暂时先写死,32位的
【碎片的宽度】 = 混淆图片的宽度 / 定值的长度
【x轴的初始坐标】 = 遍历定值 * 碎片的宽度
【x轴位于初始坐标的右侧坐标(宽度)】 = x轴的初始坐标 + 碎片的宽度
【y轴的初始坐标】 = 0 (这个直接写死)
【y轴位于初始坐标的底部坐标(高度)】 = 混淆图片的高度 (切记这个不能写死,有些图的大小不一样)
10、python模拟了一下,如果没有看懂我上面的讲解,可以结合我下面的这个python代码进行了解分析。
11、我们运行一下,看看效果是什么样的。效果很成功,还原了90%,还原后的图片有一小块没有还原
12、为了方便接下来是视觉展示效果,我更换了滑块图片
13、嘿,他缺少的那一块拼图,就是在最后面,然后我量了一下他最后一块碎片的初始坐标,是384,那么缺少的最后一块的定值应该是【384 / 12 = 32】,然后经过多个测试,发现他所有图片最后一块的碎片的定值都是32,那就妥当了
【x轴的初始坐标】 = 遍历定值 * 碎片的宽度
【定值】 = x轴的初始坐标 / 碎片的宽度(384 / 12 = 32)
14、修改python代码后,我们再次运行,发现已经成功补上了99%,还差1%没补上,怎么办,其实很简单,我们只需要在最后一步,也就是定值为32时,把碎片的宽度改高一点即可
15、我们把碎片的宽度修改为16,切记,只能在裁切的地方修改,其他地方不用改,前面会裁切多余,但会被覆盖的,唯独最后一块碎片,他的宽是16,是唯一不会被覆盖,因为遍历定值已经结束了,没有碎片去覆盖他了。
16、还原图片到此就结束了,接下来我们要去知道他的定值是怎么来的,因为我看了他的接口,接口是没有返回定值的数据信息,所以我敢肯定他的定值一定是本地通过什么参数生成的。废话不多说,我们直接去分析
17、【定值的生成】我们依然是通过画布去断住,然后我们在这个调用函数的起始位置打断点,然后刷新滑块(此时不用去管是滑块还是点选,因为他们的定值生成都是用的同一种方法)
18、【定值的生成】刷新滑块后,断住了,然后我们打开闭包,可以看到定值是已经生成了,而且堆栈的信息都出来了,可以进行调试了,那我们就向上跟栈,看看哪里有可疑之处,就打断点排查。
19、【定值的生成】经过排查,发现他的生成位置,我们直接打上断点,然后刷新滑块
20、【定值的生成】进来后,我们发现他其实是将滑块的链接里的hash提取了出来,然后用【H】函数生成的定值,这就简单了,我们直接进到【H】方法里
21、【定值的生成】进来后,发现他这个计算方式很简单,就是将hash字符串一个一个都转为ASCII码,然后再用ASCII码去计算结果,得出结果后,要先检测这个结果有没有出现过,如果出现过这个结果,那么就需要将ASCII值+1,然后再进行计算,然后再进行检测,如果还是重复,那就重复刚刚的+1步骤,如果不重复,则添加到列表内。接下来我用python给大家模拟一下
22、【定值的生成】用python模拟出来的效果就是这样的,可以看注释,比较详细点,这个不复杂。运算后得出的定值和官方的一模一样,这就成功了
23、我把python代码进行了修改,看下面的展示示例
24、【点选的背景图还原】这个我就不过多阐述了,答案都已经出来了。滑块和点选用的都是同一种算法,只是过程中容易混淆,所以一开始让大家固定选择滑块去分析,就是为了不被点选的参数所混淆,点选的图片大小和碎片宽度都是不一样的,这一点在做分析的时候容易出现问题。
现在已经把整个流程都对接完成了,参数的生成都没有问题了。给大家看一下点选的还原。
【附上完整代码】
from PIL import Image
def dingxiang(file_path, save_file_path):
"""
file_path:混淆的图片路径
save_file_path:要保存的图片路径
"""
def o(hash_value):
'''
网站:https://user.dingxiang-inc.com/
通过hash值计算出回复滑块的定值
'''
def get_unique_num(i, used_numbers):
"""递归"""
# 将ASCII码取模32 运算
num = i % 32
# 检测运算后的值是否已经出现过了,有没有重复出现,如果没有重复出现,则返回这个定值
if num not in used_numbers:
used_numbers.add(num)
return num
# 运算后的值如果已经出现过了,那么就需要将ASCII值+1,然后再次调用自身去运算,直到返回一个不重复的值为止(递归)
return get_unique_num(i + 1, used_numbers)
# 用于检测是否已经存在过了
used_numbers = set()
# 存储最终的定值
data = []
# 遍历hash字符串
for char in hash_value:
# 将字符转为ASCII码
i = ord(char)
# 进入递归,获取定值
unique_num = get_unique_num(i, used_numbers)
# 将定值存储到列表内
data.append(unique_num)
return data
hash_data = file_path.split('/')[-1].split('.')[0]
# 读取 滑块混淆图片
image = Image.open(file_path)
# 获取 混淆图片 的 宽高
width, height = image.size
print('[混淆图片][宽高] -> ', width, height)
# 碎片的宽度 = 混淆图片的宽度 // 定值的长度
s = width // 32
print('碎片的宽度 -> ', s)
# 创建新的空白图片(相当于画布)宽高要和混淆的图片保持一致
new_image = Image.new('RGBA', (width, height))
# 定值 数组列表,可以先临时写死。 注意:如果混淆的图片更改了,这个定值也要修改,这个定值是动态的,不是静态的
o = o(hash_data) + [32]
print('定值 -> ', o)
# 新画布的初始坐标点
k = 0
# 遍历定值列表
for _ in o:
# 【x轴】初始坐标
c = _ * s
# 【y轴】初始坐标 0 固定写死的
# 【x轴】位于初始坐标的右侧坐标(宽度) = c + s
# 【y轴】位于初始坐标的底部坐标 = height (和混淆的图片的高度保持一致)
image2 = image.crop((c, 0, c + 16, height)) # 裁切小碎片
# 将裁切的碎片按顺序一个一个的存储到新建的画布中
new_image.paste(image2, (k, 0))
# 更新 新画布的初始坐标点 我们存储的碎片的宽度是s,那我们就需要把坐标+s,那么我们下次在进行存储就不会覆盖原来的碎片
k += s
# 将数据写入buffered里
new_image.save(save_file_path, format="PNG")
dingxiang(file_path='e9ee618000f54c34bcae598f16cf3814.webp',
save_file_path='1.jpg')