极验滑块验证码破解与研究(三):滑块缺口识别
声明
原创文章,请勿转载!
本文内容仅限于安全研究,不公开具体源码。维护网络安全,人人有责。
本文关联文章超链接:
- 极验滑块验证码破解与研究(一):AST还原混淆JS
- 极验滑块验证码破解与研究(二):缺口图片还原
- 极验滑块验证码破解与研究(三):滑块缺口识别
- 极验滑块验证码破解与研究(四):滑块轨迹构造
- 极验滑块验证码破解与研究(五):请求分析及加密参数破解
一、环境安装
1. 第三方库安装
pip install Pillow
pip install numpy
pip install opencv-python
二、滑块缺口识别
滑块缺口识别功能的实现还是比较简单的,小编冗余一些工具函数,方便不同的场景使用。
1. 准备工作
首先我们需要准备两张图片:1. 带缺口的背景图;2. 与之对应的接口图。
缺口图片还原方式请看 极验滑块验证码破解与研究(二):缺口图片还原
2. 工具函数说明
这些工具函数主要是图片格式的相互转换功能。大家知道PIL是以RGB格式打开的图片<type ‘PIL.JpegImagePlugin.JpegImageFile’>,cv2是以BGR格式打开的图片<type ‘np.ndarray’>。我们从网页下载图片通常是bytes格式。因此,我们需要准备一些图片格式相互转换的工具函数,方便测试和上线。
# -*- coding: utf-8 -*-
from pathlib import Path
import PIL
import cv2
import numpy as np
def imshow(img, winname='test', delay=0):
"""cv2展示图片"""
cv2.imshow(winname, img)
cv2.waitKey(delay)
cv2.destroyAllWindows()
def pil_to_cv2(img):
"""
pil转cv2图片
:param img: pil图像, <type 'PIL.JpegImagePlugin.JpegImageFile'>
:return: cv2图像, <type 'numpy.ndarray'>
"""
img = cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
return img
def bytes_to_cv2(img):
"""
二进制图片转cv2
:param img: 二进制图片数据, <type 'bytes'>
:return: cv2图像, <type 'numpy.ndarray'>
"""
# 将图片字节码bytes, 转换成一维的numpy数组到缓存中
img_buffer_np = np.frombuffer(img, dtype=np.uint8)
# 从指定的内存缓存中读取一维numpy数据, 并把数据转换(解码)成图像矩阵格式
img_np = cv2.imdecode(img_buffer_np, 1)
return img_np
def cv2_open(img, flag=None):
"""
统一输出图片格式为cv2图像, <type 'numpy.ndarray'>
:param img: <type 'bytes'/'numpy.ndarray'/'str'/'Path'/'PIL.JpegImagePlugin.JpegImageFile'>
:param flag: 颜色空间转换类型, default: None
eg: cv2.COLOR_BGR2GRAY(灰度图)
:return: cv2图像, <numpy.ndarray>
"""
if isinstance(img, bytes):
img = bytes_to_cv2(img)
elif isinstance(img, (str, Path)):
img = cv2.imread(str(img))
elif isinstance(img, np.ndarray):
img = img
elif isinstance(img, PIL.Image):
img = pil_to_cv2(img)
else:
raise ValueError(f'输入的图片类型无法解析: {type(img)}')
if flag is not None:
img = cv2.cvtColor(img, flag)
return img
3. 接口识别原理讲解
首先使用上面介绍的工具函数,以灰度图方式读取背景图和缺口图
bg_gray = cv2_open(bg, flag=cv2.COLOR_BGR2GRAY)
tp_gray = cv2_open(tp, flag=cv2.COLOR_BGR2GRAY)
接下来,使用cv2的边缘检测功能,检测背景图和缺口图的边缘,阀值大小可以自己调整哦。
tp_gray = cv2.Canny(tp_gray, 255, 255)
bg_gray = cv2.Canny(bg_gray, 255, 255)
使用cv2的目标检测函数,得到相似度最高的坐标,坐标的x值就是滑动验证码需要滑动的距离
# 目标匹配
result = cv2.matchTemplate(bg_gray, tp_gray, cv2.TM_CCOEFF_NORMED)
# 解析匹配结果
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
distance = max_loc[0]
4. 缺口识别完整代码
# -*- coding: utf-8 -*-
from pathlib import Path
import PIL
import cv2
import numpy as np
def imshow(img, winname='test', delay=0):
"""cv2展示图片"""
cv2.imshow(winname, img)
cv2.waitKey(delay)
cv2.destroyAllWindows()
def pil_to_cv2(img):
"""
pil转cv2图片
:param img: pil图像, <type 'PIL.JpegImagePlugin.JpegImageFile'>
:return: cv2图像, <type 'numpy.ndarray'>
"""
img = cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
return img
def bytes_to_cv2(img):
"""
二进制图片转cv2
:param img: 二进制图片数据, <type 'bytes'>
:return: cv2图像, <type 'numpy.ndarray'>
"""
# 将图片字节码bytes, 转换成一维的numpy数组到缓存中
img_buffer_np = np.frombuffer(img, dtype=np.uint8)
# 从指定的内存缓存中读取一维numpy数据, 并把数据转换(解码)成图像矩阵格式
img_np = cv2.imdecode(img_buffer_np, 1)
return img_np
def cv2_open(img, flag=None):
"""
统一输出图片格式为cv2图像, <type 'numpy.ndarray'>
:param img: <type 'bytes'/'numpy.ndarray'/'str'/'Path'/'PIL.JpegImagePlugin.JpegImageFile'>
:param flag: 颜色空间转换类型, default: None
eg: cv2.COLOR_BGR2GRAY(灰度图)
:return: cv2图像, <numpy.ndarray>
"""
if isinstance(img, bytes):
img = bytes_to_cv2(img)
elif isinstance(img, (str, Path)):
img = cv2.imread(str(img))
elif isinstance(img, np.ndarray):
img = img
elif isinstance(img, PIL.Image):
img = pil_to_cv2(img)
else:
raise ValueError(f'输入的图片类型无法解析: {type(img)}')
if flag is not None:
img = cv2.cvtColor(img, flag)
return img
def get_distance(bg, tp, im_show=False, save_path=None):
"""
:param bg: 背景图路径或Path对象或图片二进制
eg: 'assets/bg.jpg'
Path('assets/bg.jpg')
:param tp: 缺口图路径或Path对象或图片二进制
eg: 'assets/tp.jpg'
Path('assets/tp.jpg')
:param im_show: 是否显示结果, <type 'bool'>; default: False
:param save_path: 保存路径, <type 'str'/'Path'>; default: None
:return: 缺口位置
"""
# 读取图片
bg_gray = cv2_open(bg, flag=cv2.COLOR_BGR2GRAY)
tp_gray = cv2_open(tp, flag=cv2.COLOR_BGR2GRAY)
# 边缘检测
tp_gray = cv2.Canny(tp_gray, 255, 255)
bg_gray = cv2.Canny(bg_gray, 255, 255)
# 目标匹配
result = cv2.matchTemplate(bg_gray, tp_gray, cv2.TM_CCOEFF_NORMED)
# 解析匹配结果
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
distance = max_loc[0]
if save_path or im_show:
# 需要绘制的方框高度和宽度
tp_height, tp_width = tp_gray.shape[:2]
# 矩形左上角点位置
x, y = max_loc
# 矩形右下角点位置
_x, _y = x + tp_width, y + tp_height
# 绘制矩形
bg_img = cv2_open(bg)
cv2.rectangle(bg_img, (x, y), (_x, _y), (0, 0, 255), 2)
# 保存缺口识别结果到背景图
if save_path:
save_path = Path(save_path).resolve()
save_path = save_path.parent / f"{save_path.stem}.{distance}{save_path.suffix}"
save_path = save_path.__str__()
cv2.imwrite(save_path, bg_img)
# 显示缺口识别结果
if im_show:
imshow(bg_img)
return distance
if __name__ == '__main__':
d = get_distance(
bg='assets/bg.jpg',
tp='assets/tp.png',
im_show=True,
save_path='assets/bg.jpg'
)
print(d)