总结几种常见 验证码的解决方案,非常有用!

来源 | 咸鱼学Python
截止到今天咸鱼已经写了很多期关于 Js 逆向的文章,不过这么多的文章都有一个共同点,都是关于加密参数或者密码加密的解析,很多读者在后台私信希望能够出一些关于滑动验证或者人机验证的分析教程。
于是咸鱼总结了目前遇到过的的验证码类型以及总结出来的相关处理方式和大家聊一聊。
现在市面上的验证码的类型大致有下面几种类型。
一、图形验证码
比较常见的英文数字组合成的图形验证码,常常辅以各类干扰线和扭曲图片中的内容达到提高混淆难度的目的,并且通过加长图片中的文字长度提升识别成本。



像这类验证码的处理方案有很多种,简单给大家概括一下。
难度中低的两类验证码,安装 tesserocr,通过 OCR 技术结合 Python 的 tesserocr 库可以就可以完成识别。如果验证码中带有简单干扰线可以使用灰度和二值化的方法提高代码的识别率。
常用示例代码:
import tesserocrfrom PIL import Imageimage = Image.open('code2.jpg')image = image.convert('L')threshold = 127table = []for i in range(256):if i < threshold:table.append(0)else:table.append(1)image = image.point(table, '1')result = tesserocr.image_to_text(image)print(result)
难度较高的多位英数+扭曲的图形验证码包括上面总结的中低难度的图形验证码,可以通过 TensorFlow 训练的方式达到识别验证码的目的。
使用这个方式的朋友记得要先准备好足够的验证码的样本,只要你的模型不是太差,通过足量的样本,不断调优是可以达到一个较为可观的识别率。
目前体验过最好的程序是冷月的四位英数,识别成功率高达 99.99% ,不过据知情人透露整个训练的样本达到了 6000 W ,耗费的时间精力可想而知。
二、旋转验证码
这类验证码是将验证码的图片旋转并且需要用户拖动下方滑块完成将图片摆正的操作才可以完成验证。

不过某家的这个验证码有一些小小的 bug,依靠劳苦大众的智慧,我在 GitHub 上发现了一个很 Nice 的项目。
项目地址:https://github.com/scupte/xuanzhaunyanz
因为图库的容量问题,没有超大的图库作为后盾,将全部的原图抓取下来对比完全可以得到旋转的角度了。
不过鉴于该家的验证码并没有普及所以了解一波即可。
部分对比代码:
# -*- coding: utf-8 -*-import cv2import numpy as npimagepath = '9_1.png'img = cv2.imread(imagepath)gray = cv2.cvtColor ( img , cv2.COLOR_BGR2GRAY )ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)#cv2.drawContours(img,contours,-1,(0,0,255),1)for cnt in contours:# 最小外界矩形的宽度和高度width, height = cv2.minAreaRect(cnt)[1]if width* height > 100:# 最小的外接矩形rect = cv2.minAreaRect(cnt)box = cv2.boxPoints(rect) # 获取最小外接矩形的4个顶点box = np.int0(box)print boxif 0 not in box.ravel():#绘制最小外界矩形for i in range(4):cv2.line(img, tuple(box[i]), tuple(box[(i+1)%4]), 0) # 5theta = cv2.minAreaRect(cnt)[2]if abs(theta) <= 45:print('图片的旋转角度为%s.'%theta)# angle = thetaprint thetacv2.imshow("img", img)cv2.waitKey(0)
三、滑动验证码
说到滑动验证码,一定一定要提某验,虽然说市面上关于滑动验证码的产品有很多,但是某验的地位就像 10 年前脑白金在保健品市场的地位一样,业界标杆啊。
它越牛逼,市场上用它做防护的网站也越多,像国家企业信用信息公示系统、B 站、狗东等等。
像某验的解决方案也有很多,不过原理大同小异。
selenium 模拟滑动
使用 selenium 这个大家都听过,步骤大致是将缺口图和原图进行对比获取缺口的横坐标,并使用计算完成拖动轨迹模拟,之后使用 selenium 按照轨迹滑动完成缺口的拼接。
这一类方法的优点是门槛低,原理简单,缺点是完成滑动耗时较长,成功率无法估计(同一轨迹计算规则使用多次后成功率迅速下降)
常见的轨迹生成代码:
import numpy as npimport mathdef ease_out_expo(x):"""曲线函数:param x::return:"""if x == 1:return 1else:return 1 - pow(2, -10 * x)def get_tracks(distance, seconds):"""轨迹生成函数:param distance: 滑动总距离:param seconds: 滑动总时间:return:"""tracks = [0] # 存放轨迹的数组offsets = [0] # 存放滑动总距离的记录数组for t in np.arange(0.0, seconds, 0.1): # 产生一个数列如[0.0, 0.1, 0.2, 0.3]offset = round(ease_out_expo(t/seconds) * distance) # 根据时间t计算在曲线上的滑动距离tracks.append(offset - offsets[-1]) # 本次计算的距离减去上一次移动的距离,得到本次的轨迹offsets.append(offset) # 至本次滑动了的总距离return offsets, tracksa, b = get_tracks(138, 3)print(a, b)def get_tracksb(distance):"""根据物理的先加速再减速规律计算:param distance::return:"""distance += 20 # 加上20是为了滑动超过缺口再回滑v = 0 # 初速度t = 0.2 # 以0.2秒为一个计算周期forward_tracks = [] # 轨迹记录数组current = 0 # 初始移动距离mid = distance * 3 / 5 # 减速阀值即五分之三的距离加速剩下距离减速while current < distance: # 总移动距离等于输入距离时结束if current < mid: # 加速状态a = 2 # 加速度为+2else: # 减速状态a = -3 # 加速度-3s = v * t + 0.5 * a * (t ** 2) # 计算0.2秒周期内的位移v = v + a * t # 计算本次周期后的速度current += s # 将之前移动的总距离,加上本次0.2秒周期内移动的距离forward_tracks.append(round(s)) # 记录本次0.2秒周期内的移动距离为轨迹back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1] # 手动将开头加上的20,生成减去轨迹,即回滑轨迹return {'forward_tracks': forward_tracks, 'back_tracks': back_tracks}
Js 破解关键的参数
这类方法的门槛就比较高了,通过断点调试 Js,逆向分析滑动后提交参数的生成逻辑完成参数的生成,之后构造请求完成提交,当然这中间也是需要分析图片的缺口位置与模拟轨迹,不过没有使用到模拟所以速度快成功率高。
缺点是风险高,代码维护成本高,更新一个新版本就要重新分析而且逆向相关产品的代码是有一定的法律风险的,免费吃住也不是开玩笑的,所以很多能够商业化的大佬们都闷声发大财不会到处张扬。
使用现有的服务
上面两种方法各有各的优缺,很多人就想把这一块的工作量与风险分出去,这就要使用到第三方的服务商了。
不过目前国内市场上的服务商并没有这类服务,目前咸鱼在使用的是一家俄罗斯的服务商 - 2Captcha
这个服务商提供的验证码服务有很多种,其中包含了我们比较关心的 GeeTest 。

