簡單驗證碼識別及工具編寫思路

wyzsk發表於2020-08-19
作者: crystal_lz · 2016/02/29 11:02

Author:[email protected]

注:此文章只適合簡單驗證碼,最後也將編寫的工具附上以及關鍵部分程式碼和使用說明文件

0x00 簡介


雖然驗證碼發展到如今有許多人類都難以識別的狀態了,但人有部分老系統使用的驗證碼異常的簡單。還有一些網站由於程式設計師本身的素質或者缺乏相關影像相關的知識,所以並沒有自己寫驗證碼的生成程式,而是直接在網上隨便複製貼上一個Demo級別的程式碼來用,以達到網站有驗證碼的目的,而忽略了驗證碼的強弱性,導致很多網站的驗證碼都是爆款弱驗證碼

如:

p1

還有更傻缺的比如:

p2

直接就能複製的...這種是完全不知道驗證碼的意義或者為了應付而做的驗證碼

0x01 處理方式


好吧忽略上面的圖繼續說

對於那些簡單驗證碼他們的共同點是

  1. 標準字型
  2. 背景單簡單甚至純色沒有背景
  3. 字型並沒有貼上在一起

而本文討論的就是這類的驗證碼。對於那種連背景都沒有的純色、標準字型、沒有黏貼的那種再簡單不過了直接就是100%的識別率

p3

這種就不討論了 下面來看看wooyun的驗證碼

Wooyun的驗證碼有兩種狀態

p4

一種是白色文字深色背景 一種是黑色文字淺色背景

如果只有一種無論是那種設定一個閥值都能很好的二值化 但現在的情況卻是有兩種 所以我能想到的最簡單的方式 那好 我就給出兩個閥值 對於黑色文字我就用一個較小一點的閥值 對於白色文字我就用一個較大一點的閥值

但是這樣還是會出現一個問題 白色文字二值化後背景黑色文字白色 而黑色文字二值化後背景白色文字黑色 就像下面一下

p5

可以看出上面我左邊框選區域一切正常 而右邊卻出了問題 那是因為在我寫程式的時候我認為二值化後文字都是黑色背景是白色 所以我就把黑色區域當作文字來框選就看到了如上的效果 所以說這是一個問題 不僅要二值化二值化後還要到底白色是文字還是黑色是文字

於是我又想到一種辦法 通常情況下一張圖上背景的面積都會大於文字所佔用的面積 所以在二值化的同時我還做了一件事情 二值化的同時記錄下黑點個數和白點個數 如果黑點的個數大於了白點的個數那麼我就把黑白反色一下讓黑色畫素點變成最少 這樣再把黑色畫素當作文書處理

p6

這樣做還有一個問題就是 我應該怎麼知道什麼時候應該使用那一個閥值來二值化 當然辦法可以有很多 比如當影像上深色畫素多餘淺色畫素的時候使用較大閥值 否則相反 不過我並不是這樣做的

p7

在工具上我提供了一個框讓使用者輸入驗證碼的字元個數 這樣的話我對體統的閥值挨個遍歷 二值化後去識別區域 如果框出來的區域個數是有問題的 那麼就換下一個閥值 如果所有閥值都遍歷完了還是有問題 那麼這驗證碼確實也是超出這個工具的範圍了 因為這個工具的目的是通用 對於那些需要單獨寫程式碼來識別的不在他的能力範圍內

在這之前一些驗證碼可能還需要一些處理比如很常見的一些驗證碼有邊框的

p8

左邊是沒有裁剪的邊框一起被二值化成為了黑色 然後拆字就悲劇了 右邊是裁剪掉了一個畫素的把邊框去掉了 然後就一切正常了 這種情況就不說了 都懂的

還有一種比較複雜的情況 因為二值化並不是萬能的 並不是說什麼驗證碼一進行二值化後文字和背景就出來了 下面這張圖是我以前程式需要做的百度推廣的驗證碼識別

p9

上面這張圖不怎麼能看到效果 因為都是好幾年前的事情了 驗證碼連線訪問已經是500了 這張圖都是測試的時候的截圖

我描述一下情況吧 上面的驗證碼 首先有邊框 文字 干擾線 即使能把邊框裁剪掉也找不到一個合適的閥值來把線條和文字分離 很簡單因為他的線條的顏色比文字的顏色深 如果我的閥值太小 那麼我的文字就沒有了 只會剩下一些線條在哪裡

p10

這圖為上面那張圖片上驗證碼的NZ兩個字元在ps中放大的效果(儘管上面影像原來並非儲存的png格式已經失真 但大概還是能看到點什麼的) 我也去翻了翻以前的程式碼來看 當初我二值化的時候並非直接二值化的 在二值化之前還單獨對RGB進行了判斷 程式碼截圖如下

p11

別百度推廣的驗證碼是我做的第一個驗證碼識別程式 所以我一直記得很清楚 不是一個二值化就能搞定的 所以說在這個工具中我也加入了同樣可以單獨處理RGB的功能

由於百度的這個驗證碼已經訪問不了了 所以我找了一個同樣有線條的驗證碼 但是這個驗證碼線條顏色比文字顏色淺 所以我就用預設的127作為閥值 假設二值化無法搞定

p12

用127閥值上面線條一起被黑化了 但是圖片中文字顏色接近黑色而線條顏色卻要淺一點 所以判斷的時候 可以認為RGB的平均值大於20的就視為背景 就可以這樣幹

p13

然後效果就成了這樣

p14

