OpenCV 破解滑块验证码 -Java 篇

本贴最后更新于 1488 天前,其中的信息可能已经沧海桑田

验证码作为一种安全机制,可以有效防止暴力破解密码、发帖、灌水、刷票等,大家在做web自动化的时候应该有碰到验证码这个难题,一般我们可以和开发沟通请求他们的帮助:去掉验证码或者设置一个万能验证码,而如果开发不帮忙我们该如何去解决呢?本篇文章以Java语言为例教你怎么破解验证码。

现在大多数网址会采用滑块验证码的方式,下面以腾讯的滑块验证码为例,先来看看破解的效果:

111.gif

要破解滑块验证码,我们一般采取的思路如下:

关键点就在于如何获取滑块到背景缺口图的距离,现在主流的方案是通过OpenCV(Open Source Computer Vision Library)开源计算机视觉库来处理,openCV有非常多图像处理方法,其是通过C/C++开发的,但是对外有提供Java,Python的接口,所以不管是Python还是Java我们都可以使用openCV进行图像处理。

环境准备

Step1:下载OpenCV

进入到官网 https://opencv.org/releases/ 下载对应系统的openCV软件包,之后解压

Step2:配置环境变量

进入到opencv -> build ->x64 ->vc15 ->bin目录,将路径复制追加到Path环境变量中

image20201109145625292.png

Step3:Intellij工程中添加jar包

Intellij中选择File -> Project Structure -> Modules -> Dependencies

点击add -> JARS or directories... 选择D:\software\opencv\build\java\opencv-450.jar文件

image20201109145816084.png

验证码破解实现

首先我们通过selenium进入到验证码页面(以QQ空间为例)

//设置chromeDriver识别路径
System.setProperty("webdriver.chrome.driver", "src/test/resources/chromedriver.exe");
driver = new ChromeDriver();
driver.get("https://qzone.qq.com/");
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
//切换登录页面所在iframe
WebElement loginFrame = driver.findElement(By.id("login_frame"));
driver.switchTo().frame(loginFrame);
driver.findElement(By.id("switcher_plogin")).click();
driver.findElement(By.id("u")).sendKeys("362715381");
driver.findElement(By.id("p")).sendKeys("1234r2we");
driver.findElement(By.id("login_button")).click();

进入到验证码界面后,我们仔细观察会发现:滑块和背景是两张分开的图片,src属性中保存的即为图片URL地址,所以我们可以通过URL将两者下载到本地

image20201109152101649.png

//切换到验证码所在的iframe
WebElement tcaptchaFrame = driver.findElement(By.id("tcaptcha_iframe"));
driver.switchTo().frame(tcaptchaFrame);
//定位滑块图片
WebElement slideBlock = driver.findElement(By.id("slideBlock"));
//定位验证码背景图
WebElement slideBg = driver.findElement(By.id("slideBg"));
//获取图片Url链接
String slideBlockUrl = slideBlock.getAttribute("src");
String slideBgUrl = slideBg.getAttribute("src");
//下载对应图片
System.out.println("图片下载开始...");
downloadImg(slideBlockUrl, "slideBlock.png");
downloadImg(slideBgUrl, "slideBg.png");

关键点在于获取滑块到滑动背景缺口图的横向距离,这里通过OpenCV的模板匹配技术matchTemplate

然后再通过selenium的Actions类完成滑动,在滑动的时候需要注意不能直接从开始点滑到终止点(有些网站会判定脚本操作),其中getMoveTrack用于获取滑动轨迹,控制每阶段的滑动速度

 //获取滑块到滑动背景缺口图的横向距离
double slideDistance = getSlideDistance(System.getProperty("user.dir")+"\\slideBlock.png", System.getProperty("user.dir")+"\\slideBg.png");
Actions actions = new Actions(driver);
WebElement dragElement = driver.findElement(By.id("tcaptcha_drag_button"));
//获取style属性值,其中设置了滑块初始偏离值  style=left: 23px;
//需要注意的是网页前端图片和本地图片比例是不同的,需要进行换算
slideDistance = slideDistance * 280 / 680 - 23;
actions.clickAndHold(dragElement).perform();
//根据滑动距离生成滑动轨迹,约定规则:开始慢->中间快->最后慢
List<Integer> moveTrack = getMoveTrack(slideDistance);
for (Integer index : moveTrack) {
    //Thread.sleep(20);
    actions.moveByOffset(index, 0).perform();
}
actions.release().perform();

getSlideDistance方法实现

首先对滑块进行处理

  1. 灰度化
  2. 去除图片黑边
  3. inRange二值化转黑白图

效果如下:

image20201109160457868.png

代码实现:

// 加载OpenCV本地库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//对滑块进行处理
Mat slideBlockMat = Imgcodecs.imread(slideBlockPicPath);
//1、灰度化图片
Imgproc.cvtColor(slideBlockMat, slideBlockMat, Imgproc.COLOR_BGR2GRAY);
//2、去除周围黑边
for (int row = 0; row < slideBlockMat.height(); row++) {
    for (int col = 0; col < slideBlockMat.width(); col++) {
        if (slideBlockMat.get(row, col)[0] == 0) {
            slideBlockMat.put(row, col, 96);
        }
    }
}
//3、inRange二值化转黑白图
Core.inRange(slideBlockMat, Scalar.all(96), Scalar.all(96), slideBlockMat);
  

对滑动背景图进行处理

  1. 灰度化
  2. 二值化转黑白图

image20201109161231283.png

代码如下:

//对滑动背景图进行处理
Mat slideBgMat = Imgcodecs.imread(slideBgPicPath);
//1、灰度化图片
Imgproc.cvtColor(slideBgMat, slideBgMat, Imgproc.COLOR_BGR2GRAY);
//2、二值化
Imgproc.threshold(slideBgMat, slideBgMat, 127, 255, Imgproc.THRESH_BINARY);
Mat g_result = new Mat();
/*
 * matchTemplate:在模板和输入图像之间寻找匹配,获得匹配结果图像
 * result:保存匹配的结果矩阵
 * TM_CCOEFF_NORMED标准相关匹配算法
 */
Imgproc.matchTemplate(slideBgMat, slideBlockMat, g_result, Imgproc.TM_CCOEFF_NORMED); 
/* minMaxLoc:在给定的结果矩阵中寻找最大和最小值,并给出它们的位置
 * maxLoc最大值
 */
Point matchLocation = Core.minMaxLoc(g_result).maxLoc;
//返回匹配点的横向距离
return matchLocation.x;

需要注意的是运行时需要指定library路径,不然会报如下错误

java.lang.UnsatisfiedLinkError: no opencv_java450 in java.library.path

选择Edit Configuration -> VM options中添加:

-Djava.library.path=D:\software\opencv\build\java\x64

image.png

6 操作
shakebabe 在 2020-11-25 16:55:25 更新了该帖
shakebabe 在 2020-11-10 13:53:20 更新了该帖
shakebabe 在 2020-11-10 13:51:59 更新了该帖
shakebabe 在 2020-11-10 13:51:18 更新了该帖 shakebabe 在 2020-11-10 13:50:42 更新了该帖 shakebabe 在 2020-11-10 13:50:13 更新了该帖
回帖
请输入回帖内容 ...