2015-04-07 83 views
0

我正在學習基於語言的安全性的課程,並且我必須一步一步瞭解當一個函數正確執行時堆棧中發生了什麼,以便稍後我可以學習如何防止利用。到目前爲止,我對堆棧中推送和彈出的內容以及ESP,EBP將如何跟蹤幀進行了很好的理解。另外,我知道EIP保存在堆棧中。瞭解在機器級執行功能

我不知道的是函數中的代碼實際執行的地方是爲了得到結果(我假設在內存中的其他地方,堆?)如果我給一個簡單函數的演練,有人可以解釋缺少位(我會用問題標記這些部分)。設定一個簡單的函數:

int add(int x, int y) 
{ 
    int sum = x + y; 
    return sum; 
} 

它在main()中用add(3,4)調用;

在新函數初始化時,堆棧(從最低地址到最高地址)的ESP指向頂部,EBP指向新幀的底部。下面是main()。

現在,將參數從右向左推入堆棧。函數調用將EIP的內容保存在堆棧中。 [這是函數返回後要執行的下一條指令的地址?]

現在Prolog部分:舊的EBP地址被壓入堆棧,EBP被指向ESP。最後,局部變量被壓入堆棧[這些僅僅是存儲其值的地址嗎?]

Epilog是堆棧要解開當前幀的時候。 ESP移至EBP,因此局部變量不可訪問(通常)。舊的EBP彈出堆棧,並指向它的原始地址。 ESP移動指向保存的EIP,這是在添加(3,4)被調用之前的位置。

在給出的解釋中,最後一部分是返回指令將保存的EIP值彈回EIP寄存器。 [當然,這不是函數中的return語句,而是機器級的ret語句,對吧?]

最後一個問題,有人可以解釋當函數中的代碼正在執行時以及所有時間點那叫,序言和結語是否發生?或者提供一個清晰的解釋的好鏈接?

在此先感謝堆(這麼說:)

+0

防範漏洞的方法是防止輸入溢出分配給它的空間。像'int i;'或'char * ptr'這樣的函數參數和局部變量不能溢出堆棧,但是像char [20];這樣的數組和可以使用動態分配給char * ptr的內存的數組可以溢出在堆上分配。 –

+0

爲什麼不編譯程序並使用調試器逐步完成某個功能?你會比我們在這裏可以告訴你的任何東西都學到更多。 – JS1

+0

執行x86'call'指令後,'call'後面的指令地址被壓入堆棧。函數局部變量通常不會被壓入堆棧 - 相反,堆棧指針會減少以爲當地人分配所需的存儲空間,這些存儲空間將在稍後進行初始化。而'return'確實是一個彙編指令。 –

回答

2

首先,我編譯然後拆開你的函數,所以你可以看到在ASM水平實際上是怎麼回事。我禁用優化和編譯爲32位代碼,讓事情變得簡單:

Dump of assembler code for function add: 
    0x080483cb <+0>:  push %ebp 
    0x080483cc <+1>:  mov %esp,%ebp 
    0x080483ce <+3>:  sub $0x10,%esp 
    0x080483d1 <+6>:  mov 0x8(%ebp),%edx 
    0x080483d4 <+9>:  mov 0xc(%ebp),%eax 
    0x080483d7 <+12>: add %edx,%eax 
    0x080483d9 <+14>: mov %eax,-0x4(%ebp) 
    0x080483dc <+17>: mov -0x4(%ebp),%eax 
    0x080483df <+20>: leave 
    0x080483e0 <+21>: ret  
End of assembler dump. 

嘗試看看上面的拆卸,並承認它正在做什麼,以及它如何符合你的C代碼。現在回答你的問題。

現在Prolog部分:將舊的EBP地址壓入堆棧,並使EBP指向ESP。最後,局部變量壓入堆棧[難道這些只是存儲它們的值,其中的地址嗎?]

這裏序言去從0x080483cb <+0>到包括0x080483ce <+3>。 首先我們用push %ebp; mov %esp,%ebp創建一個框架,然後我們爲sub $0x10,%esp的堆棧上的局部變量分配0x10字節的空間。該指令所做的只是將堆棧指針向下移動0x10個字節。它不存儲任何值,如果我們想要的話,它只留下一些我們可以用於局部變量的空間(我們將看到編譯器甚至沒有使用它)。

接下來我們有該函數的實際邏輯。 首先,我們從堆棧中兩個參數x和y加載到寄存器:

0x080483d1 <+6>:  mov 0x8(%ebp),%edx 
0x080483d4 <+9>:  mov 0xc(%ebp),%eax 

我們把它們相加:

0x080483d7 <+12>: add %edx,%eax 

現在我們將結果存儲在一個局部變量。該局部變量實際上只是我們在prolog中分配的堆棧空間。我們分配給本地變量爲0x10字節,而在這裏我們只用了前4個字節來存儲加法的結果:

0x080483d9 <+14>: mov %eax,-0x4(%ebp) 

而且因爲沒有任何的優化,我們立即從本地加載正確的結果變回一個寄存器,這樣我們可以退貨:

0x080483dc <+17>: mov -0x4(%ebp),%eax 

正如你所看到的代碼是非常低效的,但至少它很容易閱讀。 現在只有收尾留,這是很簡單的:

0x080483df <+20>: leave 
0x080483e0 <+21>: ret 

leave破壞我們在序言中創建的幀,ret返回到調用函數的下一條指令。

Epilog是堆棧要解開當前幀的時候。 ESP移至EBP,因此局部變量不可訪問(通常)。舊的EBP彈出堆棧,並指向它的原始地址。 ESP移動指向保存的EIP,這是在添加(3,4)被調用之前的位置。

在給出的解釋中,最後一部分是返回指令將保存的EIP值彈回EIP寄存器。 [當然,這不是函數中的return語句,而是機器級別的ret語句,對吧?]

函數中的return語句對應於機器級別的ret指令。這是直接翻譯。 請記住,您的計算機不直接運行C代碼,所有該C編譯爲機器代碼第一,並且這裏的ret指令確實是彈出EIP。

最後一個問題,有人可以解釋一下當函數中的代碼正在執行時,以及在所有那些調用,序言和epilog期間發生的事情發生了什麼?或者提供一個清晰的解釋的好鏈接?

您在上面看到的反彙編是計算機運行的粗略文本表示形式。 EIP包含計算機將運行的下一條指令的地址。當程序運行時,它存儲在內存中的某個地方,EIP直接指向內存中的指令。

所以計算機將按照寫入順序運行函數,並且prolog和epilog是函數的一部分。

prolog和epilog是一個約定,但它們只是代碼。如果你願意,你可以完全刪除序言和寫一個瘋狂的結語,它也可以工作。

我建議你去玩反彙編和調試器,以熟悉它是如何工作的。這並不困難,也很合理。

+0

非常感謝,tux3。這正是我所需要的。我在互聯網上發現的課堂上的講座和教程似乎都沒有爲簡單起見而缺少關鍵步驟,並向初學者解釋了這一過程。 –