-
-
[原创]挖洞遇到验证码那些事
-
发表于: 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 ", '
|