下面咸鱼给大家简单介绍下如何使用服务。(不要问为啥收费,人家服务商也要吃饭,况且这个价格实在便宜了)
首先,注册一个账号,官网是 http://2captcha.com/zh

完成注册之后会跳转到控制台界面,这里最重要的是获取到属于你的 API Key 。

好,拿到这个 API Key 之后就可以上手使用服务完成滑动的破解了。
通过参考官方的 API 文档,我们只需要构建两个 Get 请求就可以了。
第一个 Get 请求的组成是这样的:
https://2captcha.com/in.php?key= 上面获取的API KEY&method=geetest>= 某验参数&challenge= 某验参数&api_server=api-na.geetest.com(可选)&pageurl= 滑动验证码所在的网页地址
参数列表:
| 参数名 | 参数介绍 |
| key | API KEY |
| method | 表示验证码类型 |
| gt | 某验参数1 |
| challenge | 某验参数2 |
| api_server | api-na.geetest.com(选填) |
| pageurl | 滑动验证码所在的网页地址 |
这里解释下关于 gt 与 challenge 这两个参数的获取。
第一个请求中这两个参数其中 gt这个参数是固定的,找一个使用某验的网站就可以获取。例如:

challenge这个参数是由请求返回,你找到这个请求之后按照请求重新获取一次,如果是 XHR 的话也可以直接 replay XHR 。

完成参数构建,提交完第一个请求之后,成功会返回类似下面的结果。
OK|2122988149 or as JSON {"status":1,"request":"2122988149"}这里面的一串数字就是会话 ID。
有了这个会话 ID 之后我们就可以构建下一个请求,记住两个请求中间需要等待一些时间哦。
https://2captcha.com/res.php?key=API KEY&action=get&id=2122988149
参数列表:
| 参数名 | 参数介绍 |
| key | API KEY |
| action | Get |
| id | 上一个请求返回的会话ID |
这个请求返回的结果就是我们需要的加密参数。
{"challenge":"1a2b3456cd67890e12345fab678901c2de","validate":"09fe8d7c6ba54f32e1dcb0a9fedc8765","seccode":"12fe3d4c56789ba01f2e345d6789c012|jordan"}
以上常见的几类验证码,已经全部介绍完了。
肯定有人问像 google 家的 ReCaptcha 以及和他相似的 hCaptcha 的解决方案没有提到啊?
像以上两类验证码,刚刚提到的服务商也同样有提供接口打码。
至于其他不依靠服务商的解决方案,目前咸鱼还没有接触过,毕竟这两类验证码,咸鱼手动点击都没办法做到一次通过,目前也只能依赖服务商了。
