緩衝區溢位技術基礎
為了提高大家的技術水平,為了更瞭解我們討論的這種技術,為了把這個論壇建成一個更更好的論壇,下面我為大家推出一系列完整的有關溢出,溢出攻擊的文章。讓大家更能瞭解到這個天天說但又不太清楚怎麼回事的東西。我想,看了這個以後大家也不再會問,為什麼我用了這個工具,怎麼沒有用呀什麼的問題?
在這裡強調一下,想完全看的懂這篇文章,至少需要具備一定的組合語言,C語言和LINUX的基礎。
緩衝區溢位”在英文中可以解釋為:buffer overflow,buffer overrun,smash the
stack,trash the stack,scribble the stack, mangle the stack, memory
leak,overrun screw;
我們通常所說的“溢出”指的是緩衝區溢位,(廢話,不然要從那裡溢出呀!)先解釋一下什麼是緩衝區——緩衝區是記憶體中存放資料的地方,是程式運行時電腦記憶體中的一個連續的塊,它儲存了給定類型的資料。問題隨著動態分配變數而出現。為了不用太多的記憶體,一個有動態分配變數的程式在程式運行時才決定給他們分配多少記憶體。當程式試圖將資料放到電腦記憶體中的某一位置,但沒有足夠空間時會發生緩衝區溢位。另一種說法,就是說程式在動態分配緩衝區放入太多的資料會有什麼現象?它會溢出,會漏到了別的地方。
一個緩衝區溢位應用程式使用這個溢出的資料將組合語言代碼放到電腦的記憶體中,通常是產生root許可權的地方。單單的緩衝區溢位,並不會產生安全問題。只有將溢出送到能夠以root許可權運行命令的地區才行。這樣,一個緩衝區利用程式將能啟動並執行指令放在了有root許可權的記憶體中,從而一旦運行這些指令,就是以root許可權控制了電腦。
所以我們更多的時候把緩衝區溢位指的是一種系統攻擊的手段,通過往程式的緩衝區寫超出其長度的內容,造成緩衝區的溢出,從而破壞程式的堆棧,使程式轉而執行其它指令,以達到攻擊的目的。據統計,通過緩衝區溢位進行的攻擊占所有系統攻擊總數的80%以上。
世界上第一個緩衝區溢位攻擊——著名的Morris蠕蟲,發生在十年前,它曾造成了全世界6000多台網路伺服器癱瘓。我這是我所知的最早的緩衝區溢位攻擊程式。
記住,造成緩衝區溢位的原因是程式中沒有仔細檢查使用者輸入的參數!!!
下面舉一個最為常見的,也是最簡單的溢出。
void function(char *str){
char buffer[16];
strcpy(buffer,str);
}
在這個例子中上面的buffer的長度被限制在16,而strcpy()將直接把str中的內容copy到buffer中。這樣只要str的長度大於16,就會造成buffer的溢出,使程式運行出錯。So,我們說這個程式溢出了。
在C語言中,靜態變數是分配在資料區段中的,動態變數是分配在堆棧段的。緩衝區溢位是利用堆棧段的溢出的。一個正常的程式在記憶體中通常分為程式段,資料端和堆棧三部分。程式段裡放著程式的機器碼和唯讀資料,這個段通常是唯讀,對它的寫操作是非法的。資料區段放的是程式中的待用資料。動態資料則通過堆棧來存放。在記憶體中,它們的位置如下:
/――――――――\記憶體低端
程式段
―――――――――
資料區段
―――――――――
堆棧
\―――――――――/記憶體高端
堆棧是記憶體中的一個連續的塊。一個叫堆棧指標的寄存器(SP)指向堆棧的棧頂。堆棧的底部是一個固定地址。堆棧有一個特點就是,後進先出。也就是說,後放入的資料第一個取出。它支援兩個操作,PUSH和POP。PUSH是將資料放到棧的頂端,POP是將棧頂的資料取出。
在進階語言中,程式函數調用和函數中的臨時變數都用到堆棧。為什麼呢?因為在調用一個函數時,我們需要對當前的操作進行保護,也為了函數執行後,程式可以正確的找到地方繼續執行,所以參數的傳遞和傳回值也用到了堆棧。通常對局部變數的引用是通過給出它們對SP的位移量來實現的。另外還有一個基址指標(FP,在Intel晶片中是BP),許多編譯器實際上是用它來引用本地變數和參數的。通常,參數的相對FP的位移是正的,局部變數是負的。
當程式中發生函數調用時,電腦做如下操作:首先把參數壓入堆棧;然後儲存指令寄存器(IP)中的內容,做為返回地址(RET);第三個放入堆棧的是基底位址暫存器(FP);然後把當前的棧指標(SP)拷貝到FP,做為新的基地址;最後為本地變數留出一定空間,把SP減去適當的數值。
比如說下面這個程式:
void function(int a, int b, int c){
char buffer1[10];
char buffer2[15];
}
void main(){
function(1,2,3);
}
假設我們在Linux下,用gcc對這段源碼進行編譯,產生彙編代碼輸出:
$ gcc -S -o example1.s example1.c
看看輸出檔案中調用函數的那部分:
pushl $3
pushl $2
pushl $1
call function
這就將3個參數推入堆棧裡了,並調用function()。指令call會將指令指標IP壓入堆棧。在返回時,RET要用到這個儲存的IP。在函數中,第一要做的事是進行一些必要的處理。每個函數都必須有這些過程(為了保護呀,不然就找不到了。):
pushl %ebp
movl %esp,%ebp
subl $20,%esp
這幾條指令將EBP,基址指標放入堆棧。然後將當前SP拷貝到EBP。然後,為本地變數分配空間,並將它們的大小從SP裡減掉。由於記憶體配置是以字為單位的,因此,這裡的buffer1用了8位元組(2個字,一個字4位元組)。Buffer2用了12位元組(3個字)。所以這裡將ESP減了20。這樣,現在,堆棧看起來應該是這樣的。
低端記憶體 高端記憶體
buffer2 buffer1 sfp ret a b c
< ------ [ ] [ ] [ ] [ ] [ ] [ ] [ ]
棧頂 棧底
那是什麼導致了溢出呢?緩衝區溢位就是在一個緩衝區裡寫入過多的資料。要怎麼樣利用呢?看下面這個程式:
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}
void main() {
char large_string[256];
int i;
for( i = 0; i < 255; i++)
large_string[i] = 'A';
function(large_string);
}
||||||這個程式是一個經典的緩衝區溢位編碼錯誤。函數將一個字串不經過邊界檢查,拷貝到另一記憶體地區。當調用函數function()時,堆棧如下:
低記憶體端 高記憶體端
buffer sfp ret *str
< ------ [ ] [ ] [ ] [ ]
棧頂 棧底
很明顯,程式執行的結果是"Segmentation fault (core
dumped)"或類似的出錯資訊。因為從buffer開始的256個位元組都將被*str的內容'A'覆蓋,包括sfp,
ret,甚至*str。'A'的十六進值為0x41,所以函數的返回地址變成了0x41414141,
這超出了程式的地址空間,所以出現段錯誤。可見,緩衝區溢位允許我們改變一個函數的返回地址,在這個例子中,我們可以通過修改0x41414141來改變程式返回後的入口地址。同樣通過這種方式,就可以改變程式的執行順序了。
存在象strcpy這樣的問題的標準函數還有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在迴圈內的getc(),fgetc(),getchar()等。
緩衝區溢位技術基礎
為了提高大家的技術水平,為了更瞭解我們討論的這種技術,為了把這個論壇建成一個更更好的論壇,下面我為大家推出一系列完整的有關溢出,溢出攻擊的文章。讓大家更能瞭解到這個天天說但又不太清楚怎麼回事的東西。我想,看了這個以後大家也不再會問,為什麼我用了這個工具,怎麼沒有用呀什麼的問題?
在這裡強調一下,想完全看的懂這篇文章,至少需要具備一定的組合語言,C語言和LINUX的基礎。
緩衝區溢位”在英文中可以解釋為:buffer overflow,buffer overrun,smash the
stack,trash the stack,scribble the stack, mangle the stack, memory
leak,overrun screw;
我們通常所說的“溢出”指的是緩衝區溢位,(廢話,不然要從那裡溢出呀!)先解釋一下什麼是緩衝區——緩衝區是記憶體中存放資料的地方,是程式運行時電腦記憶體中的一個連續的塊,它儲存了給定類型的資料。問題隨著動態分配變數而出現。為了不用太多的記憶體,一個有動態分配變數的程式在程式運行時才決定給他們分配多少記憶體。當程式試圖將資料放到電腦記憶體中的某一位置,但沒有足夠空間時會發生緩衝區溢位。另一種說法,就是說程式在動態分配緩衝區放入太多的資料會有什麼現象?它會溢出,會漏到了別的地方。
一個緩衝區溢位應用程式使用這個溢出的資料將組合語言代碼放到電腦的記憶體中,通常是產生root許可權的地方。單單的緩衝區溢位,並不會產生安全問題。只有將溢出送到能夠以root許可權運行命令的地區才行。這樣,一個緩衝區利用程式將能啟動並執行指令放在了有root許可權的記憶體中,從而一旦運行這些指令,就是以root許可權控制了電腦。
所以我們更多的時候把緩衝區溢位指的是一種系統攻擊的手段,通過往程式的緩衝區寫超出其長度的內容,造成緩衝區的溢出,從而破壞程式的堆棧,使程式轉而執行其它指令,以達到攻擊的目的。據統計,通過緩衝區溢位進行的攻擊占所有系統攻擊總數的80%以上。
世界上第一個緩衝區溢位攻擊——著名的Morris蠕蟲,發生在十年前,它曾造成了全世界6000多台網路伺服器癱瘓。我這是我所知的最早的緩衝區溢位攻擊程式。
記住,造成緩衝區溢位的原因是程式中沒有仔細檢查使用者輸入的參數!!!
下面舉一個最為常見的,也是最簡單的溢出。
void function(char *str){
char buffer[16];
strcpy(buffer,str);
}
在這個例子中上面的buffer的長度被限制在16,而strcpy()將直接把str中的內容copy到buffer中。這樣只要str的長度大於16,就會造成buffer的溢出,使程式運行出錯。So,我們說這個程式溢出了。
在C語言中,靜態變數是分配在資料區段中的,動態變數是分配在堆棧段的。緩衝區溢位是利用堆棧段的溢出的。一個正常的程式在記憶體中通常分為程式段,資料端和堆棧三部分。程式段裡放著程式的機器碼和唯讀資料,這個段通常是唯讀,對它的寫操作是非法的。資料區段放的是程式中的待用資料。動態資料則通過堆棧來存放。在記憶體中,它們的位置如下:
/――――――――\記憶體低端
程式段
―――――――――
資料區段
―――――――――
堆棧
\―――――――――/記憶體高端
堆棧是記憶體中的一個連續的塊。一個叫堆棧指標的寄存器(SP)指向堆棧的棧頂。堆棧的底部是一個固定地址。堆棧有一個特點就是,後進先出。也就是說,後放入的資料第一個取出。它支援兩個操作,PUSH和POP。PUSH是將資料放到棧的頂端,POP是將棧頂的資料取出。
在進階語言中,程式函數調用和函數中的臨時變數都用到堆棧。為什麼呢?因為在調用一個函數時,我們需要對當前的操作進行保護,也為了函數執行後,程式可以正確的找到地方繼續執行,所以參數的傳遞和傳回值也用到了堆棧。通常對局部變數的引用是通過給出它們對SP的位移量來實現的。另外還有一個基址指標(FP,在Intel晶片中是BP),許多編譯器實際上是用它來引用本地變數和參數的。通常,參數的相對FP的位移是正的,局部變數是負的。
當程式中發生函數調用時,電腦做如下操作:首先把參數壓入堆棧;然後儲存指令寄存器(IP)中的內容,做為返回地址(RET);第三個放入堆棧的是基底位址暫存器(FP);然後把當前的棧指標(SP)拷貝到FP,做為新的基地址;最後為本地變數留出一定空間,把SP減去適當的數值。
比如說下面這個程式:
void function(int a, int b, int c){
char buffer1[10];
char buffer2[15];
}
void main(){
function(1,2,3);
}
假設我們在Linux下,用gcc對這段源碼進行編譯,產生彙編代碼輸出:
$ gcc -S -o example1.s example1.c
看看輸出檔案中調用函數的那部分:
pushl $3
pushl $2
pushl $1
call function
這就將3個參數推入堆棧裡了,並調用function()。指令call會將指令指標IP壓入堆棧。在返回時,RET要用到這個儲存的IP。在函數中,第一要做的事是進行一些必要的處理。每個函數都必須有這些過程(為了保護呀,不然就找不到了。):
pushl %ebp
movl %esp,%ebp
subl $20,%esp
這幾條指令將EBP,基址指標放入堆棧。然後將當前SP拷貝到EBP。然後,為本地變數分配空間,並將它們的大小從SP裡減掉。由於記憶體配置是以字為單位的,因此,這裡的buffer1用了8位元組(2個字,一個字4位元組)。Buffer2用了12位元組(3個字)。所以這裡將ESP減了20。這樣,現在,堆棧看起來應該是這樣的。
低端記憶體 高端記憶體
buffer2 buffer1 sfp ret a b c
< ------ [ ] [ ] [ ] [ ] [ ] [ ] [ ]
棧頂 棧底
那是什麼導致了溢出呢?緩衝區溢位就是在一個緩衝區裡寫入過多的資料。要怎麼樣利用呢?看下面這個程式:
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}
void main() {
char large_string[256];
int i;
for( i = 0; i < 255; i++)
large_string[i] = 'A';
function(large_string);
}
這個程式是一個經典的緩衝區溢位編碼錯誤。函數將一個字串不經過邊界檢查,拷貝到另一記憶體地區。當調用函數function()時,堆棧如下:
低記憶體端 高記憶體端
buffer sfp ret *str
< ------ [ ] [ ] [ ] [ ]
棧頂 棧底
很明顯,程式執行的結果是"Segmentation fault (core
dumped)"或類似的出錯資訊。因為從buffer開始的256個位元組都將被*str的內容'A'覆蓋,包括sfp,
ret,甚至*str。'A'的十六進值為0x41,所以函數的返回地址變成了0x41414141,
這超出了程式的地址空間,所以出現段錯誤。可見,緩衝區溢位允許我們改變一個函數的返回地址,在這個例子中,我們可以通過修改0x41414141來改變程式返回後的入口地址。同樣通過這種方式,就可以改變程式的執行順序了。
存在象strcpy這樣的問題的標準函數還有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在迴圈內的getc(),fgetc(),getchar()等。