1.redis前言
763 2023-04-03 03:30:11
乱序拼图验证是一种较少见的验证码防御
找一个市面比较普及的顶像乱序拼图进行验证防御能力
用户体验
selenium
python
+opencv
!pip install setuptools!pip install selenium!pip install numpy Matplotlib!pip install opencv-python
找到对应浏览器版本+系统平台
的driver
macOS
建议存放到 /usr/local/bin
!wget https://npm.taobao.org/mirrors/chromedriver/95.0.4638.69/chromedriver_mac64.zip
引入依赖库webdriver
打开官方网站的产品演示页面
import osimport cv2import timeimport urllib.requestimport matplotlib.pyplot as pltimport numpy as npfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWait
创建下载样本的代码
# 采集代码class CrackPuzzleCaptcha(): # 初始化 webdriver def init(self): self.url = 'https://www.dingxiang-inc.com/business/captcha' chrome_options = webdriver.ChromeOptions() # chrome_options.add_argument("--start-maximized") chrome_options.add_experimental_option("excludeSwitches", ["ignore-certificate-errors","enable-automation"]) # 设置为开发者模式 path = r'/usr/local/bin/chromedriver' #macOS# path = r'D:\Anaconda3\chromedriver.exe' #windows self.browser = webdriver.Chrome(executable_path=path,chrome_options=chrome_options) #设置显示等待时间 self.wait = WebDriverWait(self.browser, 20) self.browser.get(self.url) # 打开验证码 demo 页面,并强制元素在浏览器可视区域 def openTest(self): time.sleep(1) self.browser.execute_script('setTimeout(function(){document.querySelector("body > div.wrapper-main > div.wrapper.wrapper-content > div > div.captcha-intro > div.captcha-intro-header > div > div > ul > li.item-8").click();},0)') self.browser.execute_script('setTimeout(function(){document.querySelector("body > div.wrapper-main > div.wrapper.wrapper-content > div > div.captcha-intro > div.captcha-intro-body > div > div.captcha-intro-demo").scrollIntoView();},0)') time.sleep(1) # 找到原图,webp 格式,直接下载保存 def download(self): onebtn = self.browser.find_element_by_css_selector('#dx_captcha_oneclick_bar-logo_2 > span') ActionChains(self.browser).move_to_element(onebtn).perform() time.sleep(1) #下载 webp img_url = self.browser.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-top-left_3 > img').get_attribute("src") img_address = "test.webp" # 样本文件 response = urllib.request.urlopen(img_url) img = response.read() with open(img_address, 'wb') as f: f.write(img) print(' 已保存 ', img_address) return self.browser def crack(self): pass
开始采集
crack = CrackPuzzleCaptcha()crack.init()crack.openTest()
browser2 = crack.download()
已保存 test.webp
已经乱序
的状态切割并编号
1
次换位即可2x2
[1,2,3,4]
定义辅助函数
# 显示图形def show_images(images: list , title = '') -> None: if title!='': print(title) n: int = len(images) f = plt.figure() for i in range(n): f.add_subplot(1, n, i + 1) plt.imshow(images[i]) plt.show(block=True) # 获取图像的基本信息def getSize(p): sum_rows = p.shape[0] sum_cols = p.shape[1] channels = p.shape[2] return sum_rows,sum_cols,channels
# 输入样本file = 'test.webp'img = cv2.imread(file)sum_rows,sum_cols,channels = getSize(img)part_rows,part_cols = round(sum_rows/2),round(sum_cols/2)print(' 样本图 高度、宽度、通道 ',sum_rows,sum_cols,channels)print(' 四图切分,求原图中心位置 ',part_rows,part_cols)part1 = img[0:part_rows, 0:part_cols]part2 = img[0:part_rows, part_cols:sum_cols]part3 = img[part_rows:sum_rows, 0:part_cols]part4 = img[part_rows:sum_rows, part_cols:sum_cols]print(' 切割为 4 个小块的 W/H/C 信息,并四图编号:左上=1,右上=2,左下=3,右下=4\n',getSize(part1),getSize(part2),getSize(part3),getSize(part4))show_images([img],' 原图 ')show_images([part1,part2],' 切割图 ')show_images([part3,part4])
样本图 高度、宽度、通道 150 300 3四图切分,求原图中心位置 75 150切割为 4 个小块的 W/H/C 信息,并四图编号:左上=1,右上=2,左下=3,右下=4(75, 150, 3) (75, 150, 3) (75, 150, 3) (75, 150, 3)
原图
切割图
完成切割后匹配最佳结果
# 拼接函数def merge(sum_rows,sum_cols,channels,p1,p2,p3,p4): final_matrix = np.zeros((sum_rows, sum_cols,channels), np.uint8) part_rows,part_cols = round(sum_rows/2),round(sum_cols/2) final_matrix[0:part_rows, 0:part_cols] = p1 final_matrix[0:part_rows, part_cols:sum_cols] = p2 final_matrix[part_rows:sum_rows, 0:part_cols] = p3 final_matrix[part_rows:sum_rows, part_cols:sum_cols] = p4 return final_matrix
从编号上来看[1,2,3,4]
还原成 [4,2,3,1]
就是正确的图
# 还原图 f = merge(sum_rows,sum_cols,channels,part4,part2,part3,part1)show_images([f],' 还原图 [4,2,3,1]')
还原图 [4,2,3,1]
已知 python 实现排列组合非常方便
import itertools# 对应拼图的 4 个块的编号puzzle_list = [ "1: 左上 ","2: 右下 ", "3: 左下 ","4: 右下 "]result = itertools.permutations(puzzle_list,4)cnt=0for x in result: cnt+=1 print(x)print(' 共 ',cnt,' 种组合 ')
('1: 左上 ', '2: 右下 ', '3: 左下 ', '4: 右下 ')('1: 左上 ', '2: 右下 ', '4: 右下 ', '3: 左下 ')('1: 左上 ', '3: 左下 ', '2: 右下 ', '4: 右下 ')('1: 左上 ', '3: 左下 ', '4: 右下 ', '2: 右下 ')('1: 左上 ', '4: 右下 ', '2: 右下 ', '3: 左下 ')('1: 左上 ', '4: 右下 ', '3: 左下 ', '2: 右下 ')('2: 右下 ', '1: 左上 ', '3: 左下 ', '4: 右下 ')('2: 右下 ', '1: 左上 ', '4: 右下 ', '3: 左下 ')('2: 右下 ', '3: 左下 ', '1: 左上 ', '4: 右下 ')('2: 右下 ', '3: 左下 ', '4: 右下 ', '1: 左上 ')('2: 右下 ', '4: 右下 ', '1: 左上 ', '3: 左下 ')('2: 右下 ', '4: 右下 ', '3: 左下 ', '1: 左上 ')('3: 左下 ', '1: 左上 ', '2: 右下 ', '4: 右下 ')('3: 左下 ', '1: 左上 ', '4: 右下 ', '2: 右下 ')('3: 左下 ', '2: 右下 ', '1: 左上 ', '4: 右下 ')('3: 左下 ', '2: 右下 ', '4: 右下 ', '1: 左上 ')('3: 左下 ', '4: 右下 ', '1: 左上 ', '2: 右下 ')('3: 左下 ', '4: 右下 ', '2: 右下 ', '1: 左上 ')('4: 右下 ', '1: 左上 ', '2: 右下 ', '3: 左下 ')('4: 右下 ', '1: 左上 ', '3: 左下 ', '2: 右下 ')('4: 右下 ', '2: 右下 ', '1: 左上 ', '3: 左下 ')('4: 右下 ', '2: 右下 ', '3: 左下 ', '1: 左上 ')('4: 右下 ', '3: 左下 ', '1: 左上 ', '2: 右下 ')('4: 右下 ', '3: 左下 ', '2: 右下 ', '1: 左上 ')共 24 种组合
采用 merge 函数组合还原
后灰度图
并提取轮廓
# 还原图 f = merge(sum_rows,sum_cols,channels,part1,part2,part3,part4)show_images([f],' 还原图 [1,2,3,4]')# 灰度gray = cv2.cvtColor(f, cv2.COLOR_BGRA2GRAY)show_images([gray],' 灰度 ')# 提取轮廓edges = cv2.Canny(gray, 35, 80, apertureSize=3)show_images([edges],' 提取轮廓 ')
还原图
灰度
提取轮廓
再测试一种新的组合
f = merge(sum_rows,sum_cols,channels,part1,part3,part2,part4)gray = cv2.cvtColor(f, cv2.COLOR_BGRA2GRAY)edges = cv2.Canny(gray, 35, 80, apertureSize=3)show_images([edges],' 提取轮廓 ')f = merge(sum_rows,sum_cols,channels,part1,part2,part3,part4)gray = cv2.cvtColor(f, cv2.COLOR_BGRA2GRAY)edges = cv2.Canny(gray, 35, 80, apertureSize=3)show_images([edges],' 提取轮廓 ')# 正确的f = merge(sum_rows,sum_cols,channels,part4,part2,part3,part1)gray = cv2.cvtColor(f, cv2.COLOR_BGRA2GRAY)edges = cv2.Canny(gray, 35, 80, apertureSize=3)show_images([edges],' 正确的-提取轮廓 ')
提取轮廓
提取轮廓
正确的-提取轮廓
通过提取轮廓明显的线条
至少存在一条
线段位置或长度及线条数量可以决定正确率,需要多调整参数并筛选
这是因为原图有明显的过渡色
容易
f = merge(sum_rows,sum_cols,channels,part1,part2,part3,part4)show_images([f],' 背景渐变色 ')show_images([part3,part2,part1,part4],' 切割后 ')f = merge(sum_rows,sum_cols,channels,part1,part2,part3,part4)lf = f.copy()cv2.line(lf, (0, 75), (300, 75), (0, 0, 255), 2)cv2.line(lf, (150, 0), (150, 150), (0, 0, 255), 2)show_images([lf],' 乱序,渐变色成为了 ‘十字’ 特征线 ')
背景渐变色
切割后
乱序
特征已知后检测
十字架
的色差直线提取
f = merge(sum_rows,sum_cols,channels,part1,part2,part3,part4)gray = cv2.cvtColor(f, cv2.COLOR_BGRA2GRAY)edges = cv2.Canny(gray, 35, 80, apertureSize=3)show_images([edges],' 提取轮廓 ')lines = cv2.HoughLinesP(edges,0.01,np.pi/360,60,minLineLength=50,maxLineGap=10)if lines is None: print(' 没找到线条 ')else: lf = f.copy() for line in lines: x1, y1, x2, y2 = line[0] cv2.line(lf, (x1, y1), (x2, y2), (0, 0, 255), 2) show_images([lf])
提取轮廓
尝试正确的组合 [4,2,3,1]
f = merge(sum_rows,sum_cols,channels,part4,part2,part3,part1)gray = cv2.cvtColor(f, cv2.COLOR_BGRA2GRAY)edges = cv2.Canny(gray, 35, 80, apertureSize=3)show_images([edges],' 提取轮廓 ')lines = cv2.HoughLinesP(edges,0.01,np.pi/360,60,minLineLength=50,maxLineGap=10)if lines is None: print(' 没找到线条 ')else: lf = f.copy() for line in lines: x1, y1, x2, y2 = line[0] cv2.line(lf, (x1, y1), (x2, y2), (0, 0, 255), 2) show_images([lf])
提取轮廓
没找到线条
import itertoolsprint(' 原图顺序 ')print(1,2)print(3,4)show_images([img])# 按编号,将切割的图放入 list 做排列组合list1 = [ [1,part1], [2,part2], [3,part3], [4,part4]]result = itertools.permutations(list1,4)idx =1finded = FalsefinalResult = []for x in result: # 排列组合合并图像 f = merge(sum_rows,sum_cols,channels,x[0][1],x[1][1],x[2][1],x[3][1]) # 图像特征提取 gray = cv2.cvtColor(f, cv2.COLOR_BGRA2GRAY) edges = cv2.Canny(gray, 35, 80, apertureSize=3) # 直线匹配 lines = cv2.HoughLinesP(edges,0.01,np.pi/360,60,minLineLength=50,maxLineGap=10) if lines is None: print(' 还原图像 ') show_images([f]) show_images([gray]) show_images([edges]) print(' 正确顺序 ') print(x[0][0],x[1][0]) print(x[2][0],x[3][0]) print(' 完成!!') finded = True finalResult =[x[0][0],x[1][0],x[2][0],x[3][0]] #获取最终排列正确的结果 break else: print(idx, ' 排列:' , x[0][0],x[1][0],x[2][0],x[3][0] , ' 线:', len(lines)) lf = f.copy() for line in lines: x1, y1, x2, y2 = line[0] cv2.line(lf, (x1, y1), (x2, y2), (0, 0, 255), 2)# show_images([lf]) pass idx+=1print(' 测试次数 ',idx,' 最终状态 ',finded,finalResult)
原图顺序1 23 4
1 排列: 1 2 3 4 线: 42 排列: 1 2 4 3 线: 53 排列: 1 3 2 4 线: 44 排列: 1 3 4 2 线: 25 排列: 1 4 2 3 线: 36 排列: 1 4 3 2 线: 47 排列: 2 1 3 4 线: 38 排列: 2 1 4 3 线: 59 排列: 2 3 1 4 线: 310 排列: 2 3 4 1 线: 311 排列: 2 4 1 3 线: 112 排列: 2 4 3 1 线: 113 排列: 3 1 2 4 线: 214 排列: 3 1 4 2 线: 215 排列: 3 2 1 4 线: 316 排列: 3 2 4 1 线: 317 排列: 3 4 1 2 线: 518 排列: 3 4 2 1 线: 319 排列: 4 1 2 3 线: 420 排列: 4 1 3 2 线: 321 排列: 4 2 1 3 线: 2
还原图像
正确顺序4 23 1
完成!
测试次数 22 最终状态 True [4, 2, 3, 1]
再看看如何这种拼图交换位置
的组合有
list1 = [1,2,3,4]result = itertools.permutations(list1,2)idx=0for x in result: idx+=1 print(idx,x)
1 (1, 2)2 (1, 3)3 (1, 4)4 (2, 1)5 (2, 3)6 (2, 4)7 (3, 1)8 (3, 2)9 (3, 4)10 (4, 1)11 (4, 2)12 (4, 3)
#交换函数def change_check(a,b): diffs = [] if len(a)!=len(b): return diffs for i in range(len(a)): if a[i]!=b[i]: diffs.append(b[i]) return diffsab = change_check([1,2,3,4],finalResult)print(' 原始 ',[1,2,3,4])print(' 最终 ',finalResult)print(' 要交换的位置 ',ab)
原始 [1, 2, 3, 4]最终 [4, 2, 3, 1]要交换的位置 [4, 1]
将交换的位置
小图中心
的偏移坐标
查表法
#大图尺寸pwidth = 150 pheight = 75 #小图 xy 中心点 = 大图 wh 1/4px = round(pwidth/2)py = round(pheight/2)#创建坐标表offset_points = [ [px,py],[px+pwidth,py], [px,py+pheight],[px+pwidth,py+pheight]]print(offset_points)print(ab)#通过结果作为索引,拿到坐标表索引的坐标drag_start = offset_points[ ab[0] -1 ]drag_end = offset_points[ ab[1] -1 ]print(' 起点偏移坐标 ',drag_start,' 终点偏移坐标 ',drag_end)
[[75, 38], [225, 38], [75, 113], [225, 113]][4, 1]起点偏移坐标 [225, 113] 终点偏移坐标 [75, 38]
至此move_to_element
方法dom-a 到 dom-b
位置
# 模拟聚焦按钮,让拼图显示出来onebtn = browser2.find_element_by_css_selector('#dx_captcha_oneclick_bar-logo_2 > span')ActionChains(browser2).move_to_element(onebtn).perform() time.sleep(1)
获取最终结果
ab = change_check([1,2,3,4],finalResult)print(ab)
[4, 1]
找到网页拼图的
d1 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-top-left_3 > div')d2 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-top-right_3 > div')d3 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-bottom-left_3 > div')d4 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-bottom-right_3 > div')drag_elements = [d1,d2,d3,d4]
<ipython-input-22-61fb3f895e04>:1: DeprecationWarning: find_element_by_* commands are deprecated. Please use find_element() insteadd1 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-top-left_3 > div')<ipython-input-22-61fb3f895e04>:2: DeprecationWarning: find_element_by_* commands are deprecated. Please use find_element() insteadd2 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-top-right_3 > div')<ipython-input-22-61fb3f895e04>:3: DeprecationWarning: find_element_by_* commands are deprecated. Please use find_element() insteadd3 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-bottom-left_3 > div')<ipython-input-22-61fb3f895e04>:4: DeprecationWarning: find_element_by_* commands are deprecated. Please use find_element() insteadd4 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-bottom-right_3 > div')
找出要拖动的
drag_start = drag_elements[ ab[0] -1 ]drag_end = drag_elements[ ab[1] -1 ]print('drag_start',drag_start, 'drag_end',drag_end)
drag_start <selenium.webdriver.remote.webelement.WebElement (session="1d7d691bd509cd03cd8b1483da2056ea", element="8439005e-eb70-4b02-856e-eebbe2526d6d")> drag_end <selenium.webdriver.remote.webelement.WebElement (session="1d7d691bd509cd03cd8b1483da2056ea", element="f9239df5-9aa3-43ae-a6af-afacf81eb670")>
ActionChains(browser2).drag_and_drop(drag_start,drag_end).perform()# browser2.close()
简单拖一下
边学边做错误
之处敬请指出
项目地址