吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 16822|回复: 44
收起左侧

[转贴] 小学数学破解滑动拼图验证码

  [复制链接]
苏晓宇c 发表于 2019-2-14 22:53
本帖最后由 苏晓宇c 于 2019-2-15 00:40 编辑




首先声明:此贴为转贴
昨天在FB上看到的 觉得是一种比较简单明了的滑动验证码处理方式,所以转过来。
当然有更加高效的验证码识别,大牛勿喷


括弧
如果每次缺口形状是随机的话,这种方式就不能用了。
就像有的滑动验证 第一次星星图案 第二次月亮图案 第三次变成了拼图
括弧



近几年出现了很多新型验证码,在用户体验上完爆传统的字符图片验证码,
而滑动验证码又是最常用的新型验证码之一,那么它的安全性如何呢?相比传统字符型图片验证码有没有提升呢?本文将给出答案。


破解滑动验证码的文章网上有很多,绝大多数使用了神经网络和视觉识别算法。
这些算法是建立在复杂的数学理论基础上的,没有高数和概率论基础的同学很难理解。而本文将另辟蹊径,不用tensorflow也不用opencv,手工建立简单的数学模型,来识别滑动(拼图式)验证码。


你可能会问,放着现成的轮子不用你是不是傻。
我认为,验证码的对抗跟漏洞攻防不一样,漏洞攻防是技术上的对抗,而验证码对抗更多的是成本上的博弈,



如果你打算雇佣几n个人给你标记大量的样本,又花上几十个小时训练一套模型,那么你已经输在了起跑线上了,因为如果验证码换一套参数,你可能又要从新再来一遍。
步骤可以分为两部分,首先是识别缺口位置,然后操作浏览器,拖动滑块到缺口位置。识别缺口位置本文以某款流行的商业滑动验证码为例。

如下图所示:




缺口位置左侧边缘总是一条高度约40个像素的垂直竖线,而且缺口边沿颜色较浅。因此识别缺口位置可以简化成识别这条竖线。

先把图片转化成灰度图像,这样一来每个像素的RGB三值转变成了一个灰度值,抛弃了一些不需要的色彩信息。

缺口边缘 我们看看竖线边缘是什么样的,选择紧挨边缘的一个像素点,大概就是图1中红框的位置,

然后以该像素点为中心读取的四周的像素的灰度值,即中心、左、左上、上、右上,右、右下、下、左下。
这样我们的采样点就变成了一个3*3的矩阵。下面的数据是不同的的三张图片缺口边缘的采样数据。      



图3


采样色块第三列为竖线边缘。我们观察这几组数据,试着发现规律。
首先只看矩阵的行,发现大部分时候,每一行的第一个和第二个元素差别不大,而第二个和第三个元素有断崖式上升/下降。然后我们看矩阵的列。

第一列和第二列属于图片本身的自然内容,其灰度值有的变化大、有的变化小,没有一定规律。


但是第三列就不一样了,第三列是图片缺口的边缘,灰度值变化很小。因此识别缺口左侧边缘的方法如下:
1.以3*3的方块竖向扫描整个图片,对于每一个方块,做如下操作;
2.“方差”代表了数据波动大小。
求第一行、第二行、第三行的方差为A1、A2、A33.求第三列的方差:B3;4.B3分别与A1、A2、A3比较大小,如果比它们大,则认为是该矩阵可能位于缺口边缘。





完整代码如下:
def is_border(pixels):
    """
    判断3*3的像素块是否位于缺口边缘
    :param pixels: 像素快,3*3矩阵
    :return: bool 是否是缺口位置
    """
    if len(pixels) != 3 or len(pixels[0]) != 3:
        print("error pixel array.")
        return False
    cov1 = np.cov(pixels[0]) # 求第一行方差
    cov2 = np.cov(pixels[1]) # 求第二行方差
    cov3 = np.cov(pixels[2]) # 求第三行方差
    cov_border = np.cov(pixels.T[2]) # 求第三列方差
    # 做比较,如果第三列方差大于三个中的两个,便认为是缺口位置
    level = 0
    if cov_border < cov1:
        level += 1
    if cov_border < cov2:
        level += 1
    if cov_border < cov3:
        level += 1
    if level >= 2:
        return True
    else:
        return False



这样扫描一轮你会发现,这类满足条件的色块有很多,即便是图片正常的部分也有很多满足的。
也就是说,满足上述条件的色块,只是说明它是缺口边缘概率比较大而已,并不代表一定是缺口边缘;即便是边缘色块,也有小概率不符合上述条件。思路到这里就卡住了。
但是我们注意到一个特点,就是缺口的左边缘是一条40个像素的直线,而且是竖线。
在这条直线上,会有超过50%的像素点满足边缘特征。并且很多符合特征的点都是连续的,连续的越多,越有可能是我们要得到的。程序步骤如下:


1.遍历图片的每一列,统计这一列中符合边缘特征的色块;
2.如果第i列正色块(符合边缘条件的以下统称为正色块,不符合边缘条件的称为负色块)数量大于20,则为该列初始化一个得分Mi;
3.Mi初始化为0,如果连续出现N个正色块,得分就是Mi=Mi+N。(注意一列里面可能有多个连续的片段,得分即连续正色块的数量之和)

def scan_array(img_arr, start_x=1, start_y=1):
    """
    计算每一列的得分
    :param img_arr: 图片灰度值
    :param start_x: 从第x列开始计算,合理选择该值可以加快运算时间
    :param start_y: 从第y行开始计算
    """
    all_score = {} # 综合分数
    for x in range(start_x, img_arr.shape[1]):
        col_result = []
        for y in range(start_y, img_arr.shape[0]):
            # 取色块
            pix = select_pixels(X=x, Y=y, img=img_arr)
            if pix is None:
                continue
            # 判断是否符合边缘条件
            col_result.append(is_border(pix))
        temp_str = ""
                # T代表正色块,F代表负色块
        for elem in col_result:
            if elem:
                temp_str += "T"
            else:
                temp_str += "F"
        temp_ar = temp_str.split("F")
        for index in range(0, len(temp_ar)):
            # 边缘条件色块大于20的时候开始计算得分
            if len(temp_ar[index]) > 20:
                all_score[x] = len(temp_ar[index])
                if index-1 >= 0:
                                        # len(temp_ar[index-1]) 是连续的正色块的个数
                    all_score[x] += len(temp_ar[index-1])
                else:
                    all_score[x] += len(temp_ar[index+2])
                if index+1 < len(temp_ar):
                    all_score[x] += len(temp_ar[index+1])
                else:
                    all_score[x] += len(temp_ar[index-2])
    print(all_score)
        ...



因此理论上得分越高,它越有可能是缺口边缘,然而测试情况却不理想。原因在于图片的正常部分,也会有物体边缘,因此如果只是简单的判断得分最高的是边缘,会有很多误报。

我们还需要其它条件来补充我们的证据。

通过观察,缺口是3列像素,在这3列像素中,正色块数量差不多,找了几个图片测试了一下,这三列像素中的连续正色块数量的方差不会超过44。
(这个值可以根据不同的验证码进行自由调整,总体来说,这个值越小,误报就越低但漏报会增加)
def select_best(all_score, exclude=[], func_type="recursion"):
    """
    获取缺口边缘坐标
    :param all_score: 所有列的得分。 格式为map[横坐标数字] = 分数
    :param exclude: 不检查的列。格式为数字数组
    :param func_type: 不同的执行模式
    :return: 缺口位置的横坐标
    """
    max = 0
    key = 0
    for x_key in all_score:
        # x_key为图片横坐标
        # 由于缺口是40个像素,如果发现超过40个像素的正色块,说明不是缺口边缘
        if all_score[x_key] > 40:
            continue
        # exclude中的横坐标不做检查,主要为了后面做递归
        if x_key in exclude:
            continue
        if all_score[x_key] > max:
            # 寻找得分最大的列
            max = all_score[x_key]
            key = x_key
    if max > 0:
        if func_type == "max":
            return key
        # key 是得分最大的列的横坐标
        # 然后判断key和key右侧以及右侧的右侧的列的坐标是否符合缺口条件,且三者得分(即连续正色块数量)的方差在44以内。符合条件的话直接return
        if key+1 in all_score.keys() and key+2 in all_score.keys():
            if np.cov([all_score[key], all_score[key + 1], all_score[key + 2]]) < 44:
                return key
        # 同上,检测key与key左侧和右侧
        if key + 1 in all_score.keys() and key -1 in all_score.keys():
            if np.cov([all_score[key], all_score[key + 1], all_score[key - 1]]) < 44:
                return key
        # 同上,检测key与key左侧和左侧的左侧
        if key - 2 in all_score.keys() and key - 1 in all_score.keys():
            if np.cov([all_score[key], all_score[key - 1], all_score[key - 2]]) < 44:
                return key
        # 如果key不满足条件,key则加入exclude列表,然后进行递归,直到找到符合条件的列
        exclude.append(key)
        return select_best(all_score, exclude=exclude)
    else:
        return None



