Chinaunix首页 | 论坛 | 博客
  • 博客访问: 161858
  • 博文数量: 53
  • 博客积分: 2042
  • 博客等级: 大尉
  • 技术积分: 425
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-15 21:39
文章存档

2011年(6)

2010年(47)

分类: C/C++

2010-06-25 00:40:25

溢出是個很有意思的話題,緩衝區溢出又是當中最常見的一種情況。如果把緩衝區放在堆棧上,造成溢出,那麽有可能情況就變得很妙,比如程序就執行了你的ShellCodegogo

 

先來講將基本原理。

 

堆棧大家都很熟悉了,這裡就不說了,我們來復習一下程序的函數/過程調用。函數/過程是通過堆棧完成的,之所以這麽說是因爲程序通過堆棧向被調用的函數傳遞參數,當然沒有參數就不傳,同時使用堆棧來保存現場,待被調用之函數/過程執行完後再從中堆棧恢復現場,這個過程大家應該很熟悉,計算機體系結構還是什麽微型計算機技術、操作系統課程裏面都講過。

 

我們來仔細看看這個過程。

 

先看如下代碼:

 

void func_1(void)

...{

    int i = func_2(50);

       printf("Done!\n");

       return;

 

}

函數func_1調用了函數func_2,向其傳了50這樣一個參數,並將返回結果保存在變量i中。

 

這裡i是局部變量,局部變量是放在堆棧裏的。func_1調用func_2時,依次進行如下工作:

 

1、壓參數入棧,一般是從右往左壓,也就是如果函數有多個參數比如func(var1, var2, var3),那麽就是var3先入棧,然後是var2,最後是var1

 

2、保存返回地址(PC寄存器的值)。也就是執行完了函數調用要接著做剛才的事情,這裡就是printf("Done\n");這句話的指令地址;

 

3、保存自個兒堆棧的基址(EBP寄存器的值)。執行完調用後要恢復自個兒的堆棧就用這個。保存EBP寄存器值的方法就是壓棧;

 

4、跳到被調用函數的地址去執行。這裡就是跳到函數func_2処執行。

 

函數返回時執行相反的過程:

 

1、把返回值放到EAX中。如果沒有就不放,如果放不下就把它的地址放進去;

 

2、恢復現場。彈棧;

 

3、跳到“以前要執行的下一條指令”処執行。也是通過彈棧來實現的。

 

看圖。

 

 

圖一,函數調用之堆棧示意圖

 

圖上說得很清楚。

 

現在我們來考慮溢出的問題。所謂溢出,就是越界覆寫,這當然是很嚴重的事件——不是你的地盤的東西被你寫了。這會導致兩個問題:

 

1、破壞了別人的東西;

 

2、自己寫的東西有可能找不回來。

 

其中猶以第一個問題爲重,我們還是來看看剛才那張圖,想想如果局部變量1的東西把局部變量2覆蓋了,而程序不知道,所以它把局部變量2拿去用了這樣將導致不可預料的後果。

 

看看下面這段代碼,會有問題嗎?

 

 

 

#include

#include

#include

 

int main(void)

...{

    //退出時打印的訊息

  char info[] = "Exiting...";

 

    //操作緩衝區

 char buffer[8] = ...{0};

   

    //printf("login_passwd=0x%p info=0x%p insert=0x%p ", login_passwd, info, insert);

 

    printf("Processing... ");

    strcpy(insert, "Administrator:123456");

   

    printf("%s ", info);

   

    return TRUE;

}

 

 

這段代碼的本意是把管理員的用戶名和密碼複製到緩衝區buffer中,然後退出。可是很不幸,在堆棧上溢出了。出問題的地方很明顯,strcpy複製一個較大的緩衝區到一個較小的緩衝區,於是乎就把這個較小的緩衝區“後面”的一段内存覆寫了,這段内存正好是數組info的,所以就把管理員的密碼打印出來了。

 

看看圖二,堆棧是這樣的。

 

 

圖二,堆棧層次

 

先聲明的東西先分配堆棧,於是infobuffer的上面(x86的棧是逆向生長的)。可是你會奇怪,strcpy越界覆寫應該是把buffer後面的内存破壞了呀,説是說在後面,其實是它前面的變量遭了殃,因爲堆棧是倒著長的嘛。

 

