因业务需要,要做某网站的模拟登陆,有个比较简单的图片验证码。
之前登陆的话,同事用的基本都是js逆向。然而已经是前同事了,然而逆向的工作量我是不愿意接受的,所以划水的时候做了一个小demo,尝试解决这个问题。
当然了网站名字不好透露,这里放一部分测试数据。
思路:
首先看下商业上是怎么处理图片验证码的,借鉴一下。
百度图像识别,图像文本识别等关键词,出现了ali和tx的广告(打钱),有一个关键词频繁出现:OCR;于是又搜了一下OCR,OCR (Optical Character Recognition,光学字符识别),这个应该算是比较具体的技术名字了;拿这个关键词去GitHub搜一下,果然有现成的东西:https://github.com/tesseract-ocr/tesseract
下载下来好好康康,C++写的,win/linux都能用,直接用cli操作,指定输出路径。那这就很勇了,安装走起。
自己用画图手写了几个算式,识别效果还不错。然而直接用目标网站的图片就凉凉了……
这里简单分析了一下,应该是图片内容有问题,可以看上面的测试数据集,多了一部分干扰线;再一个tesseract的设置应该也有问题,虽然什么都没有识别出来,但是结果集是一个四行的空文档。后者直接看tesseract的文档,有一个配置项--psm,是预指定的识别模式,看了一下选择13,单行识别模式。前者的话,这一版是暂时做了简单的图像处理。
那么整体的处理模块就分为两个部分了:第一个,图像去干扰;第二个,调包生成结构再处理;
一、图像去干扰
这个图片集本身是比较简单的(这也是我做着玩也能做出来的原因,笑,不过思路还是可以分享一下)
分析一下图片,第一,图片四周一圈单像素的干扰信息,看上去就是一个黑框;第二,图片中的干扰像素和信息像素颜色区分度还算是比较高的,信息像素基本是偏向黑色的,后期查看虽然不是完全的#000000,rgb值都确实是普遍小于干扰像素的。那么就设置一个阈值,筛选出不是那么黑的像素点,直接涂白就行了。
具体代码如下:
const getPixels = require('get-pixels') // 这个包比较老,只能用回调,promisify都救不回来
const fs = require('fs')
const jpeg = require('jpeg-js') // 只有getPixels,没有setPixels。手动反向操作加密图片内容后再输出
const { exec } = require('child_process')
const detach = 30 // 颜色阈值,一边试一边调,手动调到一个合适的水平
let pic = 'math'
process.argv[2] && (pic += process.argv[2])
const solve = () => {
getPixels(`${pic}.jpeg`, (err, pixels) => {
if (err) {
return
}
let x = pixels.shape[0]
let y = pixels.shape[1]
for (let i = 0 ; i < x ; i++) {
for (let j = 0 ; j < y ; j++) {
let r = pixels.get(i, j, 0)
let g = pixels.get(i, j, 1)
let b = pixels.get(i, j, 2)
// let a = pixels.get(i, j, 3)
if (r > detach || g > detach || b > detach || i === 0 || j === 0 || i === (x - 1) || j === (y - 1)) {
pixels.set(i, j, 0, 255)
pixels.set(i, j, 1, 255)
pixels.set(i, j, 2, 255)
}
}
}
let temp = {
width: x,
height: y,
data: pixels.data
}
fs.writeFileSync('./out.jpeg', jpeg.encode(temp).data)
// orc() // 调包处理
})
}
去干扰前后对比
二、调包处理
这个没啥好说的,windows下的安装地址为:https://digi.bib.uni-mannheim.de/tesseract/tesseract-ocr-w64-setup-v5.0.0-alpha.20190708.exe
这个一路点点点下一步就好,安装完需要手动设置环境变量,环境变量不会设置的百度一下吧……
会linux的就不用说了吧……
对应代码如下:
const orc = () => {
exec('tesseract out.jpeg out --psm 13', () => {
const s = fs.readFileSync('./out.txt').toString('utf8')
let res
let nums = s.match(/\d+/g)
if (s.match(/\+/)) {
res = Number(nums[0]) + Number(nums[1])
} else if (s.match(/-/)) {
res = Number(nums[0]) - Number(nums[1])
} else if (s.match(/x/)) {
res = Number(nums[0]) * Number(nums[1])
} else if (s.match(/÷/)) {
res = Number(nums[0]) / Number(nums[1])
}
console.log(res)
})
}
差不多就是这样了,试一试,发现了两个问题,乘号被识别为了小写字母字符x,这个将就一下也可以用;除号基本识别不出来,这个就不是三分钟能解决的问题了。好在这个东西识别错了也没什么大问题,再调一次接口就好了。
所以既然这一期的标题里有一个“上”字,那这篇文章就讲到这里了;下一期划水的时候,我会讲一下tesseract训练集,专门处理一下乘号、除号无法识别的问题。