Как взломать неверный код головоломки

OpenCV
Как взломать неверный код головоломки

Распознавание и восстановление неправильной проверки головоломки - головоломка-капча

Введение

Проверка головоломки не по порядку - редкая защита кода проверки.На рынке используется больше ползунков.Многие из них были отлично побеждены.Все они усердно работали над траекторией поведения.Исследуется только восстановление головоломки.

2.png

Найдите популярную головоломку с нарушением порядка для проверки, она известна как防御能力4星,用户体验3星, В результате исследований было обнаружено, что его степень восстановления довольно высока, а идея очень проста.Ниже приводится пошаговое объяснение процесса восстановления.

x.gif

2. Подготовка окружающей среды

1. Зависимость

  • моделирование приобретенияselenium

  • сопоставление функцийpython+opencv

2. Среда установки

!pip install setuptools
!pip install selenium
!pip install numpy Matplotlib
!pip install opencv-python

3.Chromedriver скачать

найти корреспонденцию浏览器版本+系统平台изdriverЗадний,macOSРекомендуется хранить в/usr/local/bin

!wget https://npm.taobao.org/mirrors/chromedriver/95.0.4638.69/chromedriver_mac64.zip

3. Сбор образцов

Внедрить зависимые библиотеки, использоватьwebdriverОткройте демонстрационную страницу продукта на официальном сайте.

1.png

import os
import cv2
import time
import urllib.request
import matplotlib.pyplot as plt
import numpy as np

from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

Создайте код для загрузки образца, основной процесс — открыть демо-страницу официального сайта, сделать снимок экрана и сохранить его.

3.png

# 采集代码
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.png" # 样本文件
        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()

сохраненный тест.png

4. Результаты исследования

  • Ключ 1: Исходное изображение отображаемой головоломки已经乱序статус

  • Ключ 2: Исходное изображение представляет собой целое, затем получите исходное изображение.切割并编号, который дает результаты, совместимые с процессом головоломки

  • Ключ 3: головоломку просто нужно решить1Его можно заменить каждый раз,2x2матрица, которая может быть[1,2,3,4]Располагайте и комбинируйте, чтобы получить все результаты сварки

5. Процесс анализа

1. Вспомогательная функция

Определите вспомогательные функции для быстрого доступа к параметрам

# 显示图形
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

2. Резка изображения

# 输入样本
file = 'test.png'
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)

исходное изображение

output_18_1.png

схема резки

output_18_3.png

output_18_4.png

После завершения нарезки также необходимо рекомбинировать и объединить 4 изображения для匹配最佳结果

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]

output_23_1.png

4. Упорядочивайте и комбинируйте

Известно, что на питоне очень удобно реализовывать перестановку и комбинирование, а тестовый код выглядит следующим образом

import itertools

# 对应拼图的4个块的编号
puzzle_list = [
    "1:左上","2:右下",
    "3:左下","4:右下"
]

result = itertools.permutations(puzzle_list,4)
cnt=0
for 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 种组合

5. Извлечение признаков

С помощью функции слияния обрабатывается вырезанное маленькое изображение组合还原После этого преобразовать в灰度图и提取轮廓.

# 还原图 
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],'提取轮廓')

Восстановить карту[1,2,3,4]

output_27_1.png

оттенки серого

output_27_3.png

Извлечь контуры

output_27_5.png

Затем проверьте новую комбинацию, посмотрите на элементы контура [1, 3, 2, 4] и исходные элементы контура [4, 2, 3, 1].

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],'正确的-提取轮廓')

Извлечь контуры

output_29_1.png

Извлечь контуры

output_29_3.png

правильно - извлечь контуры

output_29_5.png

Извлекая контур, вы можете увидеть результат сшивания明显的线条, неверный график至少存在一条x轴或y轴的线, а удачного сплайсинга в принципе нет(线段位置或长度及线条数量可以决定正确率,需要多调整参数并筛选).

Это потому, что исходное изображение имеет очевидные过渡色, он предназначен для взаимодействия с пользователем, поэтому, когда люди его используют, они могут容易' и найти правильное место головоломки.

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],'乱序,渐变色成为了‘十字’特征线')

градиент фона

output_32_1.png

после резки

output_32_3.png

Не по порядку цвет градиента превратился в «перекрестную» характерную линию.

output_32_5.png

6. Сопоставление функций

Теперь, когда особенности известны, осталось только выполнить检测, вы можете вычислить x/2,y/2十字架Хроматическая аберрация opencv также может быть использована直线提取, тестовый код выглядит следующим образом:

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])

Извлечь контуры

output_34_1.png

output_34_2.png

попробуйте правильную комбинацию [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])

Извлечь контуры

output_36_1.png

没找到线条

7. Процесс сопоставления

import itertools

print('原图顺序')
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 =1
finded = False
finalResult = []
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+=1

print('测试次数',idx,'最终状态',finded,finalResult)
原图顺序
1 2
3 4

output_38_1.png

1 排列: 1 2 3 4 线: 4
2 排列: 1 2 4 3 线: 5
3 排列: 1 3 2 4 线: 4
4 排列: 1 3 4 2 线: 2
5 排列: 1 4 2 3 线: 3
6 排列: 1 4 3 2 线: 4
7 排列: 2 1 3 4 线: 3
8 排列: 2 1 4 3 线: 5
9 排列: 2 3 1 4 线: 3
10 排列: 2 3 4 1 线: 3
11 排列: 2 4 1 3 线: 1
12 排列: 2 4 3 1 线: 1
13 排列: 3 1 2 4 线: 2
14 排列: 3 1 4 2 线: 2
15 排列: 3 2 1 4 线: 3
16 排列: 3 2 4 1 线: 3
17 排列: 3 4 1 2 线: 5
18 排列: 3 4 2 1 线: 3
19 排列: 4 1 2 3 线: 4
20 排列: 4 1 3 2 线: 3
21 排列: 4 2 1 3 线: 2

восстановить изображение

output_38_3.png

output_38_4.png

output_38_5.png

正确顺序
4 2
3 1

Заканчивать! количество тестов 22 конечное состояние True [4, 2, 3, 1]

8. Извлеките результаты

Взгляните еще раз на то, как эта головоломка, если хотите交换位置Есть 12 комбинаций

list1 = [1,2,3,4]

result = itertools.permutations(list1,2)
idx=0
for 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 diffs

ab = 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/4
px = 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]

9. Имитация работы

На этом весь процесс анализа восстановления головоломки завершен, ниже будет принят еще один простой метод.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]

Найдите элемент dom веб-головоломки, сохраните его для манипуляций и обменяйте головоломку.

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() instead
d1 = 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() instead
d2 = 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() instead
d3 = 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() instead
d4 = browser2.find_element_by_css_selector('#dx_captcha_jigsaw_fragment-bottom-right_3 > div')

Найдите 2 дома, которые нужно перетащить и доставить в вебдрайвер.

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()

Просто перетащите его, целевой веб-сайт одобрит его, но решит, что есть проблема, и появится новый проверочный код. Это новая область технических исследований, которую стоит изучить и которой стоит поделиться с вами.

6. Финал

учиться на практике, если есть错误Пожалуйста, укажите это, спасибо!

адрес проекта:GitHub.com/Хотя нет/Битва при Пу…