用 Gocv 破解腾讯滑块验证码

omigo · 2021-03-29 11:25:54 · 7252 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2021-03-29 11:25:54 的主题,其中的信息可能已经有所发展或是发生改变。

完整源码和图片在 Github 上 https://github.com/omigo/crack-slide-captcha

一、背景 二、思路 计算滑块移动距离 模拟移动 三、实战 Go 服务 js 脚本 四、体验 五、思考 参考 一、背景 滑块验证码是一项人机识别技术,操作简单,真人体验好,机器识别效果也不差,可以有效防止脚本做任务,增加机器脚本薅羊毛的难度。但其破解也相对简单,这里演示一个Demo,通过 OpenCV 匹配找出滑块位置,计算出滑动距离,然后模拟 js 鼠标事件,在 Chrome 控制台执行脚本,完成破解动作。

二、思路 先看一下效果:

滑块验证码主要有三个元素组成:带缺口的背景大图(bg)、缺口小块(block)、滑块(slide)。

目标是把缺口移动到对应的位置,也就是说要解决两个问题:1.滑块移动距离;2.模拟移动。

计算滑块移动距离 OpenCV 有模板匹配方法,可以通过多种算法找到小图(即模板)在大图中的位置。

  1. OpenCV 读取 带缺口的背景大图(bg)、缺口小块(block) 图片,原始图片颜色有 256256256 种。
  1. 把两个图片都转成灰度图片,这样图片只有 256 种颜色了,大大降低计算量,加快匹配速度。
  1. 二值化,即把图片变成只有纯黑和纯白两种颜色,进一步减小计算量,加快匹配速度。

当然,二值化之后,图片可能出现大面积黑色或大面积白色,造成匹配结果错误。所以本文没有使用二值化,而是使用灰度图像匹配。

  1. 开始匹配。下面左图是灰度匹配的结果,非常准,右图是二值化后匹配的结果,匹配失败。

匹配过程有一些细节,对匹配结果会造成很大影响,例如,

  1. 图片处理非常重要,例如缺口小块是不规则的,边缘透明,那么这部分不能参与匹配,要使用掩码,而且掩码要尽可能和缺口互补。

  2. 匹配算法有很多种,选择合适的。

  3. 匹配不可能100%准,需要在性能和准确率之间平衡,例如上面提到的是否一定要二值化。

模拟移动 WebAPI 有个 Document.createEvent() 函数,可以用来模拟各种事件。

这里需要模拟的是鼠标移动,主要控制的是反应时间、按下位置、移动速度,大多数人操作验证码在 1-2s ,先快后慢。

这里只是为了演示,论证破解的可行性,所以是匀速滑动的,按下位置是随机的。

三、实战 这里演示的是 Chrome 下破解 腾讯防水墙 可疑用户 滑块验证码 https://007.qq.com/online.html?ADTAG=index.head ,所以使用的工具和技术非常简单,Chrome 控制台+Golang+OpenCV4.5.1。

Chrome 控制台 用于执行 js,获取图片内容,调用 Golang 服务,得到滑动距离,模拟滑动。

Golang 接收图片,调用 OpenCV,匹配缺口位置,返回给客户端。

OpenCV 强大的计算机视觉算法开源库。

Go 服务 js 无法直接调用 OpenCV,但可以调用 Web服务,而且 Web 服务通用性好,可以对接不同的前端。

  1. 开启一个Go服务,接收参数,并返回移动距离,注意要支持跨域。

接收的参数包括 带缺口的背景大图(bg)、缺口小块(block) 图片 Base64 格式的内容和他们在网页上显示的大小(非真实大小)。显示大小用于缩放图片,使缺口小图能和大图上的缺口大小完全一致,提高匹配成功率。

展开源码

  1. 图片预处理。

1)先通过 gocv 库,读取图片,注意 缺口小图边缘透明的,所以有 4个通道(BGRA),完整读取。

展开源码

2)把图片大小调整到网页显示的大小。

