奇技指南
现在的人工智能过于强大, 大部分扭曲的图形验证码都可以被机器破解, 而且图形验证码体验很差, 输入困难。而滑动验证码操作简便, 破解难度大, 很快就流行起来了。今天来讲讲滑动验证码的破解办法。
本文由 chunpu 在奇舞周刊投稿,转载请注明出处。
最近西部世界第二季很火, 小编经常分不清谁是机器人谁是真人
为了区分人类和机器, 有个人发明了一种测试, 他叫图灵~
验证码就是一个典型的图灵测试, 英文名 captcha, 全称如下
Completely Automated Public Turing test to tell Computers and Humans Apart
全自动区分计算机和人类的图灵测试
目前主流的验证码有
- 图形验证码
- 短信验证码
- 滑块验证码
- 图中点选验证码
但现在的人工智能过于强大, 大部分扭曲的图形验证码都可以被机器破解, 已经不再是一个可靠的图灵测试
而且图形验证码体验很差, 输入困难
这时候, 滑动验证码出现了, 最具代表性的就是 Geetest(极验)
滑动验证码对机器破解有两大难点, 第一个是需要通过图像识别知道滑到哪里, 第二个是需要模仿人类做出滑动的手势
滑动验证码 操作简便, 破解难度大, 很快就流行起来了
WebDriver 标准正好 W3C 近日发布了一个浏览器自动化操作的标准, 名叫 Webdriver
https://www.w3.org/TR/webdriver1/
小编就拿极验滑动验证码开刀, 和大家一起感受 WebDriver 的强大功能
先附上小编成果(本视频20秒, 没有声音):https://v.qq.com/x/page/y0715zqcz7f.html
安装 WebDriverWebDriver 已经是既定标准, 各大浏览器最新版都天然支持 WebDriver 协议, 使用门槛大大降低
小编本次使用的是支持度较好的浏览器 firefox
从 https://github.com/mozilla/geckodriver 官方仓库中即可下载 firefox 的 diver
使用 selenium-webdriver
WebDriver 标准本身只是定义了一系列操作浏览器的 HTTP 协议, 但 selenium 已经为我们封装成了 sdk, 直接调用函数即可
const webdriver = require('selenium-webdriver')
打开极验demo页面我们先用 WebDriver 实现一个最简单的例子: 打开一个网页
const webdriver = require('selenium-webdriver')
!async function() {
// 新建一个 firefox 的 driver 实例
let driver = await new webdriver.Builder().forBrowser('firefox').build()
// 访问极验demo页
await driver.get('http://www.geetest.com/type/')
console.log('success')
}()
运行这段代码, 我们能看到浏览器打开了极验的 demo 页
此时浏览器的地址栏是黄色的, 表示该浏览器被控制了, 就好像电视剧里面被心灵控制的人眼睛是红色的
选择滑动行为验证
极验第一个标签是智能组合验证, 第二个标签才是我们要破解的滑动验证, 因此需要先切换至滑动验证
我们通过 css 选择器选出要点击的标签, 然后使用 WebDriver 点击这个元素
await driver.findElement(webdriver.By.css('.products-content li:nth-child(2)')).click()
点击验证按钮来到了滑动行为验证区域, 接下来就要点击验证按钮, 同样也是先用 CSS 选择器选出按钮, 然后再点击
await driver.findElement(webdriver.By.css('.geetest_radar_tip')).click()
区域截图
到这一步, 滑动验证码已经弹出
我们碰到了破解过程中的第一个难点, 图像识别
要知道拼图需要滑动到哪里, 先要知道完整图片以及缺一块的图片, 放一起对比, 才能知道滑块需要滑动到哪里
WebDriver 提供了两种截图方式, 一种是全屏截图, 一种是元素截图
这里我们仅需要获取拼图缺失的背景图, 因此使用元素截图
// 隐藏原图再截图
await driver.executeScript(`document.querySelector('.geetest_canvas_fullbg').style.display = 'none'`)
// 找到验证码背景图元素, 是一个 canvas
const bgCanvas = await driver.findElement(webdriver.By.css('.geetest_canvas_bg'))
// 获得一个 base64 格式的 png 截图
const bgPng = await bgCanvas.takeScreenshot()
找到滑动点小编并不懂图像识别, 为了降低实现难度, 用了一个简单取巧的方法
因为拼图缺失的区域会有 阴影, 而阴影一般比较黑
因此我们把题目从 寻找拼图丢失区 改成了 寻找比较黑的点
当然这个草率的规则正确率并不高, 但为了demo演示已经足够了
那怎么定义 比较黑 呢? 最简单的方法就是挨个读取图像中的像素
我们把 R, G, B 三值相加, 数字越小就认为越黑, 最黑的 rgb(0, 0, 0) 就是0
附上小编用的读取像素库 get-pixels
开始滑动滑动操作使用了 WebDriver 中的 actions api, 可以完成一系列操作, 比如键盘输入, 鼠标移动, 点击等
// 获取拼图滑块按钮
const button = await driver.findElement(webdriver.By.css('.geetest_slider_button'))
// 获取按钮位置等信息
const buttonRect = await button.getRect()
// 初始化 action
let actions = driver.actions({async: true})
// 把鼠标移动到滑块上, 然后点击
actions = actions.move({
x: x 10,
y: y 10,
duration: 100
}).press()
// 花一秒钟把滑块拖动至拼图缺失区, 松开鼠标
await actions.move({
x: x 10 point.x - 5,
y: y 10,
duration: 1000
}).release().perform()
写完代码, 执行, 看着拼图顺畅的向前滑动, 等待着奇迹的发生
然而奇迹并没有发生, 只有一行黄字映入眼帘
怪物吃了拼图, 请3秒后重试
重复好多次, 我们发现
即便完全吻合, 也无法绕过极验的认证!
低估, 完全的低估!
拼图吻合只是必要条件, 破解的基础门槛
真正的难点是拖动过程中的 滑动轨迹!
模仿人类滑动我们再次想到了西部世界二, 最后的彩蛋威廉在世外山谷绝望的问艾米莉
威廉: Verify what?
艾米莉: Fidelity
Fidelity, 真实度
破解到了这一步, 能否模仿人类的滑动轨迹成了关键
我们反复不断的调教滑动代码, again and again
小编进行了多次尝试, 比如把 move action 分成多段, 按照不同的速度进行拖动, 甚至加入各种随机数, 但始终无法通过极验的轨迹检查
最后小编仿照微信随机红包的方式, 先把要滑动的距离切成几十份, 并且允许有负数
人在滑动拼图的时候很容易出现, 滑过了再滑动回去的场景, 因此负数可以增加 Fidelity
const count = 30 // 小编分成30步进行滑动
const steps = getSteps(distance, count)
const totalDuration = 8000 // 一共耗时8秒, 慢才能充实轨迹~
_.reduce(steps, (actions, step) => {
return actions.move({
x: x 10 step,
y: y 10 _.random(-5, 40), // 加上y轴随机数
duration: parseInt(_.random(totalDuration / count / 2, totalDuration / count * 2)) // 加上时长随机数
})
}, actions)
// 随机拆成n份
function getRandomDistribution(total, count) {
let item = total / count
item = item _.random(-item * 2, item * 3)
item = parseInt(item)
if (count === 1) {
return [total]
} else {
return [item].concat(getRandomDistribution(total - item, count - 1))
}
}
// 获取每次滑动的X坐标
function getSteps(total, count) {
let distribution = getRandomDistribution(total, count)
return _.map(distribution, (item, i) => {
return _.sum(distribution.slice(0, i 1))
})
}
保存, 执行
小编放下鼠标, 端起桌上的马克杯, 看着 WebDriver 再次控制浏览器
打开网页, 点击按钮, 拖动滑块, 滑块曲折前行...
摩擦摩擦, 似魔鬼的步伐, 似老奶奶颤巍巍的手
终于, 极验显示出一个清爽绿色的横幅, 仿佛在向我们招手: 欢迎你, 人类
,