簡介
它一般是由於編程人員的疏忽造成的。
具體的講,溢出漏洞是由於程式中的某個或某些輸入函式(使用者輸入參數)對所接收數據的邊界驗證不嚴密而造成。
根據程式執行中堆疊調用原理,程式對超出邊界的部分如果沒有經過驗證自動去掉,那么超出邊界的部分就會覆蓋後面的存放程式指針的數據,當執行完上面的代碼,程式會自動調用指針所指向地址的命令。
根據這個原理,惡意使用者就可以構造出溢出程式。
何謂溢出漏洞
溢出原理
其實溢出原理很簡單(我以前以為很難理解,太菜了,o(∩_∩)o…)。當然,這裡為了讓大家容易理解,會引用一些程式實例(如果沒有編程基礎的,可以略過程式不看,影響不大,還是能理解的),而且說得會比較通俗和簡單,不會太深入。
從書上找來找去,終於找到一個適合的程式(汗!要找符合的程式簡單啊,但是要找特級菜鳥覺得特別簡單的程式就不多了,55~~)。大家看看下面這段程式:
#include “stdafx.h”
#include “string.h”
#include “stdio.h”
char buf[255],pass[4]; /*聲明變數,讓計算機分配指定的記憶體*/
int main (int argc,char* argv[ ])
{
printf(“請輸入您的密碼:”); /*指定輸出的字元*/
scanf(%s,buf); /*輸入一個字元串,保存在變數buf中*/
strcpy(pass,buf); /*把字元串buf中的字元串複製到變數pass中*/
if (strcmp(pass,”wlqs”)= =0) /*比較輸入的字元串是否為密碼*/
printf (“輸入正確!”);
else printf(“輸入錯誤!);
return 0;
}
(註:“/*”中的中文是對程式的註解)
這是一段密碼驗證程式,與我們平時輸入密碼一樣,先讓用戶輸入密碼,然後在取得真正的密碼,與之對比,如果差異為0,則輸出密碼正確,否則輸出密碼錯誤。很多帳號登錄的程式都是這樣做的,看起來沒有非常合理,其實不然,它有一個致命缺陷!這個漏洞很容易就看出來了。那就是它給數據申請了4個位元組的儲存空間,但是萬一用戶輸入的數據不只4個位元組,那么剩餘的位元組存放在哪裡?
先舉個例子,有一條一米長的木頭,有一張紅色紙條從尾巴往頭貼,上面寫有字,然後又有一張藍色紙條,上面也寫有字,要從木頭的頭往它的尾巴貼,但是貼了紅色紙條過後只剩4cm的長度,貼完後會有人讀出後面96cm的字,並且執行字條的命令,但是藍色紙條卻有10cm的長度,怎么辦呢?只有把藍色紙條剩下的部分貼在紅色紙條上了。那么紅色紙條的一些字就被覆蓋了。但是那個人還是會去讀那後面96cm的字,所以他就只有讀錯,前面讀的都是藍色字條的字。先前去執行的是藍色字條後面6cm的命令。
當然大家看了這個例子也不是很懂,下面來註解一下:
人——CPU
紅色字條上的字——CPU要執行的命令
4cm的長度——計算機為數據申請的記憶體空間
藍色字條上的字——要儲存的數據
可以看見藍色字條已經覆蓋了紅色字條上的字,然而那個人還是必須讀出後面96cm的字並執行。後面已經不是規定的命令了!他根本就不能執行,根本讀不懂!那么他就不能執行了,並且報錯。
如圖系統只為我的密碼分配4個位元組的記憶體,那么我輸入的密碼是“714718366”循環了6次的,不只4個位元組吧,其他剩下的字元將溢出!剩下的數字將占用記憶體空間,那么系統執行命令的時候將會執行占用記憶體的數據,而不是執行原先寫好的命令了!這些數字系統根本就讀不懂,如何執行?那么它只好報錯了!說此程式遇到問題需要關閉。那么計算機上的程式將出錯而無法執行或關閉。
本地溢出
上面所說的本地計算機因數據溢出而關閉程式或無法執行就叫做本地溢出。輸入超長的數據已經把計算機要執行的代碼覆蓋掉了,可是,計算機不會管指令有沒有被更改,依舊取原先存放指令的空間裡的數據來運行,取到“shujucuole!shujucuole!shujucuole!”這些不合法的溢出數據,它依舊會執行,可是在計算機里這樣的指令是非法指令,也就是不符合計算機邏輯的指令,用戶執行它的時候就會出錯,於是程式就被強行關閉了。
題外話:(想來想去,還是說一說o(∩_∩)o…我的愛好……損人利己的愛好)利用這樣的溢出漏洞可以關閉很多程式,比如各學校機房裡安裝的那些遠程教育系統,學生的計算機被教師的計算機所控制是因為學生機上安裝有一個學生端程式,教師機可以通過教師端來對學生端進行遠程控制,學生端沒有退出功能,學生所在的用戶組也沒有強行結束進程的許可權,當學生不想被老師控制的時候,可以打開學生端自帶的遠程訊息功能,在訊息里輸入很長的數據,比如幾百上千句“敢控制我!看我不宰了你!”,然後傳送,就可以令學生端程式出錯而被系統強行關閉。這招對某些網咖的收費系統也有用的!^_^
遠程溢出
再舉個列子:
#include “stdafx.h”
#include <winsock.h>
#pragma comment(lib,”ws2_32”)
int main(int argc,char* argv[ ])
{
char buf[255]=” ”,pass[4]=” ”; //聲明變數,讓計算機分配記憶體
//================================================================
//這節的代碼功能是初始化網路連線
//並偵聽1234連線埠等待連線
//沒有編程基礎的特級菜鳥可以略過不看
SOCKET sock1,sock2;
struct sockaddr_in addr1;
struct sockaddr_in addr2;
addr1 .sin_addr.s_addr=INADDR_ANY;
addr1 .sin_family=AF_INET;
addr1 .sin_port=htons(1234);
WSADATA * wsadatal=new WSADATA( );
WSAStartup(MAKEWORD(2,2),wsadatal1);
sock1=socket(AF_INET,SOCK_STREAM,0);
bind(sock1,(sockaddr *)&addr1,sizeof(struct sockaddr) );
listen(sock1,10);
int iSin=sizeof(struct sockaddr_in);
//=================================================================
if(sock2=accept(sock1,(sockaddr *)&addr2,&iSin)
{//有用戶連線進來
send(sock2,“請輸入密碼,密碼正確,則告訴你我的qq:”,36,0);
//傳送提示用戶輸入密碼
if (recv(sock2,buf,255,0))
{//接受用戶傳送過來的數據並保存在緩衝buf變數里
strcpy (pass,buf);//把緩衝buf變數里的數據複製到pass變數中
if(strcmp(pass,”wlqs”= =0)
//比較pass變數里的數據跟“wlqs”字元串之間的差異是否為0
{//差異為0,則說明兩者相等,密碼正確
send(sock2,”714718366”,9,0);//傳送QQ號給用戶
}
else
{//否則就說明密碼錯誤
send (sock2,”密碼錯誤!”,10,0);
}
}
}
//=================[/ft]關閉網路連線並退出=======================
closesocket(sock2);
closesocket(sock1);
return 0;
}
這是一個伺服器程式,當有用戶連線的時候,它會先傳送一句話,提示用戶輸入登錄密碼。其實它和前面說的本地溢出例子形似,問題也就處在把數據從快取複製到記憶體的那句代碼里,如果遠程用戶輸入的密碼太長,那么同樣出現溢出的現象。那么程式就會出錯,服務端將被強行關閉。
比如騰訊公司的即時通訊軟體服務端程式就曾被黑客不停地攻擊導致服務端崩潰,不能正常提供服務,致使很多用戶都不能登入,即使登入成功也會在幾分鐘之內再次掉線,就是因為他們的服務端有這樣的漏洞存在,被別人利用了,這給他們以及他們的客戶造成了不可估計的損失。
相關資料
緩衝區溢出漏洞攻擊方式
緩衝區溢出漏洞可以使任何一個有黑客技術的人取得機器的控制權甚至是最高許可權。一般利用緩衝區溢出漏洞攻擊root程式,大都通過執行類似“exec(sh)”的執行代碼來獲得root 的shell。黑客要達到目的通常要完成兩個任務,就是在程式的地址空間裡安排適當的代碼和通過適當的初始化暫存器和存儲器,讓程式跳轉到安排好的地址空間執行。
在程式的地址空間裡安排適當的代碼
在程式的地址空間裡安排適當的代碼往往是相對簡單的。如果要攻擊的代碼在所攻擊程式中已經存在了,那么就簡單地對代碼傳遞一些參數,然後使程式跳轉到目標中就可以完成了。攻擊代碼要求執行“exec(‘/bin/sh’)”,而在libc庫中的代碼執行“exec(arg)”,其中的“arg”是個指向字元串的指針參數,只要把傳入的參數指針修改指向“/bin/sh”,然後再跳轉到libc庫中的回響指令序列就可以了。當然,很多時候這個可能性是很小的,那么就得用一種叫“植入法”的方式來完成了。當向要攻擊的程式里輸入一個字元串時,程式就會把這個字元串放到緩衝區里,這個字元串包含的數據是可以在這個所攻擊的目標的硬體平台上運行的指令序列。緩衝區可以設在:堆疊(自動變數)、堆(動態分配的)和靜態數據區(初始化或者未初始化的數據)等的任何地方。也可以不必為達到這個目的而溢出任何緩衝區,只要找到足夠的空間來放置這些攻擊代碼就夠了。
控制程式轉移到攻擊代碼的形式
緩衝區溢出漏洞攻擊都是在尋求改變程式的執行流程,使它跳轉到攻擊代碼,最為基本的就是溢出一個沒有檢查或者其他漏洞的緩衝區,這樣做就會擾亂程式的正常執行次序。通過溢出某緩衝區,可以改寫相近程式的空間而直接跳轉過系統對身份的驗證。原則上來講攻擊時所針對的緩衝區溢出的程式空間可為任意空間。但因不同地方的定位相異,所以也就帶出了多種轉移方式。
(1)Function Pointers(函式指針)
在程式中,“void (* foo) ( )”聲明了個返回值為“void” Function Pointers的變數“foo”。Function Pointers可以用來定位任意地址空間,攻擊時只需要在任意空間裡的Function Pointers鄰近處找到一個能夠溢出的緩衝區,然後用溢出來改變Function Pointers。當程式通過Function Pointers調用函式,程式的流程就會實現。
(2)Activation Records(激活記錄)
當一個函式調用發生時,堆疊中會留駐一個Activation Records,它包含了函式結束時返回的地址。執行溢出這些自動變數,使這個返回的地址指向攻擊代碼,再通過改變程式的返回地址。當函式調用結束時,程式就會跳轉到事先所設定的地址,而不是原來的地址。這樣的溢出方式也是較常見的。
(3)Longjmp buffers(長跳轉緩衝區)
在C語言中包含了一個簡單的檢驗/恢復系統,稱為“setjmp/longjmp”,意思是在檢驗點設定“setjmp(buffer)”,用longjmp(buffer)“來恢復檢驗點。如果攻擊時能夠進入緩衝區的空間,感覺“longjmp(buffer)”實際上是跳轉到攻擊的代碼。像Function Pointers一樣,longjmp緩衝區能夠指向任何地方,所以找到一個可供溢出的緩衝區是最先應該做的事情。
植入綜合代碼和流程控制
常見的溢出緩衝區攻擊類是在一個字元串里綜合了代碼植入和Activation Records。攻擊時定位在一個可供溢出的自動變數,然後向程式傳遞一個很大的字元串,在引發緩衝區溢出改變Activation Records的同時植入代碼(權因C在習慣上只為用戶和參數開闢很小的緩衝區)。植入代碼和緩衝區溢出不一定要一次性完成,可以在一個緩衝區內放置代碼(這個時候並不能溢出緩衝區),然後通過溢出另一個緩衝區來轉移程式的指針。這樣的方法一般是用於可供溢出的緩衝區不能放入全部代碼時的。如果想使用已經駐留的代碼不需要再外部植入的時候,通常必須先把代碼做為參數。在libc(熟悉C的朋友應該知道,現在幾乎所有的C程式連線都是利用它來連線的)中的一部分代碼段會執行“exec(something)”,當中的something就是參數,使用緩衝區溢出改變程式的參數,然後利用另一個緩衝區溢出使程式指針指向libc中的特定的代碼段。
程式編寫的錯誤造成網路的不安全性也應當受到重視,因為它的不安全性已被緩衝區溢出表現得淋漓盡致了。