返回值就是缺口位置。操作浏览器通过测试发现,这款验证码对于拖动滑块的速度是有严格校验的,如果按如下行为操作滑块是无法通过验证的:1.直接将滑块瞬间拖至缺口位置。2.将滑块以匀速拖至缺口位置。以上两类行为有明显的程序操作特征,而人类操作滑块有如下特征:1.滑块速度先快后慢,但加速度不恒定。2.在接近缺口的时候,滑块可能会左右摇摆来适应缺口位置。3.滑动过程中,鼠标的Y坐标不是恒定不变,会有上下浮动。操作浏览器我准备了三套方案:1.直接在验证码页面中执行js。虽然能执行绑定在元素上的各种js事件。但是模拟程度有限,例如无法模拟真实的鼠标滑动轨迹以及鼠标点击。且权限较低,可被验证码的js检测到,易陷入攻防战。2.使用按键精灵。灵活权限高,可以模拟任何真实的的鼠标操作。但是无法多开浏览器并行操作(除非使用多个虚拟机)。3.使用selenium。相当于浏览器“外挂”,即可以多开,在输入模拟方面不如按键精灵灵活。因此最终选择了selenium方案,代码如下:

# 获取滑块元素
elem = driver.find_elements_by_class_name("yidun_slider")[1]
# 下载验证码图片,保存为临时文件
network.download(img.get_attribute("src"), "./temp/cc.jpg")
# 分析缺口位置
move_x = pic.get_x_file("./temp/cc.jpg")
#action.click_and_hold(elem).move_by_offset(elem_x + move_x, elem_y).release().perform()
# 模拟鼠标操作,点击滑块不松开
mouse_action = action.click_and_hold(elem)
# 由于图片在页面中进行了缩放32/33,因此缺口位置也要等比例缩小
# move_x为要移动的总距离
move_x = int(move_x * 32/33)
# selenium有限制(或者bug),鼠标最少只能移动4个像素
# 因此要将滑块移动至指定位置,最多需要执行move_steps步
move_steps = int(move_x/4)
for i in range(0, move_steps):
    # 路程前半部分速度较快
    if i < int(move_steps/2):
        # sleep(random.randint(1, 10) / 500)#
        # 滑块每次向右移动四个像素,鼠标Y坐标在上下5个像素内随机摆动
        mouse_action.move_by_offset(4, random.randint(-5, 5)).perform()
    else:
        # 在路程的后半段,越接近终点速度越慢
        # 每次移动之前sleep一段时间,时间为总距离与已移动距离方差的倒数
        seed = 90.0/(pow(move_steps, 2) - pow(i, 2))
        sleep(seed)
        mouse_action.move_by_offset(4, random.randint(-5, 5)).perform()
    print(elem.location)
    action = ActionChains(driver)
    mouse_action = action.click_and_hold(elem)
# 到达终点时,左右摆动,假装做调整。
#sleep(0.1)
#mouse_action.move_by_offset(5, random.randint(-5, 5)).perform()
#sleep(0.2)
#mouse_action.move_by_offset(-4, random.randint(4,5)).perform()
sleep(0.1)
# 松开鼠标
mouse_action.release().perform()


以上代码步骤如下:
1)下载图片,分析缺口位置,得到缺口横坐标,由于图片一般不会在网页中以原始尺寸显示,而是按一定比例放大或缩小,因此缺口横坐标也要等比放大或缩小;
2)计算总共要移动多少步。在路程的前半段,保持较快的速度。路程后半段,约接近终点,速度越慢。
设总距离为D,已经移动了x,K是一个人工设置的常量(代码中为90),那么滑块速度为:



测试结果经测试,缺口位置识别的准确率在80%以上,而拖曳操作这一步的通过率只有60%左右,分析原因,由于selenium每次只能拖曳最小4个像素,无法模拟出鼠标那种平滑流畅的拖曳操作,因此与正常的操作行为差别较大。
以下是屏幕录像,可见虽然拖曳成功,但是卡顿感较强。


https://v.qq.com/x/page/t0831kral7w.html

abcb98da7bc073d8b9634a57668d0328.GIF





谁更安全理论上所有形式的验证码都是可破解的,我们在讨论其安全性的时候,无非就是在讨论破解成本。
笔者曾用LSTM+CNN破解过变长加噪音的图片验证码,虽然也达到了90%以上的准确率,但是付出的代价是14天+700元打码费用,
反观这次破解,用时只有两天,“训练数据”只有6张图片,准确率稍逊但成本方面完胜。

经过本次研究,可以得出结论:滑动拼图式验证码相比传统的字符图片验证码,在安全性上并没有什么提升,甚至不如传统方案








如果对你有帮助,麻烦给个热心呦,谢谢大家了
(影视区进不去很头秃啊)






本文作者:山东星维九州安全技术有限公司,转载请注明来自FreeBuf

点评

感谢转载这么好的帖子  发表于 2019-2-16 10:13

免费评分

参与人数 20吾爱币 +20 热心值 +19 收起 理由
迷雾 + 1 + 1 我很赞同!
lanzhipeng2011 + 1 + 1 我很赞同!
天羽华客 + 1 用心讨论,共获提升!
xgameboy + 1 + 1 牛逼,可以写成论文了
随风丶而逝 + 1 用心讨论,共获提升!
小六升臣 + 1 + 1 谢谢@Thanks!
Xiao_Hui + 1 + 1 我很赞同!
boyulin + 1 + 1 鼓励转贴优秀软件安全工具和文档!
莲子荷花 + 1 + 1 谢谢@Thanks!
liphily + 3 + 1 可以写成论文了
天上飞来一只 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
szjzxm4321 + 1 + 1 我很赞同!
shaoyiwei + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Menglong + 1 + 1 谢谢@Thanks!
rainyl + 1 + 1 我很赞同!
SafeTech + 1 + 1 神TM的小学~~
saya0769 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
wangyeyu2015 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
lenmou + 1 + 1 我很赞同!
宝男居 + 1 + 1 谢谢@Thanks!

查看全部评分

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

 楼主| 苏晓宇c 发表于 2019-2-15 23:20
打字的小强 发表于 2019-2-15 22:13
这不是抄袭的帖子吗?今天还百度出来过

= = 你们都不带看的嘛,我上来就先说了
首先声明:此贴为转贴,
昨天在FB上看到的 觉得是一种比较简单明了的滑动验证码处理方式,所以转过来。
当然有更加高效的验证码识别,大牛勿喷



结尾还标明了原作者
本文作者:山东星维九州安全技术有限公司,转载请注明来自FreeBuf

涛之雨 发表于 2019-2-15 15:54
cqr2287 发表于 2019-2-15 08:52
那种点一下就能识别是人是机的验证码是怎么实现的

根据鼠标轨迹和速度(这个不是仅点击的时候判断的。在ja加载完成的时候就开始了。)
之前闲的无聊,分析过某公司的js文件
。。。所以。。知道一点
qazwsx357rainy 发表于 2020-8-9 02:48
是个高手啊,实干派,真正能解决问题,受教了
小飞虫 发表于 2019-12-14 22:45
150164 发表于 2019-2-25 13:38
震惊!!!小学数学破解滑动拼图验证码,大学生居然看不懂!

哈哈哈赶紧回小学复读
unhack 发表于 2019-12-10 17:03
算4个角是不是更好
150164 发表于 2019-2-25 13:38
震惊!!!小学数学破解滑动拼图验证码,大学生居然看不懂!
破解促进发展 发表于 2019-2-16 16:16
可以的兄弟, 你的水平不错啊,我可以带你的
wangshi467 发表于 2019-2-16 13:49
膜拜大森、、完全看不懂的先回帖路过,看懂了再来
蓝晴 发表于 2019-2-16 10:31

厉害,我虽然小学了,完全看不懂……
打字的小强 发表于 2019-2-15 22:13
这不是抄袭的帖子吗?今天还百度出来过
小六升臣 发表于 2019-2-15 20:52
这个我真要学习下
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-3-29 05:43

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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