java OpenCV挑戰極驗滑動拼圖驗證碼


一丶解析驗證碼組成

在這里插入圖片描述

從上面三張圖來看,極驗滑動拼圖驗證碼是由一個小的拼圖和一個大的背景圖組成,拼圖的形狀各式各樣,背景圖中有一個陰影缺口,與拼圖形狀一致。
在這里插入圖片描述
這里我們使用F12大法打開瀏覽器控制台,觀察一下驗證碼的頁面結構。
在這里插入圖片描述
通過觀察可以看到,驗證碼所包含的圖片均以<canves>畫布的形式呈現在頁面中,且有三張圖片,且第三張圖片被加上了屬性style=“display: none;”,即為隱藏不顯示。那么我們修改下頁面代碼,看下這張圖究竟是什么。

在這里插入圖片描述
修改完代碼發現,這不就是完整的背景圖嘛。那么根據上面的命名來看,基本可以確定這三張圖分別是什么了。

  • 第一張class為geetest_canvas_bg geetest_absolute,可以確定為帶缺口的背景圖。

  • 第二張class為geetest_canvas_slice geetest_absolute,可以確定為拼圖。

  • 第三張便是完整的圖片。

二丶分析出破解思路

  1. 首先根據這個驗證碼的組成,來分析一下我們人要做的事情:

按照正常的手動操作流程來看,我們需要看出背景圖中與拼圖對應的陰影缺口的位置,然后鼠標按住下方滑塊來把拼圖對正到缺口位置來完成驗證。

  1. 然后根據人要做的事情,來分析一下程序要做的事情:

根據分析得出下面幾個步驟:
1.獲取到兩張圖片(帶缺口背景圖、完整背景圖)
2.處理圖片,得到陰影位置並計算滑動距離
3.根據滑動距離模擬滑動

三丶具體操作步驟

1丶獲取到兩張圖片

由於這里的圖片都是通過canvas畫布呈現的,我們可以通過執行js代碼來生成圖片。
可以參考《如何抓取canvas畫布中的圖片》

在這里插入圖片描述

2丶處理圖片,計算滑動距離

通過第一步得到的兩張圖片可以看出,兩張圖有兩處不同的地方,一處差異不大,一處差異較大,我們可以通過比較每一個像素點的差異度來確定陰影缺口的位置。缺口的位置橫坐標減去小圖距離邊框的距離即為滑動距離。

以下是關鍵部分代碼:

private final String INDEX_URL = "https://www.geetest.com/Register"; // 延時加載 private static WebElement waitWebElement(WebDriver driver, By by, int count) throws Exception { WebElement webElement = null; boolean isWait = false; for (int k = 0; k < count; k++) { try { webElement = driver.findElement(by); if (isWait) System.out.println(" ok!"); return webElement; } catch (org.openqa.selenium.NoSuchElementException ex) { isWait = true; if (k == 0) System.out.print("waitWebElement(" + by.toString() + ")"); else System.out.print("."); Thread.sleep(50); } } if (isWait) System.out.println(" outTime!"); return null; } /** * 計算需要平移的距離 * * @param driver * @param fullImgPath完整背景圖片文件名 * @param bgImgPath含有缺口背景圖片文件名 * @return * @throws IOException */ public static int getMoveDistance(WebDriver driver, String fullImgPath, String bgImgPath) throws IOException { File fullFile = new File(fullImgPath); File bgFile = new File(bgImgPath); try { BufferedImage fullBI = ImageIO.read(fullFile); BufferedImage bgBI = ImageIO.read(bgFile); for (int i = 0; i < bgBI.getWidth(); i++) { for (int j = 0; j < bgBI.getHeight(); j++) { int[] fullRgb = new int[3]; fullRgb[0] = (fullBI.getRGB(i, j) & 0xff0000) >> 16; fullRgb[1] = (fullBI.getRGB(i, j) & 0xff00) >> 8; fullRgb[2] = (fullBI.getRGB(i, j) & 0xff); int[] bgRgb = new int[3]; bgRgb[0] = (bgBI.getRGB(i, j) & 0xff0000) >> 16; bgRgb[1] = (bgBI.getRGB(i, j) & 0xff00) >> 8; bgRgb[2] = (bgBI.getRGB(i, j) & 0xff); if (difference(fullRgb, bgRgb) > 255) { return i; } } } } catch (Exception e) { return 0; } finally { fullFile.delete(); bgFile.delete(); } return 0; } private static int difference(int[] a, int[] b) { return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]); } /** * // 執行 JS 代碼並生成圖片 * * @param driver * @param jsString * @param input * @return圖片路徑 */ public static String getImgByJs(WebDriver driver, String jsString) { try { String imgFilePath = "c://GeeTest_" + System.currentTimeMillis() + "_" + (Math.random() * 9 + 1) * 100000 + ".jpg"; String imgInfo = ((JavascriptExecutor) driver).executeScript(jsString).toString(); if (imgInfo != null && imgInfo.contains("data")) { imgInfo = imgInfo.substring(imgInfo.indexOf(",") + 1); ByteArrayOutputStream outputStream = imgStrToFile(imgInfo); if (outputStream != null) { byte[] picBytes = outputStream.toByteArray(); outPicToFile(picBytes, imgFilePath); return imgFilePath; } } return null; } catch (Exception e) { return null; } } /** * 將base64字節碼轉byte輸出流 * * @param imgBase64Str * @return */ private static ByteArrayOutputStream imgStrToFile(String imgBase64Str) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { if (imgBase64Str != null) { BASE64Decoder decoder = new BASE64Decoder(); byte[] data = decoder.decodeBuffer(imgBase64Str); outputStream.write(data); outputStream.flush(); } return outputStream; } catch (Exception e) { return null; } } /** * 圖片流轉圖片 * * @param o * @param imgFilePath */ private static void outPicToFile(Object o, String imgFilePath) { if (o == null) return; try { if (o instanceof byte[]) { // 轉為圖片 if (((byte[]) o).length == 0) return; File imgFile = new File(imgFilePath); // byte數組到圖片 FileImageOutputStream imageOutput = new FileImageOutputStream(imgFile); imageOutput.write((byte[]) o, 0, ((byte[]) o).length); imageOutput.close(); } else { return; } } catch (Exception e) { } } /** * 模擬人工移動 * * @param driver * @param element頁面滑塊 * @param distance需要移動距離 * @throws InterruptedException */ public static void move(WebDriver driver, WebElement element, int distance) throws InterruptedException { int randomTime = 0; if (distance > 90) { randomTime = 250; } else if (distance > 80 && distance <= 90) { randomTime = 150; } List<Integer> track = getMoveTrack(distance - 2); int moveY = 1; try { Actions actions = new Actions(driver); actions.clickAndHold(element).perform(); Thread.sleep(200); for (int i = 0; i < track.size(); i++) { actions.moveByOffset(track.get(i), moveY).perform(); Thread.sleep(new Random().nextInt(300) + randomTime); } Thread.sleep(200); actions.release(element).perform(); } catch (Exception e) { e.printStackTrace(); } } /** * 根據距離獲取滑動軌跡 * * @param distance需要移動的距離 * @return */ public static List<Integer> getMoveTrack(int distance) { List<Integer> track = new ArrayList<>();// 移動軌跡 Random random = new Random(); int current = 0;// 已經移動的距離 int mid = (int) distance * 4 / 5;// 減速閾值 int a = 0; int move = 0;// 每次循環移動的距離 while (true) { a = random.nextInt(10); if (current <= mid) { move += a;// 不斷加速 } else { move -= a; } if ((current + move) < distance) { track.add(move); } else { track.add(distance - current); break; } current += move; } return track; } private void seleniumTest() { ChromeDriverManager manager = ChromeDriverManager.getInstance(); int status = -1; String phone = "13814389438"; try { WebDriver driver = manager.getDriver(); driver.get(INDEX_URL); driver.manage().window().maximize(); // 設置瀏覽器窗口最大化 Thread.sleep(2000); // 輸入手機號 WebElement phoneElemet = waitWebElement(driver, By.xpath("//input[@placeholder='手機號碼']"), 20); phoneElemet.clear(); for (int i = 0; i < phone.length(); i++) { char c = phone.charAt(i); phoneElemet.sendKeys(c + ""); phoneElemet.click(); } sleep(50); // 點擊獲取驗證碼 waitWebElement(driver, By.className("sendCode"), 20).click(); sleep(2000); // 完整背景圖geetest_canvas_fullbg geetest_fade geetest_absolute String fullImgJs = "return document.getElementsByClassName(\"geetest_canvas_fullbg geetest_fade geetest_absolute\")[0].toDataURL(\"image/png\");"; String fullImgPath = getImgByJs(driver, fullImgJs); // 含有缺口背景圖geetest_canvas_bg geetest_absolute String bgImgJs = "return document.getElementsByClassName(\"geetest_canvas_bg geetest_absolute\")[0].toDataURL(\"image/png\");"; String bgImgPath = getImgByJs(driver, bgImgJs); // 獲取滑動按鈕 WebElement moveElemet = waitWebElement(driver, By.className("geetest_slider_button"), 20); // 獲取滑動距離並刪除圖片 int distance = getMoveDistance(driver, fullImgPath, bgImgPath); if (distance == 0) { } // 滑動 move(driver, moveElemet, distance - 6); // 滑動結果 sleep(2 * 1000); String gtInfo = waitWebElement(driver, By.className("sendCode"), 20).getAttribute("innerHTML"); System.out.println(gtInfo); } catch (Exception e) { e.printStackTrace(); } finally { manager.closeDriver(status); } } protected static void sleep(long time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } 

3丶根據滑動距離模擬滑動

得到滑動距離之后,我們再來看下滑動軌跡,如果滑動軌跡過於規律,則很容易被識別。所以我們就將滑動軌跡貼近人類的正常操作軌跡即可。
比如:先快后慢,慢慢對准缺口。在缺口處左右晃動。在缺口處停留,欣賞成果等。

四丶結果展示

在這里插入圖片描述

五丶結果分析

目標:

識別陰影位置,推算出對應滑動距離,模擬滑動。

實現思路:

1.獲取到兩張圖片(完整圖、缺口圖)
2.處理圖片,得到陰影位置並計算滑動距離
3.根據滑動距離模擬滑動

識別耗時:

15 - 50毫秒

通過率:

>95%

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM