懟就完事了,總結幾種驗證碼的解決方案

  • 2020 年 1 月 14 日
  • 筆記

點擊上方「鹹魚學Python」,選擇「加為星標」

第一時間關注Python技術乾貨!

截止到今天鹹魚已經寫了很多期關於 Js 逆向的文章,不過這麼多的文章都有一個共同點,都是關於加密參數或者密碼加密的解析,很多讀者在後台私信希望能夠出一些關於滑動驗證或者人機驗證的分析教程。

於是鹹魚總結了目前遇到過的的驗證碼類型以及總結出來的相關處理方式和大家聊一聊。

現在市面上的驗證碼的類型大致有下面幾種類型。

圖形驗證碼

比較常見的英文數字組合成的圖形驗證碼,常常輔以各類干擾線扭曲圖片中的內容達到提高混淆難度的目的,並且通過加長圖片中的文字長度提升識別成本。

7位英數-難度高

4位英數-難度中等

4位英數-難度低

像這類驗證碼的處理方案有很多種,簡單給大家概括一下。

難度中低的兩類驗證碼,安裝 tesserocr,通過 OCR 技術結合 Python 的 tesserocr 庫可以就可以完成識別。如果驗證碼中帶有簡單幹擾線可以使用灰度和二值化的方法提高代碼的識別率。

常用示例代碼:

import tesserocr  from PIL import Image  image = Image.open('code2.jpg')  image = image.convert('L')  threshold = 127  table = []  for i in range(256):      if i < threshold:          table.append(0)      else:          table.append(1)  image = image.point(table, '1')  result = tesserocr.image_to_text(image)  print(result)

難度較高的多位英數+扭曲的圖形驗證碼包括上面總結的中低難度的圖形驗證碼,可以通過 TensorFlow 訓練的方式達到識別驗證碼的目的。

之前我有一個系列文章介紹了整個訓練部署的流程,大家可以點擊參考。

大佬說 | 寫給程序員的TensorFlow教程-準備篇

大佬說 | 寫給程序員的TensorFlow教程-編碼篇

大佬說 | 寫給程序員的TensorFlow教程-上線篇

使用這個方式的朋友記得要先準備好足夠的驗證碼的樣本,只要你的模型不是太差,通過足量的樣本,不斷調優是可以達到一個較為可觀的識別率。

目前體驗過最好的程序是冷月的四位英數,識別成功率高達 99.99% ,不過據知情人透露整個訓練的樣本達到了 6000 W ,耗費的時間精力可想而知。

旋轉驗證碼

這類驗證碼是將驗證碼的圖片旋轉並且需要用戶拖動下方滑塊完成將圖片擺正的操作才可以完成驗證。

旋轉驗證碼

不過某家的這個驗證碼有一些小小的 bug,依靠勞苦大眾的智慧,我在 GitHub 上發現了一個很 Nice 的項目。

項目地址:https://github.com/scupte/xuanzhaunyanz

因為圖庫的容量問題,沒有超大的圖庫作為後盾,將全部的原圖抓取下來對比完全可以得到旋轉的角度了。

不過鑒於該家的驗證碼並沒有普及所以了解一波即可。

部分對比代碼:

# -*- coding: utf-8 -*-  import cv2  import numpy as np    imagepath = '9_1.png'  img = cv2.imread(imagepath)  gray = cv2.cvtColor ( img , cv2.COLOR_BGR2GRAY )  ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)    contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)  #cv2.drawContours(img,contours,-1,(0,0,255),1)  for cnt in contours:        # 最小外界矩形的寬度和高度      width, height = cv2.minAreaRect(cnt)[1]        if width* height > 100:          # 最小的外接矩形          rect = cv2.minAreaRect(cnt)          box = cv2.boxPoints(rect)  # 獲取最小外接矩形的4個頂點            box = np.int0(box)          print box            if 0 not in box.ravel():                #繪製最小外界矩形              for i in range(4):                  cv2.line(img, tuple(box[i]), tuple(box[(i+1)%4]), 0)  # 5              theta = cv2.minAreaRect(cnt)[2]              if abs(theta) <= 45:                  print('圖片的旋轉角度為%s.'%theta)                  #     angle = theta  print theta  cv2.imshow("img", img)  cv2.waitKey(0)  

滑動驗證碼

說到滑動驗證碼,一定一定要提某驗,雖然說市面上關於滑動驗證碼的產品有很多,但是某驗的地位就像 10 年前腦白金在保健品市場的地位一樣,業界標杆啊。

它越牛逼,市場上用它做防護的網站也越多,像國家企業信用信息公示系統、B 站、狗東等等。

像某驗的解決方案也有很多,不過原理大同小異。

selenium 模擬滑動

使用 selenium 這個大家都聽過,步驟大致是將缺口圖和原圖進行對比獲取缺口的橫坐標,並使用計算完成拖動軌跡模擬,之後使用 selenium 按照軌跡滑動完成缺口的拼接。

這一類方法的優點是門檻低,原理簡單,缺點是完成滑動耗時較長,成功率無法估計(同一軌跡計算規則使用多次後成功率迅速下降)

常見的軌跡生成代碼:

import numpy as np  import math      def ease_out_expo(x):      """      曲線函數      :param x:      :return:      """      if x == 1:          return 1      else:          return 1 - pow(2, -10 * x)      def get_tracks(distance, seconds):      """      軌跡生成函數      :param distance: 滑動總距離      :param seconds: 滑動總時間      :return:      """      tracks = [0]  # 存放軌跡的數組      offsets = [0]  # 存放滑動總距離的記錄數組      for t in np.arange(0.0, seconds, 0.1):  # 產生一個數列如[0.0, 0.1, 0.2, 0.3]          offset = round(ease_out_expo(t/seconds) * distance)  # 根據時間t計算在曲線上的滑動距離          tracks.append(offset - offsets[-1])  # 本次計算的距離減去上一次移動的距離,得到本次的軌跡          offsets.append(offset)  # 至本次滑動了的總距離      return offsets, tracks      a, b = get_tracks(138, 3)  print(a, b)      def get_tracksb(distance):      """      根據物理的先加速再減速規律計算      :param distance:      :return:      """      distance += 20  # 加上20是為了滑動超過缺口再回滑      v = 0  # 初速度      t = 0.2  # 以0.2秒為一個計算周期      forward_tracks = []  # 軌跡記錄數組      current = 0  # 初始移動距離      mid = distance * 3 / 5  # 減速閥值即五分之三的距離加速剩下距離減速      while current < distance:  # 總移動距離等於輸入距離時結束          if current < mid:  # 加速狀態              a = 2  # 加速度為+2          else:  # 減速狀態              a = -3  # 加速度-3            s = v * t + 0.5 * a * (t ** 2)  # 計算0.2秒周期內的位移          v = v + a * t  # 計算本次周期後的速度          current += s  # 將之前移動的總距離,加上本次0.2秒周期內移動的距離          forward_tracks.append(round(s))  # 記錄本次0.2秒周期內的移動距離為軌跡        back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1]  # 手動將開頭加上的20,生成減去軌跡,即回滑軌跡      return {'forward_tracks': forward_tracks, 'back_tracks': back_tracks}

Js 破解關鍵的參數

這類方法的門檻就比較高了,通過斷點調試 Js,逆向分析滑動後提交參數的生成邏輯完成參數的生成,之後構造請求完成提交,當然這中間也是需要分析圖片的缺口位置與模擬軌跡,不過沒有使用到模擬所以速度快成功率高。

缺點是風險高,代碼維護成本高,更新一個新版本就要重新分析而且逆向相關產品的代碼是有一定的法律風險的,免費吃住也不是開玩笑的,所以很多能夠商業化的大佬們都悶聲發大財不會到處張揚。

使用現有的服務

上面兩種方法各有各的優缺,很多人就想把這一塊的工作量與風險分出去,這就要使用到第三方的服務商了。

不過目前國內市場上的服務商並沒有這類服務,目前鹹魚在使用的是一家俄羅斯的服務商 – 2Captcha

這個服務商提供的驗證碼服務有很多種,其中包含了我們比較關心的 GeeTest 。

每1000次的價格

下面鹹魚給大家簡單介紹下如何使用服務。(不要問為啥收費,人家服務商也要吃飯,況且這個價格實在便宜了)

首先,註冊一個賬號,官網是 http://2captcha.com/zh

首頁

完成註冊之後會跳轉到控制台界面,這裡最重要的是獲取到屬於你的 API Key 。

API KEY

好,拿到這個 API Key 之後就可以上手使用服務完成滑動的破解了。

通過參考官方的 API 文檔,我們只需要構建兩個 Get 請求就可以了。

第一個 Get 請求的組成是這樣的:

https://2captcha.com/in.php  ?key= 上面獲取的API KEY  &method=geetest  &gt= 某驗參數  &challenge= 某驗參數  &api_server=api-na.geetest.com(可選)  &pageurl= 滑動驗證碼所在的網頁地址

參數列表:

參數名

參數介紹

key

API KEY

method

表示驗證碼類型

gt

某驗參數1

challenge

某驗參數2

api_server

api-na.geetest.com(選填)

pageurl

滑動驗證碼所在的網頁地址

這裡解釋下關於 gtchallenge 這兩個參數的獲取。

第一個請求中這兩個參數其中 gt這個參數是固定的,找一個使用某驗的網站就可以獲取。例如:

challenge這個參數是由請求返回,你找到這個請求之後按照請求重新獲取一次,如果是 XHR 的話也可以直接 replay XHR 。

完成參數構建,提交完第一個請求之後,成功會返回類似下面的結果。

OK|2122988149 or as JSON {"status":1,"request":"2122988149"}

這裏面的一串數字就是會話 ID。

有了這個會話 ID 之後我們就可以構建下一個請求,記住兩個請求中間需要等待一些時間哦。

https://2captcha.com/res.php  ?key=API KEY  &action=get  &id=2122988149

參數列表:

參數名

參數介紹

key

API KEY

action

Get

id

上一個請求返回的會話ID

這個請求返回的結果就是我們需要的加密參數。

{        "challenge":"1a2b3456cd67890e12345fab678901c2de",        "validate":"09fe8d7c6ba54f32e1dcb0a9fedc8765",        "seccode":"12fe3d4c56789ba01f2e345d6789c012|jordan"  }

以上常見的幾類驗證碼,已經全部介紹完了。

肯定有人問像 google 家的 ReCaptcha 以及和他相似的 hCaptcha 的解決方案沒有提到啊?

像以上兩類驗證碼,剛剛提到的服務商也同樣有提供接口打碼。

至於其他不依靠服務商的解決方案,目前鹹魚還沒有接觸過,畢竟這兩類驗證碼,鹹魚手動點擊都沒辦法做到一次通過,目前也只能依賴服務商了。