首页
社区
课程
招聘
[原创]挖洞遇到验证码那些事
发表于: 2023-8-11 22:20 3900

[原创]挖洞遇到验证码那些事

2023-8-11 22:20
3900

前言

平台:aHR0cHM6Ly93d3cudnVsYm94LmNvbS8=公益SRC

厂商:zay7qMuz

链接:aHR0cHM6Ly91cGFzcy4xMGpxa2EuY29tLmNuL2xvZ2lu

文章搭配视频地址:挖洞遇到验证码那些事

正文

输入手机号码与账号密码登录的接口都有滑块。区别在于账号密码登录的时候有个加盐密码。流程比手机号码的接口复杂一下,为了省时间。此文章便是根据手机号码的接口去写的,毕竟咱的目的是过滑块嘛。

发包逻辑分析

每次刷新验证码,都会发四个数据包下来。第一个数据包用于请求图片。图片链接的拼接就是urlParams加上imgs的值。

第三个数据包是背景图

第四个数据包是缺口图片

正确拼接验证码,短信验证码发送成功

captcha_signature在刷新验证码发下来的数据包里面的sign参数。

captcha_phrase和captcha_ticket在getTicket接口从服务器返回回来的,captcha_phrase是缺口,如果缺口对齐失败的话captcha_ticket是获取不到的。所以得先把getTicket接口的参数补齐拿到ticket。

getTicket的前五个参数直接从getPreHandle接口返回的urlParams获取。

phrase直接用搜索大法就能搜到关键位置

1
phrase: (x) + ";" + inity + ";" + opt.width + ";" + opt.height

x是缺口识别的x轴,opt.width和opt.height是固定的值

inity也是直接在js文件一搜就能拿到他的关键代码。data.data.inity在getPreHandle接口里面的inity。这段代码就是取那个inity相除相乘再拼接到phrase里头

1
inity = (data.data.inity / 195 * opt.height);

x直接就是缺口识别然后减去15(因为图片实际大小跟他在网页上面的大小不一样),懒得缩小了。因为通过训练相同的图片跟缺口其x就相差15,直接减更方便(懒人做法)

