網頁木馬攻防實戰學習筆記二:緩衝區溢出

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) ;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章