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

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3423|回复: 49
上一主题 下一主题
收起左侧

[Web逆向] [验证码逆向]某手势验证码逆向分析

  [复制链接]
跳转到指定楼层
楼主
s1lencee 发表于 2024-2-8 01:00 回帖奖励
本帖最后由 s1lencee 于 2024-2-8 15:56 编辑

[验证码逆向]某手势验证码逆向分析

本文章所有内容仅供学习和研究使用,本人不提供具体模型和源码。若有侵权,请联系我立即删除!维护网络安全,人人有责。


前言

手势验证码是一种绘制轨迹进行人机的验证码,我曾经遇到过几次。

查了查网上没有较好的教程,便打算自己写一篇文章。

目标网址:

aHR0cHM6Ly93d3cudmFwdGNoYS5jb20vI2RlbW8=

手势验证码长这样

我将该验证码的协议逆向分析和验证码识别分为2篇文章,本文主要研究该验证码的逆向分析。

目录

  • 请求分析

    • 准备工作

    • 4种请求

    • config请求

    • get请求

    • validate请求

  • 获取验证码

    • en参数分析

    • fp指纹分析

    • fp指纹分析2

    • 构造en参数

    • 获取数据

  • 图片还原

    • 还原代码分析

    • 获取图片还原顺序

    • python还原图片

  • 图片识别

    • 关于图片识别

  • 构造请求

    • 请求分析

    • 轨迹分析

    • 轨迹加密

    • 请求验证

  • 总结


请求分析

准备工作

首先,每个验证码平台都有唯一的业务标识,用于区分不同网站。我们应该养成看文档的好习惯,一般情况下直接在该验证码的官网找到Web端开发文档

可以看到可以看到验证码初始化的代码,vid就是唯一标识,然后在验证码出现的页面全局搜索vaptcha(

然后记住该id59b252ed57f5a2111xxxxxxx(为了脱敏,部分真实数据我将用xxx代替)

4种请求

该验证码没有debugger反爬措施,直接打开浏览器开发者工具抓包即可。

可以看到一共有4种请求,其中59b252ed57f5a2111xxxxxxx接口就是用户的vid,用于获取验证服务器。

返回的api就是下面3个请求的域名。

config请求

该请求是获取验证码配置的,即一些sdk地址和版本。

请求数据

响应数据

vi参数就是vid

其中我们只需要响应数据的knock

那么u值是怎么生成的呢?

我们开始跟栈

getConfig这个函数名那么明显,我们进去看看

可以明显看到,u值的来源是localStorage中的vaptchanu,那么意味着可以是空值。

当把该页面的缓存清空后,可以发现u值为空,这里我就不放图了,有兴趣大家试试。

get请求

顾名思义,该请求是获取验证码数据的。

请求数据

响应数据

可以看到k参数就是config接口返回的knock

en参数包含一些环境检测,我将在下文分析

validate请求

该请求用于验证是否正确。

请求数据

正确的响应数据

返回的token值就是我们的目标,我们携带该值请求需要验证的接口就可以返回数据了。

获取验证码

en参数分析

我们进入post的前面的栈

由于switch的存在,代码是分步执行的,我们在switch打上记录点,记录执行顺序

可以看到,获取了一些localStorage的内容,

hex_md5其实就是md5加密 (严格来说不算加密,因为md5无法解密)

我们清空缓存后重新请求可以发现执行顺序。

6
0
1
2
5

我们只需要把执行过的函数扣下来即可。

我只说几个重点

  1. 获取了localStorage的变量都是三元运算符,当localStorage里的值为空时就会设置默认值

  2. _0x502684['GenerateFP']_0x5d0164['GenerateFP']都是浏览器指纹,但是后者是Promise异步任务,我接下来会分析这两个的区别。

  3. _0x4f54b3['secretC']是一个固定值。

  1. _0x4f54b3['globalMd5']config返回内容处理后的md5加密值。

将返回值的值相加即可得到该字符串。由于js的相加和python不太一样,我们可以使用以下代码在python中实现

def sort_dict_by_key(input_dict):
    """字典按照键排序"""
    sorted_keys = sorted(input_dict.keys())
    sorted_dict = {key: input_dict[key] for key in sorted_keys}
    return sorted_dict

def splicing_obj(obj: dict):
    obj = sort_dict_by_key(obj)
    result = ""
    for k, v in obj.items():
        if isinstance(v, str):
            result += v
        elif isinstance(v, int):
            result += str(v)
        elif isinstance(v, float):
            result += str(v)
        elif isinstance(v, bool):
            result += "true" if v else "false"
        elif isinstance(v, list):
            result += ",".join(v)
        else:
            result += "[object Object]"
    return result

fp指纹分析

_0x502684['GenerateFP']函数主要通过canvas绘制特定字符再转换为base64来计算浏览器指纹。

传入一个参数,该参数将会绘制在canvas上。

该方法在不同浏览器上的结果不相同,这就意味着我们可以不用canvas来计算指纹,但是要求同一参数的计算值相同。

我们可以使用加盐的哈希算法来计算,在同一次请求中盐值应当相同(要注意CRC32校验,否则验证不通过)。

由于本文是教程,所以我就直接使用canvas来计算指纹(nodejs安装canvas库有些麻烦)

其他就是一些转换了,包括CRC32校验,直接扣下来即可。

fp指纹分析2

_0x5d0164['GenerateFP']是个异步函数

我们跟进函数内查看,在返回部分打个断点重新请求。

可以看到,_0x33ff5b是浏览器环境,经过hashComponents后返回一个哈希值(我没见过这种加密,有懂的大佬可以说说)

查看代码得知该GenerateFP函数可以传入两个参数:

  • 第一个参数将被设置进_0x33ff5b中,和浏览器环境一起加密
  • 第二个参数是个布尔值,当为true时返回全部哈希值,false时截取前面8为返回
// _0x41154d存在时将加入浏览器环境
if (_0x41154d) {
  _0x33ff5b = _0x3633c3(_0x3633c3({}, _0x2d65f5['components']), {
    'param': {
      'value': _0x41154d,
      'duration': 0x0
    }
  });
} else {
  _0x33ff5b = _0x3633c3({}, _0x2d65f5['components']);
}

// 计算哈希值
_0x4c267d = _0x5bf32f['hashComponents'](_0x33ff5b);

// 是否截取前八位,_0x596253是回调函数
if (_0x5799b7)
  _0x596253(_0x4c267d);
else
  _0x596253(_0x4c267d['slice'](0x0, 0x8));

_0x596253是回调函数,在下一步代码的_0x4f1db8['sent']()可以获取该值

我们直接将hashComponents全部扣下来即可。

关于浏览器环境

就是检测一些字体、插件、设置等,完全可以弄成定值,同样的相同的参数返回的值应该相同。

构造en参数

encryFuncselectFrom没有特殊含义,直接扣下来即可

最后,把扣下来的代码整合可以得到en

获取数据

最后把en携带参数去请求服务器即可获得验证码数据

图片还原

还原代码分析

可以看到,接口返回的图片是乱序的。

我们可以在事件侦听器中勾选canvas创建,因为浏览器还原乱序图片肯定会用到canvas

跳过几个检测指纹的canvas,我们成功断住了还原图片的地方

我们只需要注意传入的_0x8782d1即可,该变量就是图片顺序。

获取图片还原顺序

向前跟栈找到生成顺序的地方

大体就是一个计算得到的整数_0x19be99和接口返回的img_order经过一个_0x2f94f6['Decrypt']方法得到

en一样,找到switch的执行顺序以后可以得到0x19be99的值

_0x19be99 = hex2int(GenerateFP(ha)) + hex2int(hashComponents(hb)) + parseInt(secretC) + pow(r);

rhahb就是get接口返回的内容

当有一项为空值时,就直接设为0.

powwork2.js返回的内容,直接扣下来即可

整合后

function get_order(img_order, r, hb, ha){
    let hb_en = 0;
    let ha_en = 0;
    if (hb !== "" && hb) {
        hb_en = hex2int(hashComponents(hb))
    }
    if (ha !== "" && ha) {
        ha_en = hex2int(GenerateFP(ha))
    }

    let _0x28c598 = ha_en + hb_en + 8549731620 + pow(r);
    return Decrypt(img_order, _0x28c598)
}

python还原图片

from PIL import Image
from io import BytesIO

def restore(img, order):
    """还原图片"""
    img = Image.open(BytesIO(img))
    new_img = Image.new('RGB', (400, 230))
    width = 80
    height = 115

    def drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight):
        """
        param image:
        param sx:       开始剪切的 x 坐标位置。
        param sy:       开始剪切的 y 坐标位置。
        param sWidth:   被剪切图像的宽度。
        param sHeight:  被剪切图像的高度。
        param dx:       在画布上放置图像的 x 坐标位置。
        param dy:       在画布上放置图像的 y 坐标位置。
        param dWidth:   要使用的图像的宽度
        param dHeight:  要使用的图像的高度
        """
        split_img = image.crop((sx, sy, sx + sWidth, sy + sHeight))
        new_img.paste(split_img, (dx, dy))

    for i in range(10):
        if i < 5:
            j = i
            if int(order[i]) < 5:
                o = int(order[i])
                drawImage(img, j * width, 0, width, height, o * width, 0, width, height)
            else:
                o = int(order[i]) - 5
                drawImage(img, j * width, 0, width, height, o * width, height, width, height)
        else:
            j = i - 5
            if int(order[i]) < 5:
                o = int(order[i])
                drawImage(img, j * width, height, width, height, o * width, 0, width, height)
            else:
                o = int(order[i]) - 5
                drawImage(img, j * width, height, width, height, o * width, height, width, height)

    return new_img