這樣線條就被處理掉了 不過這個驗證碼直接設定閥值就能搞定 只是為了說明所以採用127作為閥值 還有一點這個驗證碼和百度的那個 他們線條都是在文字的下方 如果是在文字上方 那麼同樣的超出了這個工具的範圍 對於線條在上方的 我想過一些處理方式 假設線條為紅色的時候 我在遍歷的時候遇到一個紅色畫素點 我就把紅色畫素設定為和他相鄰畫素的非紅色的顏色 但是我想了一想這個“相鄰”就涉及了它周圍八個畫素點 我應該取那一個畫素點的顏色 如果是在背景上還好 他周圍應該都是背景的顏色 那一個都無所謂 可是如果是線上條、背景還有文字的交界處就不好處理了 所以工具裡面暫時還沒提供這樣的功能 還有那種很難分離背景或者字黏貼在一起的但是每個文字都是一個顏色的那種 也想過一些處理方式 但是實現起來我感覺都會純在一些小問題 所以就還展示沒有做 就不扯那麼多了 等做好了再來扯 才比較有證據

0x02 拆字和識別


下面來說說驗證碼識別中的一個難點 -> 拆字

基本上在我看來 能正確的拆字 那麼就已經成功了80%了 因為剩下的就是比對的問題了 我在工具中只提供了兩種方式拆字

p15

手動新增就不用說了 我這裡的自動識別是最傳統的深度遍歷 從影像的第一個畫素點開始遍歷 因為影像已經二值化 按照我的工具的理解 就只剩下白色背景和黑色文字 所以遇到一個黑色畫素點的時候開始記錄 然後開始深度遍歷 大概效果如下

這是個GIF_p16

大概程式碼如下

p17

對於拆字還有很多其他的方式 這裡只是最普通的也是最簡單的一種 對於其他方式這個工具中並沒有提供 因為工具只針對簡單通用的驗證碼 對於那種需要單獨寫程式碼的驗證碼不考慮 而且工具上功能附加太多也就變得複雜了 其實重點就是感覺有點付出和回報不成正比 而且對於那些流傳的拆字理論知識 說起來確實簡單 但是實際做的時候才會發現 這些理論其實是存在漏洞的 只會在特定條件下才會成立 而驗證碼卻是變幻多端的 這裡也就不扯那麼多了

剩下來的就是識別了 我採用的識別方式比較簡單 就是兩張圖來對比 一張是驗證碼上面擷取出來的影像 一張是已知的樣本影像

p18

呼叫函式會返回這兩張圖的重疊的畫素的個數 這樣我把擷取出來的驗證碼字元和我所有的樣本對比一次 取出nCount最高的一個 作為結果 也就是說取出和樣本中重疊率最高的一個出來作為結果 在工具中我有兩種方式提供樣本 一種是使用系統的字型 一種是手動採集

p19

如果使用系統字型在文字框內輸入驗證碼可能出現的字元 然後點選 生成 會彈出系統對話方塊設定字型從而產生樣本 不過對於一些非標準字型 系統字型就很難搞定了 無論是標準字型還是非標準的字型都建議使用手動採集的方式 因為直接從驗證碼上擷取下來的圖怎麼說也是原配 重複的圖片工具也只會採集一次不會重複新增降低效率比對 下面就是一個非標準字型

p20

理論上來說 樣本採集越多越全 識別率就越高 反正我每次都是使用的手動採集樣本 對了這個工具只是一個配置工具而已 並不能用來做什麼其他事情 當一切都配置好了之後就可以點選工具上的 檔案 -> 儲存 將這些所有的配置 儲存成一個檔案 可以儲存為兩種後最(.ci和.ci.png) 後者以圖片儲存方便電腦上檢視

p21

而識別是另一個獨立的工具呼叫 如果是.NET則直接呼叫提供的dll來識別 之所以這樣設計是因為 我並不知道別人 會用驗證碼識別來做什麼事情 所以除了識別以外我也不知道別人想要什麼功能 所以把所有東西全部獨立出來共別人呼叫或者使用 對於識別我提供了一個命令列呼叫工具供給非.NET平臺的程式呼叫

p22

以python舉例:

#!python
# coding: UTF-8
import os
result = os.popen('verifytool.exe D:\\woo.ci.png -f D:\\woo-verify.png').readlines()
print (result)

在我的D盤有這樣一張圖

p23

這樣別人就可以自己寫指令碼去做自己愛做的事情 不過我還是建議使用-p的方式來呼叫

#!python
# coding: UTF-8
import urllib2
from socket import *

h = urllib2.urlopen('http://www.wooyun.org/captcha.php')  
str = h.read()                      #獲取驗證碼
s = socket(AF_INET,SOCK_DGRAM);
s.sendto(str,('localhost',14250))   #將獲取到的驗證碼傳送給識別程式
code = s.recvfrom(65500)            #接受識別出來的驗證碼
print(code)

p24

如果程式是.NET平臺編寫 則可直接使用VerifyReader.dll檔案 將其新增引用然後:

#!vb
CodeInfo ci = CodeInfo.LoadFromFile("D:\\woo.ci.png");
CodeHelper helper = new CodeHelper(ci);
string code = helper.GetCodeString(Image.FromFile("D:\\woo-verify.png"));

另外這裡還單獨的做了一個賬戶爆破的工具出來

p25

以下是用自己測試的結果

p26

p27

雙擊列表即可檢視資料

p28

0x03 相關連結


全套工具及核心程式碼和使用說明下載連線:http://down.future-sec.com/VerifyReader-1.1.zip

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章