最近在给一个app抓包的时候发现App在特定时间会弹出验证码,验证之后会给一个token,需要携带token才能发起能正常请求。
文章源码地址:点击查看
验证码如下:
弹出验证码的Response如下:
完成验证码的Request如下
由此观察到只要将验证码的点击坐标post到完成验证码的接口,就可以获取到token,即现在的目标就是提取坐标。
观察了下这个验证码不是很难,因为它没有图案扭曲,所以还是比较好过的,同时我也想起了以前过滑动验证码的一个方案(同时给出)。
要过验证码,就是将目标图案在背景图片上找到,并且将其像素点找到就可以。
于是我使用Python的OpenCV进行图片的识别。
首先观察发现目标图片都是黑色图案,且背景为透明地址,当我直接使用 cv2.imread(front_image) 来加载图片时,会显示一片漆黑:
即使后来我使用了保留透明通道的加载 cv2.imread(front_path, cv2.IMREAD_UNCHANGED),依旧是一片漆黑。
于是我想可以将透明通道剥离,然后将目标图案透明色设置为白色,那么目标图案就自然显现了,成品如下:
我先要将目标图片的三个图案分割出来,对每一个图案分别找出他的像素位置。
本来想通过颜色精准分割,但有些图案并不是整体粘连的,经过观察发现目标图片的三个图案排列位置都是固定的,所以直接记录出他的坐标进行像素分割:
rectangle_list每一个元素是[x1,y1,x2,y2]
分割后我们将目标图和背景图都转化为灰度,防止颜色碍事:
然后直接进行最佳匹配:
最后发现匹配结果欠佳,总是不能完整的将3个目标图案都准确找到,所以还需要再优化。
继续观察,发现背景图片中的目标图案总是白色的,所以我们放弃使用默认的灰度,转而将背景图片上所有的白色部分保留,其余全部转为黑色,这样不就完全没有杂色了。
为了尽可能保留完整的图案,经过多次RGB颜色的尝试,发现250-255区间可以保留大部分目标图案的白色:
同时为了和背景图片上的黑色色块一致,我再将黑色的目标图案反转为白色:
由于要获取的是点击坐标,所以我们将x1,y1(即左上角坐标)进行+20的偏移,来移动到图案本身上面
经过验证,现在的识别就能正常过点击验证码了。
贴出代码:
滑动验证码与上同理,甚至现在比较常见的一种滑动验证码已经有了通用的代码,如:
这种滑动验证码已经是无脑式 matchTemplate 、minMaxLoc 就可以,非常方便:
贴出代码:
背景图 |
目标图 |
|
|
{
"data"
: {
"errorCode"
:
0
,
"errorMsg"
: "",
"bg"
:
"https://example.com/ENZg076503962.jpg"
,
"front"
:
"https://example.com/ENZg076503963.png"
,
"token"
:
"eyJhbGci..."
,
"width"
:
765
,
"height"
:
396
}
}
{
"data"
: {
"errorCode"
:
0
,
"errorMsg"
: "",
"bg"
:
"https://example.com/ENZg076503962.jpg"
,
"front"
:
"https://example.com/ENZg076503963.png"
,
"token"
:
"eyJhbGci..."
,
"width"
:
765
,
"height"
:
396
}
}
{
"m"
:
"[{\"x\":395,\"y\":256},{\"x\":89,\"y\":273},{\"x\":670,\"y\":140}]"
,
"token"
:
"eyJhbGci..."
}
{
"m"
:
"[{\"x\":395,\"y\":256},{\"x\":89,\"y\":273},{\"x\":670,\"y\":140}]"
,
"token"
:
"eyJhbGci..."
}
rectangle_list
=
[[
9
,
9
,
75
,
75
], [
109
,
9
,
175
,
75
], [
209
,
9
,
275
,
75
]]
for
idx, rectangle
in
enumerate
(rectangle_list, start
=
1
):
cropped_image
=
white_front[rectangle[
1
]:rectangle[
3
], rectangle[
0
]:rectangle[
2
]]
rectangle_list
=
[[
9
,
9
,
75
,
75
], [
109
,
9
,
175
,
75
], [
209
,
9
,
275
,
75
]]
for
idx, rectangle
in
enumerate
(rectangle_list, start
=
1
):
cropped_image
=
white_front[rectangle[
1
]:rectangle[
3
], rectangle[
0
]:rectangle[
2
]]
gray_bg
=
cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
gray_front
=
cv2.cvtColor(front, cv2.COLOR_BGR2GRAY)
gray_bg
=
cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
gray_front
=
cv2.cvtColor(front, cv2.COLOR_BGR2GRAY)
res
=
cv2.matchTemplate(gray_front, gray_bg, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc
=
cv2.minMaxLoc(res)
cv2.putText(bg,
str
(idx), (x, y), cv2.FONT_HERSHEY_SIMPLEX,
0.5
, (
7
,
249
,
151
),
2
)
show(bg)
res
=
cv2.matchTemplate(gray_front, gray_bg, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc
=
cv2.minMaxLoc(res)
cv2.putText(bg,
str
(idx), (x, y), cv2.FONT_HERSHEY_SIMPLEX,
0.5
, (
7
,
249
,
151
),
2
)
show(bg)
gray_bg
=
cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
_, strong_contrast_bg
=
cv2.threshold(gray_bg,
250
,
255
, cv2.THRESH_BINARY)
gray_bg
=
cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
_, strong_contrast_bg
=
cv2.threshold(gray_bg,
250
,
255
, cv2.THRESH_BINARY)
gray_bg
=
cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
_, strong_contrast_bg
=
cv2.threshold(gray_bg,
250
,
255
, cv2.THRESH_BINARY)
res
=
cv2.matchTemplate(
strong_contrast_bg,
cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)),
cv2.TM_CCOEFF_NORMED
)
min_val, max_val, min_loc, max_loc
=
cv2.minMaxLoc(res)
x, y
=
max_loc
x, y
=
x
+
20
, y
+
20
gray_bg
=
cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
_, strong_contrast_bg
=
cv2.threshold(gray_bg,
250
,
255
, cv2.THRESH_BINARY)
res
=
cv2.matchTemplate(
strong_contrast_bg,
cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)),
cv2.TM_CCOEFF_NORMED
)
min_val, max_val, min_loc, max_loc
=
cv2.minMaxLoc(res)
x, y
=
max_loc
x, y
=
x
+
20
, y
+
20
import
logging
import
cv2
import
numpy as np
def
ProcessCaptcha(bg_path:
str
, front_path:
str
):
result
=
[]
bg
=
cv2.imread(bg_path)
front
=
cv2.imread(front_path, cv2.IMREAD_UNCHANGED)
white_front
=
np.ones_like(front)
*
255
alpha_channel
=
front[:, :,
3
]
white_front[:, :,
0
:
3
]
=
cv2.bitwise_and(
white_front[:, :,
0
:
3
],
white_front[:, :,
0
:
3
],
mask
=
cv2.bitwise_not(alpha_channel)
)
rectangle_list
=
[[
9
,
9
,
75
,
75
], [
109
,
9
,
175
,
75
], [
209
,
9
,
275
,
75
]]
for
rectangle
in
rectangle_list:
cropped_image
=
white_front[rectangle[
1
]:rectangle[
3
], rectangle[
0
]:rectangle[
2
]]
gray_bg
=
cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
_, strong_contrast_bg
=
cv2.threshold(gray_bg,
250
,
255
, cv2.THRESH_BINARY)
strong_contrast_front
=
cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY))
res
=
cv2.matchTemplate(
strong_contrast_bg,
strong_contrast_front,
cv2.TM_CCOEFF_NORMED
)
x, y
=
cv2.minMaxLoc(res)[
3
]
result.append((x
+
20
, y
+
20
))
logging.info(f
"Done process: "
+
str
(result).replace(
'\n'
,
' '
))
return
result
import
logging
import
cv2
import
numpy as np
def
ProcessCaptcha(bg_path:
str
, front_path:
str
):
result
=
[]
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)