許多網站在接受HTTP輸入的時候常用到一項認證碼技術來防止DOS攻擊。簡單地說就是在顯示提交資料頁面之前或者同時,伺服器會向用戶端發送一個小圖片,該圖片通常由一些隨機的數字或字母(很少情況下會有其他字元)組合而成,並要求在HTTP的提交表單(FORM)中正確輸入該數字,無效的輸入會使資料提交失敗。這樣的機制可以有效阻止或降低一些登陸/張貼/投票的自動機對網站產生的負面作用。
作者:doublelee
日期:2005-5-5
概述:
許多網站在接受HTTP輸入的時候常用到一項認證碼技術來防止DOS攻擊。簡單地說就是在顯示提交資料頁面之前或者同時,伺服器會向用戶端發送一個小圖片,該圖片通常由一些隨機的數字或字母(很少情況下會有其他字元)組合而成,並要求在HTTP的提交表單(FORM)中正確輸入該數字,無效的輸入會使資料提交失敗。這樣的機制可以有效阻止或降低一些登陸/張貼/投票的自動機對網站產生的負面作用。
早期的認證圖片是一個圖片庫的形式存放在伺服器端,每次隨機選取一幅圖片發送到用戶端。對這樣的認證機制有一個相當簡單的攻擊方法就是,攻擊者可以用自動機經過一定時間窮舉,下載所有的圖片並經過人工辨認,存入攻擊者的資料庫,攻擊時,根據得到的圖片或者甚至是其校正和,在資料庫中查詢其對應的認證碼即可。對於一個4位元字的情況,攻擊者只要儲存一萬張圖片即可,這其實是一個很小的工作量。
針對上述攻擊方法,現在有很多jsp,php,cgi的指令碼可以動態產生這樣的認證圖片,即先隨機產生認證碼,再根據認證碼動態產生圖片。這樣同樣的認證數字產生的圖片完全不同,使得儲存圖片庫的攻擊方式失效。但也許由於技術原因限制,動態產生的圖片在字型變形方面通常做的很差,大多數甚至完全沒有變形機制,這樣的情況下,編寫程式識別這樣的圖片也就成為了可能。
本文就以國內某網站為例,談一下編寫這樣的圖片自動識別工具的思路。
本文:
首先圖片格式不外乎bmp,jpg,gif,可以通過開來源程式全部轉換為統一格式,故不予考慮。這裡只考慮bmp格式作為目標,因為這是最簡單和易於處理的格式。
現在我們來分析圖片的構成,發出了50個請求之後,我們得到了這樣50個圖片,其中包含一對同樣數字圖片 7208
經過分析,圖片內容為4位元字,不含字母或其他符號。圖片格式為bmp,所有圖片具有統一的大小,寬,高。經過查詢bmp檔案格式文檔,發現其調色盤也完全相同,以2位元組代表一個象素。另外還發現圖片所有的數字所處的相對位置固定,大小固定,字型固定,變化的只是顏色和背景中一些隨機的黑色斑點。而且顏色上,也許是考慮色盲使用者的存在,並沒有過於鮮豔的顏色,也就是說基本上數字輪廓內的顏色以深色高灰階為主,變化並不大。背景中的斑點也是一樣。數目約40個象素左右(圖片共17*60=1020象素)
進一步分析,確定各數字所處的位置,所佔的象素。由於bmp檔案格式很簡單,這個工作細心一點很快就可以完成。得到資料如下:
//圖片高17,寬60,以下x,y是第一象限座標
//每個數字高13,寬10,字間間隙為3
//起始座標依次在(x,y) = (7,1), (20,1), (33,1), (46,1)
//每個象素佔用2個位元組
根據以上這些資料,可以在程式中輕鬆定位每一個數字並提取出代表該位置數位所有象素的資料資訊,這些資訊可以存在一個2*13*10=260位元組的一個數組裡。
下面的問題是如何準確判斷這260個位元組資料代表的數字,這涉及到一個模糊比對的問題。好在在本例中,匹配目標只有十個,幹擾元素影響也不是很大。經過幾次摸索,我認為產生10個標準的匹配目標即可,這個匹配目標我稱之為數字模板(module),共10個,分別代表0~9數位典型灰階分布,每個數字模板也佔用260個位元組。這樣以後將每次得到的260位元組資料與之比較,差異最小的即認為最匹配即可。
現在產生兩個問題,一,模板檔案哪裡來?二,比較函數用什麼公式?
對於第一個問題,我可不想260個位元組一個位元組一個位元組地手工尋找和填寫,2600次,很累的也!那麼想辦法寫程式統計。最開始我是統計所有的相同數字,取平均值,例如每看到一個0,假設前面已經統計過k個0,模板中的對應位元組的值是前k個數字0統計的平均值,那麼看到第k+1個0的圖片p0(k+1)之後,修正模板為
m0(k+1)=(m0(k)*k+p0)/(k+1)
但是隨即發現問題,對於不同的顏色,p0(k)的差異很大,平均值不能體現問題。更為嚴重的是,作為整數,這樣的包含除法的平均值計算公式並不好用,誤差十分的大,經過一些輪次的計算後,模板資料變的一塌糊塗,新資料也很難以起作用了。
再次對資料進行仔細地觀察,發現深色象素處,高位位元組的值一般小於0xc0,但具體的值由顏色決定,差異很大,但淺色象素的高位位元組大於0xc0,肉眼觀察圖片實際上得到的印象只由象素的灰階決定,具體顏色並不要緊。因此我作了一個修正,就是取0xc0為閾值,大於此數的取1,小於此數的取0,用加法修正到模板對應的位元組上,同時對每個模板增加一個記數器module_weight,記錄模板是統計過多少個源圖片而得來的。並且如果統計到255個就不再統計。保證各模板的資料基本權重相等。後來證明這個做法很有效。
第二個問題,我開始想使用各位元組差的絕對值之和,即
Sigma(abs(x(i)-m(i))) i=0,1,...259
來表示,這裡x(i)表示要判斷的圖片的第i個位元組,m(i)表示模板中第i個位元組的值。
後來發現這個數位值受顏色影響比較大,那麼我們需要增大每一對象素差異對整體比較函數的影響。為了達到這一目的,除了修改模板檔案以外,還必須對判斷公式進行調整。如果學過機率論的一些基本知識就知道,統計其二階中心矩,也就是方差將是一個不錯的選擇。
現在公式調整為
Sigma ((x-m)^2)
好了,基本問題都解決了。調整程式試試吧。注意到每個象素是2位元組,即一個short型資料,兩個short型資料的差的平方應該用int型變數記錄,這樣260個int變數加起來會溢出的。為了程式簡潔,不處理大整數,我將每個象素的2位元組分開處理,其平方用short即可記錄,260個unsinged short變數的和,哈哈,unsigned int怎麼也不會溢出了吧。
搞定!
程式:
以下是c的原始碼,還不到200行,處理bmp檔案格式,包含了產生模板的功能,即study函數
//-------------------------------------------------------------------------------------------
// File Name: myocr.c
// special crafted for xxxx.com's image.
// by doublelee@etang.com
#include
#include
#define MOD_FILE "digit.mod"
unsigned char map[4][260]; //每圖4個數字,每個數字用260個byte表示
unsigned char module[10][260]; //模板10個,代表0~9,也用260 bytes.
unsigned char buf[0x083a]; //檔案大小,固定
unsigned char module_weight[10];//訓練次數,增到255就不再增
unsigned int diff, tmpdiff ; //diff表示該數字與模板的相似性,現在用 sigma ((x-m)^2) 判斷。
//tmpdiff表示到現在為止最相似的值。越小越相似
char value[4]={0}; //儲存結果,值必定在0~9之間
void readmap() //從buf的相應位置讀入圖片資料
{
int i; //第i個數字
int x,y; //座標
for(i=0; i<4; i++)
for(x=0; x<20; x++)
for(y=0; y<13; y++)
{
map[i][x+y*20] = buf[ 0x42 + (7+13*i+60*(y+1))*2 + x ];
//圖片高17,寬60,x,y是第一象限座標
//每個數字高13,寬10,字間間隙為3
//起始座標依次在(x,y) = (7,1), (20,1), (33,1), (46,1)
//每個象素要2個位元組,因此有以上公式,0x42是BMP頭長
//其實還可以改進,每個象素只判斷高位位元組(和灰階關係比較大?),準確性應該還可以提高
//現在的測試結果已經比較令人滿意
}
return;
}
void outputvalue() //輸出結果
{
int i;
for(i=0;i<4;i++)
fprintf(stdout, "%d", value[i]);
return;
}
int read_mod()
{
FILE* fp;
fp = fopen(MOD_FILE, "r+b");
if(!fp)
{
memset(module, 0, 10*260);
memset(module_weight, 0, 10);
return 0;
}
fread(module, 10, 260, fp);
fread(module_weight, 10, 1, fp);
fclose(fp);
return 1;
}
int write_mod()
{
FILE* fp;
fp = fopen(MOD_FILE, "w+b");
if(!fp)
return 0;
fwrite(module, 10, 260, fp);
fwrite(module_weight, 10, 1, fp);
fclose(fp);
return 1;
}
int checkbmp(FILE* fd)
{
int x;
if(!fd)
return 0;
fread(&x, 1, sizeof(int), fd);
if(x!=0x083a4d42) return 0;
fseek(fd, -1, SEEK_END);
x = ftell(fd);
if(x+1!=0x083a)
return 0;
//以上是基本檢查,其實可以檢查更多的BMP頭結構欄位
rewind(fd);
fread(buf, 1, 0x083a, fd);
readmap();
return 1;
}
void checkdigit() //判斷函數
{
int i,j,k;
unsigned char x;
for (i=0;i<4;i++)
{
diff = 0;
tmpdiff = 0xffffffff;
for (j=0;j<10;j++)
{
diff = 0;
for (k=0;k<260;k++)
{
x = module[j][k]>map[i][k] ?
(module[j][k]-map[i][k]) :
(map[i][k]-module[j][k]);
diff += x*x;
}//計算 Sigma ((x-m)^2)
if(diff{
value[i] = j;
tmpdiff = diff;
}
}//遍曆完10個模板
}//4個數字全部解決
}
int study() //訓練函數
{
int i,j;
for(i=0;i<4;i++)
{
if(module_weight[value[i]] != 0xff)
{
for(j=0;j<260;j++)
{
module[value[i]][j] += (map[i][j]>0xc0? 1:0 );
}
module_weight[value[i]]++; //又訓練完一次
}
}
return 0;
}
int main(int argc, char** argv)
{
FILE* fd;
int i,ret ;
read_mod();
if(argc == 1)
fd = stdin;
if(argc > 1)
{
fd = fopen(argv[1],"rb");
if(!fd)
{
fprintf(stderr, "open file %s failed ", argv[2]);
return 0;
}
}
if(argc > 2) //後跟訓練數字
{
for(i=0;i<4;i++)
{
value[i]=argv[2][i]-0x30;
if(value[i]>9 || value[i]<0)
{
fprintf(stderr, "wrong number %s ", argv[2]);
return 0;
}
}
checkbmp(fd);
ret = study();
write_mod();
return ret;
}
if(checkbmp(fd))
{
checkdigit();
outputvalue();
}
if(fd) fclose(fd);
return 1;
}
//----------------------------------------------------------------------------------------------------------------
這個程式維護和依賴一個模板檔案digit.mod,如果只有一個參數,則認為是bmp檔案名稱,根據模板檔案判斷其所代表的數字,例如
myocr 2110.bmp
如果有兩個參數,則認為進入學習模式,根據argv[1]代表的bmp檔案和argv[2]代表的數字,對模板檔案digit.mod進行修改。例如
myocr 2110.bmp 2110
我寫了一個bat檔案執行所有的訓練,多次訓練結束後,digit.mod檔案中所有的module_weight都變為0xff,就可以了。
以上是針對某個特定網站的圖片識別程式,對其中的參數稍加調整就可以對很多這樣的圖片實現程式辨認。
結論:
縱觀整個認證碼技術體系,現在基本可以分為靜態圖片庫技術和動態產生技術。
靜態圖片庫方式的確可以通過強烈的變形技術防止程式識別,但太少的圖片數目容易被窮舉,而產生這樣的圖片庫需要人工參與,否則難以保證可識別程度。所以代價偏高。
動態圖片技術由於程式複雜度原因,併兼顧到可識別性,往往不能做到很好的變形甚至沒有變形,這樣給程式實現攻擊提供了可乘之機,對這樣的動態圖片的攻擊可以總結為一句話,“用程式產生的東西是很容易被程式辨認出來的。”
事實片認證技術對防止WEB機器人確實起到了一定的作用,但是由於其實現的局限性,並不能完全依賴這種技術保證網站的安全性。