1 2 3 4 5 func resize(origin gocv.Mat, cols, rows int) gocv.Mat { resized := gocv.NewMatWithSize(cols, rows, origin.Type()) gocv.Resize(origin, &resized, image.Pt(cols, rows), 0, 0, gocv.InterpolationNearestNeighbor) return resized }

3)变成灰度图像。

1 2 3 4 5 func gray(origin gocv.Mat) gocv.Mat { grayed := gocv.NewMat() gocv.CvtColor(origin, &grayed, gocv.ColorBGRToGray) return grayed }

4)二值化(可选)。

1 2 3 4 5 func threshold(origin gocv.Mat) gocv.Mat { thresholdBG := gocv.NewMat() gocv.Threshold(origin, &thresholdBG, 100, 255, gocv.ThresholdBinaryInv) return thresholdBG }

  1. 匹配。

1)由于缺口小图不规则,所以需要使用掩码,掩码图像刚好就是缺口小图的 Alpha通道 即透明度,所以直接使用这个通道即可。

1 gocv.Split(resized)[3]

2)选定一种算法,不同的算法,结果可能会不一样,开始匹配,并对中间结果归一化处理,然后根据选择的算法,选择最佳匹配结果,即移动距离。

http://zhaoxuhui.top/blog/2017/06/12/%E5%9F%BA%E4%BA%8EPython%E7%9A%84OpenCV%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%8614.html

这篇文章对此讲解得非常到位,所以这里不多缀述。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func match(bg, block, mask gocv.Mat) image.Point { result := gocv.NewMatWithSize( bg.Rows()-block.Rows()+1, bg.Cols()-block.Cols()+1, gocv.MatTypeCV32FC1) defer result.Close()

gocv.MatchTemplate(bg, block, &result, gocv.TmSqdiff, mask)
gocv.Normalize(result, &result, 0, 1, gocv.NormMinMax)

_, _, _, maxLoc := gocv.MinMaxLoc(result)

log.Debug(maxLoc)
return maxLoc

}

js 脚本 1.获取图片内容和大小。

带缺口的背景大图(bg)、缺口小块(block) 都有固定的 id,取得dom的 src 属性,然后通过 FileReader 读出图片内容。

图片大小即 clientWidth/clientHeight。

1 2 3 4 5 6 7 8 9 10 async function toDataURL( url) { const resp = await fetch(url); const blob = await resp.blob(); return new Promise((resolve, reject) => { const reader = new FileReader() reader.onloadend = () => resolve(reader.result) reader.onerror = reject reader.readAsDataURL(blob) }) }

2.调用 Go 服务,得到 移动距离。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let resp = await fetch('http://127.0.0.1:8080/getdistance', { body: JSON.stringify({ 'bg_base64': bgBase64, 'bg_width': bgElem.clientWidth, 'bg_height': bgElem.clientHeight, 'block_base64': blockBase64, 'block_width': blockElem.clientWidth, 'block_height': blockElem.clientHeight, }), // must match 'Content-Type' header headers: { 'content-type': 'application/json' }, method: 'POST', // GET, POST, PUT, DELETE, etc. mode: 'cors' // no-cors, cors, same-origin }) const data = await resp.json() // parses response to JSON console.log(data.distance)

  1. 模拟移动。

1)确定滑动位置和滑块元素。

1 2 3 4 5 6 7 8 9 const obj = document.querySelector(slide); console.log(obj) obj.target = '_self'; const _owh = obj.getBoundingClientRect(); let _ox = _owh.width / 2, _oh = _owh.height / 2; _ox = Math.floor(Math.random() _ox + 60); _oh = Math.floor(Math.random() _oh + 60); _ox = _ox + _owh.x; _oh = _oh + _owh.y;

2)创建鼠标事件。

1 2 3 4 5 6 function iME(obj, event, screenXArg, screenYArg, clientXArg, clientYArg) { var mousemove = document.createEvent("MouseEvent"); mousemove.initMouseEvent(event, true, true, window, 0, screenXArg, screenYArg, clientXArg, clientYArg, 0, 0, 0, 0, 0, null); obj.dispatchEvent(mousemove); }

