Repository: chxj1992/slide_captcha_cracker Branch: master Commit: 0215fb92552c Files: 5 Total size: 6.2 KB Directory structure: gitextract_6a0k9blj/ ├── .gitignore ├── README.md ├── app.py ├── run.sh └── templates/ └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea __pycache__ ================================================ FILE: README.md ================================================ # slide_captcha_cracker ![](https://github.com/chxj1992/slide_captcha_cracker/raw/master/screenshot.png) [在线Demo](http://slide-captcha.chxj.name/) ### 简介 本项目是一个通过简单图片边缘检测算法来定位滑动验证码拼图在背景图中位置的一个例子。 代码主要采用`opencv`提供的一些函数对图片进行处理并实现定位,在这里只是提供一个思路,抛砖引玉。 ### 关于滑动验证码 演示项目所用到的滑动验证码的实现相对简单,整个交互过程主要包含以下步骤: * 服务端将背景图片和拼图图片合并为一张图片,并记录下拼图在背景图中的`x坐标`,然后将拼好图片和单独的拼图图片返回给客户端 * 客户端实现单片拼图在背景图上拖动的动画效果,并在用户完成拖动动作后,将当前拼图所处位置的坐标数据加密后返回给服务端 * 服务端解密数据并比较客户端返回的`x坐标`数据并与之前保存的`x坐标`数据进行比较,允许小范围内的误差 ### 实现原理 基于以上的验证码实现,本例子通过以下方式实现对验证码拼图在背景图中的定位(其他步骤较为简单,不做考虑): * 利用`opencv`库中提供的边界查找函数(cv2.findContours)提取单片拼图边缘轨迹并构造成一个二维矩阵(算子) * 利用 `高斯模糊算子`(cv2.GaussianBlur)和 `Canny边缘检测算子`(cv2.Canny)对背景图进行处理,凸显出拼图在图片中的边缘 * 用拼图轨迹算子在处理后的背景图上进行 `互相关操作`,所得最大(小)值的位置就是拼图在背景图中的坐标 ### 其他 拼图的定位只是破解滑动验证码的一个中间环节,想要破解一个好的验证码产品除了定位拼图在整个图片中的位置外,可能还有以下几个问题需要考虑: * 从服务端获取的背景图可能是经过加密的(需要阅读js源码获得恢复图片的算法) * 返回给服务端的参数一般是经过加密处理的(需要阅读js源码理解算法) * 服务端可能会对用户滑动行为的移动轨迹对用户进行校验(可以尝试通过selenium等工具模拟拖动行为,或者积累真实的拖动数据后学习规律并通过js源码获得构造数据的算法) ================================================ FILE: app.py ================================================ from flask import Flask from flask import jsonify, render_template from matplotlib.pyplot import imsave app = Flask(__name__) import base64 from io import BytesIO import cv2 import numpy as np import requests from PIL import Image # 偏移量 bias = -1 slide_captcha_url = 'http://api.shuibei.chxj.name/slide-captcha' def predict(): response = requests.get(slide_captcha_url) base64_image = response.json()['data']['dataUrl'] base64_image_without_head = base64_image.replace('data:image/png;base64,', '') bytes_io = BytesIO(base64.b64decode(base64_image_without_head)) img = np.array(Image.open(bytes_io).convert('RGB')) img_blur = cv2.GaussianBlur(img, (3, 3), 0) img_gray = cv2.cvtColor(img_blur, cv2.COLOR_BGR2GRAY) img_canny = cv2.Canny(img_gray, 100, 200) operator = get_operator('shape.png') (x, y), _ = best_match(img_canny, operator) x = x + bias print('the position of x is', x) buffer = mark(img, x, y) return {'value': x, 'image': base64.b64encode(buffer.getbuffer()).decode()} def get_operator(path, url=False, expand=False): if url: req = requests.get(path) arr = np.asarray(bytearray(req.content), dtype=np.uint8) shape = cv2.resize(cv2.imdecode(arr, -1), (69, 69)) else: shape = cv2.resize(cv2.imread('shape.png'), (69, 69)) shape_gray = cv2.cvtColor(shape, cv2.COLOR_BGR2GRAY) _, shape_binary = cv2.threshold(shape_gray, 127, 255, cv2.THRESH_BINARY) _, contours, hierarchy = cv2.findContours(shape_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) contour = contours[0] operator = np.zeros((69, 69)) for point in contour: operator[point[0][0]][point[0][1]] = 1 if expand: if point[0][0] > 0: operator[point[0][0] - 1][point[0][1]] = 1 if point[0][0] < 68: operator[point[0][0] + 1][point[0][1]] = 1 if point[0][1] > 0: operator[point[0][0]][point[0][1] - 1] = 1 if point[0][1] < 68: operator[point[0][0]][point[0][1] + 1] = 1 return operator def best_match(image, operator): y_range, x_range = image.shape max_value, position = 0, (1, 1) for y in range(1, y_range - 1): for x in range(1, x_range - 1): if y + 69 > 185 or x + 69 > 315: continue block = image[(y - 1):(y + 68), (x - 1):(x + 68)] value = (block * operator).sum() if value > max_value: max_value = value position = (x, y) return position, max_value def mark(img, x, y): cv2.putText(img, 'O', (x - 15, y + 70), cv2.FONT_HERSHEY_SIMPLEX, 4, (255, 0, 0), 2, cv2.LINE_AA) buffer = BytesIO() imsave(buffer, img) return buffer @app.route('/') def index(): return render_template('index.html') @app.route('/fetch') def fetch(): return jsonify(predict()) ================================================ FILE: run.sh ================================================ export FLASK_APP=app.py flask run --host=0.0.0.0 ================================================ FILE: templates/index.html ================================================ Slide Captcha Fork me on GitHub


滑动点X坐标 :