JAVA使用selenium破解极验验证码(下载背景图+识别缺口+移动轨迹处理)
验证码这是一个很好的防御发明,但总有人会破解掉它。包括它的各种变种。极验验证码就有很多公司使用。心血来潮,试试滑动类型的验证码。
这里使用的是官方测试地址
https://www.geetest.com/demo/slide-bind.html。
这种验证方式是提供一个缺损图,通过移动元素到正确的位置完全匹配来判定是否是人为操作。
1.页面分析
打开chrome控制台,先手动操作几次看看它的请求,及页面结构。我们首先应该去获取图片。
通常有几种获取图片的方式:
– 直接获取页面上的img标签src属性。(可以是url也可以是base64编码后的信息)
(这种图片都是可以直接访问的,没有次数限制等。获取很方便。)
– canvas标签中的图片,是在HTML5中新增的标签用于在网页实时生成图像
(这种图片也比较方便获取,文中示例就是这种获取方式。)
– 通过API接口的规则获取返回值
(这个要分析接口的验证规则、解密数据等,比较麻烦。)
– 屏幕截图
(这种要计算坐标,可能还截取不到所需要的原图)
具体的信息可以百度,都有对应的处理方式,不在赘述。
2.图片分析
当获取到所需要的原图 + 背景图之后,需要对比判断是什么位置出现了缺口。参考网上的处理方式是获取RGB颜色,进行逐一比对,如果差值较大,就判定是缺口位置。当然这种方式会有一定的失败概率。
因为,有的图片增加了干扰信息,有的图片做了对比度的调整。
3.轨迹分析
当判断好元素所需要移动的x轴距离之后,通过代码控制移动就可以了。但是这里的处理难度应该是(任意类型的滑动验证码)很高的。因为它也在识别你是人还是机。
- 匀速从左滑到右
- 匀速从左滑到右 + 纵向抖动
- 移动时前快后慢 + 超出折返处理
- 用户历史平均数据对比 + 其他维度因素比对等。
只能说你模拟的越像人,它的通过几率就越高。
4. 代码
1. selenium模拟打开页面,获取图片,移动滑块
package com.chl.spider;
import java.util.Calendar;
import java.util.Random;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.interactions.Actions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.chl.tools.image.CompareImages;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
/**
* 极验测试地址:https://www.geetest.com/demo/slide-bind.html
*
*/
public class GeetestProcessor implements PageProcessor {
Logger logger = LoggerFactory.getLogger(GeetestProcessor.class);
public static String baseUrl = "https://www.geetest.com/demo/slide-bind.html";
static {
System.getProperties().setProperty("webdriver.chrome.driver",
"/Users/chenhailong/Downloads/tools/nessarytool/chromedriver");
}
@Override
public void process(Page page) {
try {
WebDriver w = new ChromeDriver();
w.get(baseUrl);
Thread.sleep(1 * 1000);
if (BaseUtil.doesWebElementExist(w, By.id("btn"))) {
w.findElement(By.id("btn")).click();
Thread.sleep(3 * 1000);
long t = Calendar.getInstance().getTimeInMillis();
System.out.println(t);
JavascriptExecutor jsExecutor = (JavascriptExecutor) w;
String JS = "return document.getElementsByClassName('geetest_canvas_bg geetest_absolute')[0].toDataURL('image/png');";
Object obj = jsExecutor.executeScript(JS);
FileUtil.generateImage(obj.toString().split(",")[1], "/Users/chenhailong/Desktop/geetest_bg.webp");
JS = "return document.getElementsByClassName('geetest_canvas_fullbg geetest_fade geetest_absolute')[0].toDataURL('image/png');";
obj = jsExecutor.executeScript(JS);
FileUtil.generateImage(obj.toString().split(",")[1], "/Users/chenhailong/Desktop/geetest_fullbg.webp");
JS = "return document.getElementsByClassName('geetest_canvas_slice geetest_absolute')[0].toDataURL('image/png');";
obj = jsExecutor.executeScript(JS);
FileUtil.generateImage(obj.toString().split(",")[1], "/Users/chenhailong/Desktop/geetest_block.webp");
Thread.sleep(3 * 1000); //下载图片后对比
int x = CompareImages.CompareImagesNormal(); //获取缺口x坐标
x = x - 5; //有时候滑块并不是在x=0的位置上,有偏移
WebElement move = w.findElement(By.className("geetest_slider_button"));
Actions action = new Actions(w);
action.moveToElement(move).perform();
action.clickAndHold(move).perform();
// //第一种:水平匀速移动, 怪物吃了拼图
// int mod = 0;
// if(x > 10) { mod = x/10; }
// for (int i = 0; i < mod; i++) {
// action.moveByOffset(10, 0).perform();
// Thread.sleep(200);
// }
// action.dragAndDropBy(move,(x - mod*10), 0).perform();
// //第二种:前快中匀后快,超过后返回, 怪物吃了拼图
// int mod = 0;
// if(x > 0) {mod = x/10;}
// for(int i = 0; i< mod;i++) {
// if(i<2) {
// action.moveByOffset(10, 0).perform();
// Thread.sleep(new Random().nextInt(5) * 200 );
// }else if(mod - i > 2) {
// action.moveByOffset(10, 0).perform();
// Thread.sleep(5 * 200 );
// }else {
// action.moveByOffset(10, 0).perform();
// Thread.sleep(2 * 200 );
// }
// }
// int d = new Random().nextInt(20);
// action.moveByOffset((x - mod*10) + d, 0).perform();
// Thread.sleep(4 * 200 );
// action.dragAndDropBy(move,-d, 0).perform();
//第三种:前快中匀后快,超过后返回,+纵向抖动 居然成功了
int mod = 0;
if(x > 0) {mod = x/10;}
for(int i = 0; i< mod;i++) {
if(i<2) {
action.moveByOffset(10, new Random().nextInt(5) -new Random().nextInt(5)).perform();
Thread.sleep(new Random().nextInt(5) * 200 );
}else if(mod - i > 2) {
action.moveByOffset(10, new Random().nextInt(5) -new Random().nextInt(5)).perform();
Thread.sleep(5 * 100 );
}else {
action.moveByOffset(10, new Random().nextInt(5) -new Random().nextInt(5)).perform();
Thread.sleep(2 * 150 );
}
System.out.println(move.getLocation().toString());
}
int d = new Random().nextInt(20);
action.moveByOffset((x - mod*10) + d, new Random().nextInt(5) -new Random().nextInt(5)).perform();
Thread.sleep(4 * 150 );
action.dragAndDropBy(move,-d, 0).perform();
Thread.sleep(10000);
}
w.quit();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private Site site = Site.me().setRetryTimes(3).setSleepTime(1000).setTimeOut(30000);
@Override
public Site getSite() {
site.addHeader("User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36");
site.addHeader("Accept", "application/json, text/javascript, */*; q=0.01");
site.addHeader("X-Requested-With", "XMLHttpRequest");
site.addHeader("Accept-Encoding", "gzip, deflate, br");
return site;
}
public static void main(String[] args) {
GeetestProcessor yzm = new GeetestProcessor();
System.out.println(yzm);
Spider.create(yzm).addUrl(baseUrl).thread(1).run();
}
}
下图是使用水平滑动无纵向抖动的模拟结果。
2. 下载图片
public static boolean generateImage(String imgStr, String targetPath) { // 对字节数组字符串进行Base64解码并生成图片
if (imgStr == null) // 图像数据为空
return false;
BASE64Decoder decoder = new BASE64Decoder();
try {
// Base64解码
byte[] b = decoder.decodeBuffer(imgStr);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {// 调整异常数据
b[i] += 256;
}
}
// 生成jpeg图片
OutputStream out = new FileOutputStream(targetPath);
out.write(b);
out.flush();
out.close();
return true;
} catch (Exception e) {
return false;
}
}
3. 对比图片查找缺口
package com.chl.tools.image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
*
* @author chenhailong
* 参考:https://www.iteye.com/blog/chenxu-8456-1322020(像素对比)
* 参考:https://blog.csdn.net/u013248535/article/details/53929605/(图片获取像素)
* 参考:https://blog.csdn.net/CY19980216/article/details/103339082(python版破解, 对比度增强相关)
*
*/
public class CompareImages {
//界定两个图片的RGB差值为多少之后,确定是阴影。因为有的背景图会设置干扰项,所以这个不一定准确()
private static int RANGE = 50;
public static void main(String[] args) {
CompareImagesNormal();
}
/**
* 循环坐标对比原图+缺口图的X轴坐标信息
* @return
*/
public static int CompareImagesNormal() {
int[][] fullbg = getXY("/Users/chenhailong/Desktop/geetest_fullbg.webp");
int[][] bg = getXY("/Users/chenhailong/Desktop/geetest_bg.webp");
for(int i = 0; i< fullbg.length; i++) {
for(int j = 0; j < fullbg[i].length ; j++) {
if(tempCul(fullbg[i][j], bg[i][j])) {
return i; //这里返回横坐标
}
}
}
return 0;
}
private static int[][] getXY(String image){
int[][] list = null;
try {
BufferedImage bi = ImageIO.read(new File(image));
int w = bi.getWidth();
int h = bi.getHeight();
int x = bi.getMinX();
int y = bi.getMinY();
list = new int[bi.getWidth()][bi.getHeight()];
//第一种获取像素的方式,方便计算
for(int i = x;i< w; i++) {
for(int j = y; j< h; j++) {
list[i][j] = bi.getRGB(i, j) ;
// System.out.printf("%s%s%s\t",rgb[0],rgb[1],rgb[2]);
}
}
//第二种获取像素的方式,(这个调整有点繁琐)
// Raster raster = bi.getData();
// int[] temp = new int[raster.getWidth()*raster.getHeight()*raster.getNumBands()];
// int [] pixels = raster.getPixels(0,0,raster.getWidth(),raster.getHeight(),temp);
// for (int i=0;i<pixels.length;) {
// if((i%raster.getWidth()*raster.getNumBands())==0)//输出一列数据比对
// System.out.printf("ff%x%x%x\t",pixels[i],pixels[i+1],pixels[i+2]);
// i+=3;
// }
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
/**
* 获取旧/新的像素点,进行RGB对比,差值范围为RANGE(一些情况会有图片变暗等处理)
* @param origion 源
* @param news 新
* @return
*/
private static boolean tempCul(int origion, int news) {
int[] a = getRGB(origion);
int[] b = getRGB(news);
for(int i = 0; i < 3; i++) {
if(Math.abs( a[i] - b[i]) > RANGE) {
return true;
}
}
return false;
}
/**
* 根据像素点获取rgb颜色
* @param pixel
* @return
*/
private static int[] getRGB(int pixel) {
int[] rgb = new int[3];
rgb[0] = (pixel & 0xff0000) >> 16;
rgb[1] = (pixel & 0xff00) >> 8;
rgb[2] = (pixel & 0xff);
return rgb;
}
}
下图是前快中匀后快,超过后返回,+纵向抖动的模拟操作。看起来就像老太太手抖的操作一般,居然通过了。
不知道这个地址上的极验验证码是否为老版的。
相关代码仅供参考,学习,请勿使用在非法获取数据等方面。
点击验证码+手势验证码有点难 Orz ! 跪了
更多查看:JAVA处理爬虫逻辑的相关信息整理、代码实现、逻辑整理
原创文章,转载请标明本文链接: JAVA使用selenium破解极验验证码(下载背景图+识别缺口+移动轨迹处理)