图片缺口识别代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def detect_captcha_gap(bg, tp): 
    ''' 
    bg: 背景图片 
    tp: 缺口图片 
    return:空缺距背景图左边的距离 
    '''    # 读取背景图片和缺口图片 
    bg_img = cv2.imread(bg) # 背景图片 
    tp_img = cv2.imread(tp) # 缺口图片 
    # 识别图片边缘 
    bg_edge = cv2.Canny(bg_img, 100, 200
    tp_edge = cv2.Canny(tp_img, 100, 200
    # 转换图片格式 
    bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB) 
    tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB) 
    # cv2.imwrite("bg_style.png",bg_pic) # 保存背景轮廓提取 
    # cv2.imwrite("slide_style.png",tp_pic) # 保存滑块背景提取 
    # 缺口匹配 
    res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED) 
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 寻找最优匹配 
    th, tw = tp_pic.shape[:2
    tl = max_loc  # 左上角点的坐标 
    # 返回缺口的左上角X坐标 
    br = (tl[0] + tw, tl[1] + th)  # 右下角点的坐标 
    cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2# 绘制矩形 
    cv2.imwrite("result_new.png", bg_img)  # 保存在本地 
    # 返回缺口的左上角X坐标 
    return tl[0
x = detect_captcha_gap("bg.jpg","tp.jpg")-15

mobile加密就是一个rsa加密,没有任何混淆跟难点。

验证码校验接口还有个hexin-v参数

hexin-v硬扣

校验验证码的数据包里面有个加密的header参数hexin-v,他跟cookie的v是一模一样的。有两种方法可以找到关键加密位置,第一种是headers hook,第二种是cookie hook。

cookie hook代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function () {
    'use strict';
    var cookieTemp = "";
    Object.defineProperty(document, 'cookie', {
        set: function (val) {
            console.log('Hook捕获到cookie设置->', val);
            if (val.indexOf('v') != -1) {
                debugger;
            }
            cookieTemp = val;
            return val;
        },
        get: function () {
            return cookieTemp;
        }
    });
})();

在控制台输入hook的代码

断点断了下来。回溯堆栈找到关键代码加密的地方

w函数便是设置cookie的函数,r就是v加密生成返回的值。那么qn.update()就是v生成的函数了。把断点下到qn.update()那一行然后刷新。由于这个网站的js文件是动态生成的,所以需要重新进入新的js文件在qn.update()那一行重新下个断点刷新。

断点断下之后进入qn.update()看看里面写了啥

进入到qn.update()函数内部可以看出他返回了另外一个函数的返回值。

把断点断到R函数这一行进入到R函数内部。

R函数往E对象里面压入一堆值。找找E在哪里生成的。

E对象是在b函数里面被赋值的,但因作用域的原因,他并不是在这里声明的,E声明的地方还得往上面去找。

在945行找到E声明的地方,同时也发现了这一段大的函数是一个自执行的函数。

开始扣代码,就只扣需要的代码,R()是v加密后的值,R函数里面有E对象,E对象是在b函数里面被实例化的。b函数也扣下来,b函数里面调用了C函数。C函数也一并扣下来。因为E对象是全局作用域,其声明的代码在几个函数的外面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var o = r[40], i = r[37], u = e[26], c = e[121], s = parseInt(Kn(n[80], e[122], t[135]), t[67]), f = n[94], v = r[59], l = e[74], p = n[53], d = parseInt(t[29], t[62]), h = parseInt(n[68], n[134]), g = n[135], m = parseInt(e[123], n[51]), w = parseInt(n[136], e[58]), I = parseInt(e[124], t[136]), _ = parseInt(e[125], e[79]), y = parseInt(n[137], n[134]), A = parseInt(e[126], t[67]), E; 
    function C() { 
        var r = Wn.getCookie(On) || $n.get(Pn); 
        if (r && r[t[59]] == parseInt(n[138], t[62])) { 
            var e = Qn.decode(r); 
            if (e && (E.decodeBuffer(e), 
            E[o] != n[29])) 
                return 
        
        E[o] = Gn.random() 
    
    function b() { 
        var t = e[31
          , a = parseInt(e[127], e[26]) 
          , o = r[52
          , u = parseInt(e[128], e[26]); 
        E = new Un([u, u, u, u, t, t, t, o, a, a, a, a, a, a, a, u, a, t]), 
        E[i] = Gn.serverTimeNow(), 
        C(), 
        E[_] = Dn, 
        E[A] = xn, 
        E[y] = n[29], 
        E[c] = Gn.strhash(navigator.userAgent), 
        E[w] = Yn.getBrowserFeature(), 
        E[s] = Yn.getPlatform(), 
        E[f] = Yn.getBrowserIndex(), 
        E[v] = Yn.getPluginNum() 
    
    function R() { 
        var n = Y; 
        E[y]++
        E[i] = Gn.serverTimeNow(), 
        E[u] = Gn.timeNow(), 
        E[_] = Dn, 
        E[l] = Jn.getMouseMove(), 
        n = q, 
        E[p] = Jn.getMouseClick(), 
        E[d] = Jn.getMouseWhell(), 
        E[h] = Jn.getKeyDown(), 
        E[g] = Jn.getClickPos().x, 
        E[m] = Jn.getClickPos().y; 
        var t = E.toBuffer(); 
        return Qn.encode(t) 
    
 
    console.log(R())

代码运行报错,r未定义。那么e,t,n是否也是未定义。在浏览器运行一下看看是什么呢

在控制台打印r,e,t,n。发现他们都是大数组。直接扣下来就行了

1
2
3
4
var n=["xi", "n-", "v", "ai", ".", "get", "kie", "; d", "\x1c)Aw\x1b", "3", "ced", "strT", "rigi", "]V8a", "w", "l", Array, "Map", Function, 58, "ttpR", "Feb 2050", "fZ,\x19", "\x15\x18;", "", 9527, 256, String, "head", 0, 1, "al", "Coo", 92, 81, !1, window, "[X9^QR", "TR8", "\u250c\u252c", "localStorage", "d", "___", "documentElement", "addBehavior", '

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册