1 常見案例
1.1 棧溢出
棧是一個在進程映象內存的高地址內的後進先出(LIFO) 的緩衝區。
#include<stdio.h>
#include <string.h>
char evil[] =”AAAAAAAAAAAAAAAAAAAAAAAA" ;
void main()
{
char buff[4];
strcpy (buff,evil);
return;
}
上述代碼中預留的緩衝區只有4個字節,而strcpy函數向其中複製了24 個字節,導致了緩衝區溢出的發生,存儲在棧中的函數返回地址將被覆蓋。
1.2 堆溢出
這裏所說的堆,不是《數據結構》裏描述的堆,這裏的堆(英文稱做heap),是Windows系統中的一種物理結構,用來動態分配和釋放對象,用在事先不知道程序所需對象的數量和大小的情況下。
#include<stdio.h>
#include <string.h>
int foo(char *buf) ;
int main(int argc, char *argv[])
{
HMODULE 1 = LoadLibrary ("msvcrt.d11") ;
printf (" \n\nHeapoverflow Poc\n") ;
if (argc != 2)
return printf("缺少參數");
foo(argv[1]) ;
return 0;
}
int foo(char *buf)
{
HLOCAL h1 = 0, h2 = 0;
HANDLE hp;
hp = HeapCreate (0, 0x1000, 0x10000) ;
if(!hp)
return printf("Failed to create heap. \n") ;
h1 = HeapAlloc (hp, HEAP_ZERO_MEMORY,4) ;
printf ("HEAP: %.8X %.8X\n" ,h1, &h1) ;
strcpy (h1,buf) ;// Heap Overflow occurs here:
h2 = HeapAlloc (hp, HEAP_ZERO_MEMORY,260) ;//第二次調用HeapAlloc()將使我們獲得程序的控制權
printf("hello") ;
return 0;
}
在foo()函數中,HeapAlloc 函數申請了4個字節大小的堆用於存放變量,而strcpy函數又對buf變量的長度沒有檢查,如果將一個超長字符串複製到堆中,將破壞原有的堆控制數據結構,當第二次調用HeapAlloc(函數的時候,通過精心構造惡意的字符串,就能取得程序的控制權。
1.3 .data節中的溢出
Visual C++中,你能用#pragma指令讓編譯器插入數據到一個節中。一個Visual C++編譯出的典型程序有如下的節:
.data節中通常存放一些全局變量,因此發生在data中的溢出是完全可能的。
1.4 TEB/PEB溢出
每個TEB都有一個緩衝區被用於將ANSI字符集轉換爲Unicode字符集,像SetComputerNameA和GetModuleHandleA這兩個API函數就會用到這個緩衝區,可以想像如果這兩個函數沒有對傳入的字符串進行長度檢查,那麼溢出就會發生。同時TEB裏也有許多重要的指針,例如SEH裏的EXCEPTION REGISTR ATION結構體,如果能觸發異常,那麼就能取得程序的控制權。
1.5 格式化字符串漏洞
最常用的兩種函數調用規則,第一種方法叫cdecl,調用者負責把參數從右向左壓入堆棧,被調用函數返回後由調用者負責調整堆棧。使用這種調用方式的典型函數是printf 函數。它利用格式化字符串支持可變參數的傳遞。第二種方法叫stdcall,它與第一種方法唯一不同的一點是由被調用函數負責調整堆棧。
printf()函數的調用格式爲:
printf("<格式化字符串>”,<參數表>);
其中格式化字符串包括兩部分內容:一部分是正常字符,這些字符將按原樣輸出:另一部分是格式化規定字符, 以“%”開始,後跟一個或幾個規定字符,用來確定輸出內容格式。參量表是需要輸出的系列參數。
除了Printf()函數,以下函數的使用錯誤也同樣會導致惡意代碼的執行:frintfo(),sprintf(),snprintf(),vfprintf(), vprintf(),vsprintf(), vsnprintf()。
1.6 整數溢出
一個整數,整型和指針的尺寸一般是相同的(在32位的系統中,例如i386,一個整數是32位長,在64位的系統中,例如SPARC, -個整數是64位長)。計算機中整數是以二進制存儲的。計算機中需要負數,通過一個變量的最高位來決定正負。如果最高位置1,這個變量就被解釋爲負數;如果置0,這個變量就解釋爲整數。
既然整數有一個固定的長度,那麼它能存儲的最大值是固定的,當嘗試去存儲一一個大於這個固定的最大值時,將會導致一個整數溢出。
整數溢出引起的緩衝區溢出主要有兩類,一類是“負數等於很 大的整數”,另一類是“真符號數和無符號數比較”。
負數在被當成無符號數處理的時候,就是很大的整數,32 位有符號整數-1可以表示成0xFFFFFFF,但是看成無符號數的時候,就是4294967295。
#include <stdio. h>
#include <wi ndows .h>
int foo(char *buf) ;
int main(int argc, char *argv[] )
{
printf (" \n\nFormat String Error Poc\n") ;
if(argc !=2)
return printf("缺少參數");
foo(argv[1]) ;
return 0;
}
int foo(char *buf)
{
char s[8];
strncat (s, buf, sizeof (s)-strlen(buf)-1) ;
printf ("Integer overflow!") ;
return 0 ;
}
給它傳遞一個超長參數便間接地導致了緩衝區溢出的產生。
1.7 Off-by-one攻擊
有些字符串處理函數會自動地在字符串末尾添加\x0O的結束符,當字符串的長度大於緩衝區長度的時候,字符串處理函數將結束符寫入到下一個字節。這就是所謂的Off-by one攻擊。
strncat 函數會自動地在字符串的末尾加上\x00結束符,在某些特殊情況下,與緩衝區相鄰的EBP寄存器將會被改寫,間接地將導致棧指針ESP也被改寫。
2 經典溢出情形
2.1 最容易發現的情形
int main(int argc,char **argv)
{
char buf[256] ;
strcpy (buf ,argv[1]) ;
}
說明:
覆蓋main函數返回地址。
2.2 主程序不返回的例子
int main(int argv,char **argc) {
char buf[256];
strcpy(buf,argc[1]) ;
exit(1);
}
說明:
Windows 下利用Windows的異常機制可以成功地溢出這個程序。
2.3 覆蓋函數指針
int main(int argv,char **argc) {
extern system, puts;
void (*fn) (char*)= (void(*) (char*) )&system;
char buf[256];
fn= (void(*) (char*)) &puts;
strcpy (buf,argc[1]) ;
fn(argc[2]) ;
exit(1) ;
}
說明:
可利用緩衝區溢出覆蓋fn函數指針,以達到攻擊的目的。
2.4 覆蓋普通指針
int main(int argv,char **argc) {
char *pbuf=malloc (strlen (argc[2])+1) ;
char buf[256] ;
fn= (void(*) (char*) ) &puts;
strcpy (buf,argc[1]) ;
strcpy (pbuf,argc[2]) ;
fn(argc[3]) ;
while(1) ;
}
說明:
可第一個strcpy時候,可覆蓋到pbuf指針,可使pbuf指向fn地址,所以第二次strcpy的時候就會覆蓋到fn 指針,結果在運行fn()函數的時候就可以執行任意函數調用,比如system()。
2.5 覆蓋GOT
int main(int argv, char**argc) {
char *pbuf=malloc (strlen(argc[2])+1) ;
char buf[256] ;
strcpy (buf, argc[1]) ;
for (; *pbuf++=*(argc[2]++);) ;
exit(1) ;
}
說明:
第一個strcpy時候,可覆蓋到pbuf指針,可使pbuf指向exit的GOT或者.dotrs地址+4,從而可以覆蓋到那些部分,獲得控制權。
2.6 覆蓋strcpy函數的返回地址
int main(int argv,char **argc) {
char *pbuf=malloc (strlen (argc[21)+1) ;
char buf[256];
strcpy (buf,argc[1]) ;
strcpy (pbuf,argc[2]) ;
while(1) ;
}
說明:
第一個strcpy時候,可覆蓋到pbuf指針,可使pbuf指向第二個strcpy函數的返回地址,從而可以覆蓋到該地址,第二個stcpy--返回就可以獲得控制權。
2.7 兩次free
int main(int argv,char **argc) {
char *pbuf1= (char*)malloc(256) ;
char *pbuf2= (char*)malloc(256) ;
gets (pbuf1) ;
free (pbuf2) ;
free (pbuf1) ;
}
2.8 一次free
int main(int argv,char **argc) {
char *pbuf1= (char*)malloc(256) ;
gets (pbuf1) ;
free (pbuf1) ;
}