JAVA使用selenium破解极验验证码(下载背景图+识别缺口+移动轨迹处理)

作者: admin 分类: Scrapy 发布时间: 2020-05-09 18:15  阅读: 509 views

验证码这是一个很好的防御发明,但总有人会破解掉它。包括它的各种变种。极验验证码就有很多公司使用。心血来潮,试试滑动类型的验证码。

这里使用的是官方测试地址
https://www.geetest.com/demo/slide-bind.html。
这种验证方式是提供一个缺损图,通过移动元素到正确的位置完全匹配来判定是否是人为操作。

1.页面分析

打开chrome控制台,先手动操作几次看看它的请求,及页面结构。我们首先应该去获取图片。

通常有几种获取图片的方式:
– 直接获取页面上的img标签src属性。(可以是url也可以是base64编码后的信息)
(这种图片都是可以直接访问的,没有次数限制等。获取很方便。)
– canvas标签中的图片,是在HTML5中新增的标签用于在网页实时生成图像
(这种图片也比较方便获取,文中示例就是这种获取方式。)
– 通过API接口的规则获取返回值
(这个要分析接口的验证规则、解密数据等,比较麻烦。)
– 屏幕截图
(这种要计算坐标,可能还截取不到所需要的原图)

具体的信息可以百度,都有对应的处理方式,不在赘述。

java破解极验

2.图片分析

当获取到所需要的原图 + 背景图之后,需要对比判断是什么位置出现了缺口。参考网上的处理方式是获取RGB颜色,进行逐一比对,如果差值较大,就判定是缺口位置。当然这种方式会有一定的失败概率。

因为,有的图片增加了干扰信息,有的图片做了对比度的调整。

java破解极验

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();

    }
}

下图是使用水平滑动无纵向抖动的模拟结果。
java破解极验

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;
    }

}

下图是前快中匀后快,超过后返回,+纵向抖动的模拟操作。看起来就像老太太手抖的操作一般,居然通过了。

java破解极验

不知道这个地址上的极验验证码是否为老版的。
相关代码仅供参考,学习,请勿使用在非法获取数据等方面。

点击验证码+手势验证码有点难 Orz ! 跪了

更多查看:JAVA处理爬虫逻辑的相关信息整理、代码实现、逻辑整理


   原创文章,转载请标明本文链接: JAVA使用selenium破解极验验证码(下载背景图+识别缺口+移动轨迹处理)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

更多阅读