3)计算滑动速度,模拟移动鼠标。

1 2 3 4 5 6 7 8 9 10 11 12 13 var elem = document.querySelector(id), k = 0, interval; iME(elem, "mousedown", 0, 0, clientX, clientY); interval = setInterval(function () { k++; iME(elem, "mousemove", clientX + k, clientY, clientX + k, clientY);

    if (k >= distance) {
        clearInterval(interval);
        iME(elem, "mouseup", clientX + k, clientY, clientX + k, clientY);
    }
}, 8);

四、体验 完整代码如下:

match.go 展开源码 slide.js 展开源码

  1. 开启 Go 服务,注意 gocv 只是包装了 OpenCV,开启服务前必须安装 OpenCV,参考 https://github.com/hybridgroup/gocv#macos

  2. 用 Chrome 打开 腾讯防水墙主页 https://007.qq.com/,依次点击 "可疑用户"-"体验验证码",弹出滑块验证码。

  3. 调出开发者工具,选择 "控制台",由于滑块验证码嵌入在 iframe 中,所以要把控制台的上下文从 "top" 切换成 "tcaptcha_iframe(cap_union_new_show)"。(注意是 cap_union_new_show, 不是 drag_ele.html。)

  4. 把 slide.js 所有代码粘贴到控制台,回车执行 js,即可看到滑块验证码开始移动。如果一次不成功,再点击"体验验证码",上下文,多试几次,因为匹配不能保证100%准确。(注意每次执行执行 js 前都要切换上下文,否则会报错找不到对象。)

五、思考

  1. 匹配前还可以根据验证码特点做很多优化,例如 缺口高度是固定的,而且位置都靠右,这样匹配位置可以确定在很小的范围内,大大提高匹配效率。

  2. 验证码生成过程是很耗时的,一般系统都是预先生成有限个验证码图片,重复利用,所以,如果加上缓存,同样的验证码图片无需重复计算滑动距离,匹配算法变成一次简单的查缓存了。

  3. 真人滑动验证码是有一定反应时间的,不如机器反应灵敏,所以真实情况破解程序要休眠一点时间。

  4. 真人滑动验证码,到终点时不仅速度慢,结果还有可能不会完全吻合,相差几个像素,还有可能来回微小移动。

  5. 真人滑动验证码,按下位置可能在滑块(slide)中间部位,但不会每次都是正中。

  6. 移动端的验证码真人是用手指滑动的,所以按压力度也需要注意。

  7. 真人滑动验证码滑动过程,即轨迹,不会完全是直线的。

所以破解验证码,不仅要计算滑动距离,还要模拟反应时间、按下位置、移动速度,还要模拟按压力度、滑动轨迹(并非绝对直线)。

除此只外,当前环境和脚本也需要伪装或隐藏,所处的位置(IP、GPS等)、使用的设备等信息在破解量大时需要不断更换。

当然破解是个对抗的过程,如果验证码没有对移动速度、滑动轨迹、设备、环境等做风控策略,那么破解就相对简单。

转换一个角色,验证码防护如果做了各个方面的策略(除上面提到的,还有其他策略),不需要面面俱到,也不需要做得非常复杂,仅仅在多个方面都做一些简单的风控,就可以拦截大部分机器脚本,大大增加破解难度。

参考 GoCV: https://github.com/hybridgroup/gocv

OpenCV 模板匹配: http://zhaoxuhui.top/blog/2017/06/12/%E5%9F%BA%E4%BA%8EPython%E7%9A%84OpenCV%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%8614.html

破解腾讯拼图验证码: https://www.freebuf.com/geek/265662.html

破解顶象: https://www.freebuf.com/geek/265479.html

《腾讯防水墙滑动拼图验证码》 《百度旋转图片验证码》 《网易易盾滑动拼图验证码》 《顶象区域面积点选验证码》


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

7252 次点击  
加入收藏 微博
1 回复  |  直到 2021-03-30 09:05:18
Felixw
Felixw · #1 · 5年之前

这个可以有啊

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传