好了,看完這個例子,我們再回去看看圖一。你可能會想:緩衝區溢出有什麽用?我想除非別有用心,不然那純粹是個壞東西。不過我覺得別有用心並非是什麽壞事,比如圖一那個棧...把返回地址給改了就不錯...嘿嘿:儅一個函數調用另外一個函數,執行完成返回時卻“意外”地跳到另外一個地方執行去了,比如什麽Shutdown函數之類的。仔細想想,這的確並非不可能,比如讓圖一的那個“局部變量1”溢出並越過“保存的EBP”寫到函數的“返回地址”上去,那麽儅這個函數調用完後“返回地址”中的内容就會彈入EIP寄存器。爲了演示這種可能性,我們來一段代碼。不過代碼中我並不打算讓局部變量溢出從而破壞返回地址——因爲這樣把老的EBP也破壞了。這裡我直接通過指針的計算,“尋址”到返回地址,然後覆寫它。

 

 

 

#include

#include

#include

 

void bad(void)

...{

    printf("haha ");

    return;

}

 

int prblm(int input)

...{

    printf("input=%d ", input);

   

    int *p = &input;

    printf("p=%p ", p);

 

    //調整指針讓其指向函數返回地址

     p += 7;

 

     //覆寫

    *p = (int)bad;

   

    printf("exiting func... ");

    return TRUE;

}

 

int main(void)

...{

    printf("start... ");

    prblm(0x100);

   

    printf(" end! ");

    return TRUE;

}

 

 

再看圖三,一定要看圖。

 

 

 

圖三,堆棧上之覆寫示意圖

 

這裡傳入的參數是input,代碼中用p指向它,然後--向下移動一個單位(4個字節),這裡p用的是整形指針,免得以後轉換。移動後的p就指向了保存函數返回的地址的地址,改了它!把它的内容替換成了函數bad的地址。這當然是我們蓄意的代碼,並非什麽溢出,建議運行一把看看會是什麽樣子。。。

 

bad函數被“意外”地執行了!程序中並沒有執行bad函數的代碼,但是bad卻執行了,這就是因爲函數prblm返回的地址(main函數中調用函數prblm完後下一條代碼的地址)被覆蓋成bad函數的地址了,所以儅函數prblm執行完後程序就跳到bad函數処去執行了。

 

這就是大夥兒最常用的堆棧溢出。堆棧溢出的原理很簡單,但是要有效地實施卻未必是件容易的事。比如你想對某個程序實施堆棧溢出執行你想要的代碼:

 

第一、這個程序裏要有溢出點,就是程序設計缺陷的地方,比如一段輸入的數據沒有檢查長度,這就有可能讓你輸入過長的數據而產生覆蓋;

 

第二、緩衝區是局部自動變量,只有局部自動變量才在堆棧中;

 

第三、程序中有你想要執行的代碼,如果程序中有你想要執行的代碼溢出纔有意義,否則只是弄壞別人的程序而已,比如對方程序中正好有個AddUser(UserName, Password)這樣的函數,你可以直接跳到這裡執行它。如果對方程序裏沒有逆向執行的代碼,問題就變得更複雜了:你得構造一段你要的代碼,然後想辦法植入到對方程序當中。這樣,首先目標程序要有接收存儲輸入的機制,這樣你才能把代碼放上去,比如它給你提供的一個Input框,Input框中的内容會被放在某塊内存(變量)當中,而且這個地方要合適:你的代碼裏是否包含0?它將被認爲是字符串的結尾。輸入框是否夠大?你的代碼有200字節,而它只能讓你輸入100個字節的數據?

 

第四、能夠定位到你想執行的代碼,不管是程序中内置的代碼還是你植入的代碼,要想執行它們必須能夠得到它們的地址,能否做到?

 

這樣看來想要在堆棧溢出上面“做點文章”可謂條件苛刻,不過盡管如此我們還是能夠屢屢得手——縂有那麽些同志寫代碼的時候不注意,給我們留了機會。下一篇我們講要看看“機會”是怎麽產生的以及“機會的把握”。

 

文章出处: 

阅读(1173) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~