传入2个参数即可还原,img是图片二进制数据,order为图片还原顺序

图片识别

关于图片识别

图片识别我将在另一个专题详细讲

大家可以稍等

构造请求

请求分析

get请求一样,在post前面断住

我们可以看到,和get请求的en加密基本相同,只不过多了几个参数。

其中_0x13cb8b变量包含验证信息:

  • dt: 用时,不到4位数用0补上
  • ch: canvas的高
  • cw: canvas的宽
  • v:  加密后的轨迹值

轨迹分析

_0x491ed2就是轨迹信息。

可以看到这个轨迹有点迷糊,有很多小数。我们找到生成轨迹的地方。

可以看到,就是鼠标位置减去绘制的起始位置。

这里有个坑。

这里区域实际上比事件位置多出30px,(验证图片原高230px,宽400px)

所以我们在原来的位置加上30px即可。

其他注意事项:

当轨迹x或y间隔小于5时,不会加入轨迹列表

轨迹加密

assemblyCoordData就是轨迹加密函数

我们直接扣下来即可

验证请求

这样,我们使用识别图片的轨迹加密生成en后去请求。

总结

在这个验证码飞速发展的时代,可能我今天的文章,明天就过期了。

所以,不要只一昧的CV,我们要学习新的思路,这样知识才是自己的。

如果文章有什么不足的欢迎各位大佬们补充。

免费评分

参与人数 21吾爱币 +24 热心值 +20 收起 理由
千小秋 + 1 + 1 我很赞同!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
kkyi + 1 + 1 谢谢@Thanks!
Yangzaipython + 1 鼓励转贴优秀软件安全工具和文档!
风动鸣 + 1 + 1 用心讨论,共获提升!
都同学 + 1 + 1 用心讨论,共获提升!
qq1481 + 1 + 1 谢谢@Thanks!
lookerJ + 1 + 1 用心讨论,共获提升!
没有细胞的人 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
eijop252023 + 1 + 1 我很赞同!
xvsc + 1 + 1 谢谢@Thanks!
liuxuming3303 + 1 + 1 谢谢@Thanks!
T4DNA + 1 + 1 用心讨论,共获提升!
fengoto + 1 + 1 热心回复!
ytfh1131 + 1 + 1 谢谢@Thanks!
timeni + 1 + 1 用心讨论,共获提升!
ABCblock + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Issacclark1 + 1 谢谢@Thanks!
prince_cool + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
dooppojie + 1 + 1 我很赞同!
howsk + 2 + 1 鼓励转贴优秀软件安全工具和文档!

查看全部评分

本帖被以下淘专辑推荐:

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

推荐
 楼主| s1lencee 发表于 2024-2-14 18:41 |楼主
443566434 发表于 2024-2-14 17:15
请教一下,有些APP的比如某宝的滑动验证码,手动右滑就可以验证成功,但是用js代码的swipe滑 能右滑过去了 ...

大概率检测轨迹了,你的轨迹不像人滑的

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
443566434 + 1 + 1 我很赞同!

查看全部评分

推荐
wangxd 发表于 2024-2-14 22:23
请教一下,有些APP的比如某宝的滑动验证码,手动右滑就可以验证成功,但是用js代码的swipe滑 能右滑过去了但是验证不给过,这是什么原理呢?
沙发
 楼主| s1lencee 发表于 2024-2-8 01:02 |楼主
3#
海是倒过来的天 发表于 2024-2-8 08:39
凌晨发帖,分析得这么详细,学习了
4#
次谐波 发表于 2024-2-8 08:40
进来学习下。
5#
ztqddj007 发表于 2024-2-8 08:47
牛逼大佬支持一波
6#
lhfcsm 发表于 2024-2-8 10:01
呵呵,正在学习中
7#
序列号001 发表于 2024-2-8 10:48

进来学习下
8#
xuelinghua 发表于 2024-2-10 16:03
进来学习下
9#
Zrzy123456 发表于 2024-2-10 16:11
前排膜拜一下大佬
10#
jflmao 发表于 2024-2-11 07:58
这个帖子怎么消失了几天,8号上午还有,中午就没了
您需要登录后才可以回帖 登录 | 注册[Register]

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

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

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

GMT+8, 2024-2-27 07:00

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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