明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

編寫自己的緩沖區(qū)溢出使用程序

[摘要]內(nèi)容: 本文主要講解有關(guān)Buffer Overflow的原理, 以及結(jié)合實(shí)戰(zhàn)范例介紹Linux和Solaris下的漏洞利用. 本文并不介紹如何編寫shell code. 要求: 讀者要有一點(diǎn)C和匯編語言基礎(chǔ). 目標(biāo): 希望本文能夠盡量做到通熟易懂,使得稍有計(jì)算機(jī)基礎(chǔ)知識(shí)的朋友看后能夠親自動(dòng)手寫自己...

內(nèi)容: 本文主要講解有關(guān)Buffer Overflow的原理, 以及結(jié)合實(shí)戰(zhàn)范例介紹Linux和Solaris下的漏洞利用. 
本文并不介紹如何編寫shell code. 

要求: 讀者要有一點(diǎn)C和匯編語言基礎(chǔ). 

目標(biāo): 希望本文能夠盡量做到通熟易懂,使得稍有計(jì)算機(jī)基礎(chǔ)知識(shí)的朋友看后能夠親自動(dòng)手寫自己的Exploit 
如果你覺得自己對(duì)這些都懂了, 就請(qǐng)不要再往下看了. 


第一部份 概述篇 

1. Buffer overflow是如何產(chǎn)生的? 
所謂Buffer overflow, 中文譯為緩沖區(qū)溢出. 顧名思意, 就是說所用的緩沖區(qū)太小了, 以至裝不下 
那么多的東西, 多出來的東西跑出來了. 就好象是水缸裝不了那么多的水, 硬倒太多會(huì)溢出來一樣;) 
那么, 在編程過程中為什么要用到buffer(緩沖區(qū))呢? 簡單的回答就是做為數(shù)據(jù)處理的中轉(zhuǎn)站. 

2. UNIX下C語言函數(shù)調(diào)用的機(jī)制及緩沖區(qū)溢出的利用. 
1) 進(jìn)程在內(nèi)存中的影像. 
我們假設(shè)現(xiàn)在有一個(gè)程序, 它的函數(shù)調(diào)用順序如下. 
main(...) -> func_1(...) -> func_2(...) -> func_3(...) 
即: 主函數(shù)main調(diào)用函數(shù)func_1; 函數(shù)func_1調(diào)用函數(shù)func_2; 函數(shù)func_2調(diào)用函數(shù)func_3 

當(dāng)程序被操作系統(tǒng)調(diào)入內(nèi)存運(yùn)行, 其相對(duì)應(yīng)的進(jìn)程在內(nèi)存中的影像如下圖所示. 

(內(nèi)存高址) 
+--------------------------------------+ 
 ......   ... 省略了一些我們不需要關(guān)心的區(qū) 
+--------------------------------------+ 
 env strings (環(huán)境變量字串)   \ 
+--------------------------------------+ \ 
 argv strings (命令行字串)   \ 
+--------------------------------------+ \ 
 env pointers (環(huán)境變量指針)   SHELL的環(huán)境變量和命令行參數(shù)保存區(qū) 
+--------------------------------------+ / 
 argv pointers (命令行參數(shù)指針)   / 
+--------------------------------------+ / 
 argc (命令行參數(shù)個(gè)數(shù))   / 
+--------------------------------------+ 
 main 函數(shù)的棧幀   \ 
+--------------------------------------+ \ 
 func_1 函數(shù)的棧幀   \ 
+--------------------------------------+ \ 
 func_2 函數(shù)的棧幀   \ 
+--------------------------------------+ \ 
 func_3 函數(shù)的棧幀   Stack (棧) 
+......................................+ / 
   / 
...... / 
   / 
+......................................+ / 
 Heap (堆)   / 
+--------------------------------------+ 
 Uninitialised (BSS) data   非初始化數(shù)據(jù)(BSS)區(qū) 
+--------------------------------------+ 
 Initialised data   初始化數(shù)據(jù)區(qū) 
+--------------------------------------+ 
 Text   文本區(qū) 
+--------------------------------------+ 
(內(nèi)存低址) 

這里需要說明的是: 
i) 隨著函數(shù)調(diào)用層數(shù)的增加, 函數(shù)棧幀是一塊塊地向內(nèi)存低地址方向延伸的. 
隨著進(jìn)程中函數(shù)調(diào)用層數(shù)的減少, 即各函數(shù)調(diào)用的返回, 棧幀會(huì)一塊塊地 
被遺棄而向內(nèi)存的高址方向回縮. 
各函數(shù)的棧幀大小隨著函數(shù)的性質(zhì)的不同而不等, 由函數(shù)的局部變量的數(shù)目決定. 
ii) 進(jìn)程對(duì)內(nèi)存的動(dòng)態(tài)申請(qǐng)是發(fā)生在Heap(堆)里的. 也就是說, 隨著系統(tǒng)動(dòng)態(tài)分 
配給進(jìn)程的內(nèi)存數(shù)量的增加, Heap(堆)有可能向高址或低址延伸, 依賴于不 
同CPU的實(shí)現(xiàn). 但一般來說是向內(nèi)存的高地址方向增長的. 
iii) 在BSS數(shù)據(jù)或者Stack(棧)的增長耗盡了系統(tǒng)分配給進(jìn)程的自由內(nèi)存的情況下, 
進(jìn)程將會(huì)被阻塞, 重新被操作系統(tǒng)用更大的內(nèi)存模塊來調(diào)度運(yùn)行. 
(雖然和exploit沒有關(guān)系, 但是知道一下還是有好處的) 
iv) 函數(shù)的棧幀里包含了函數(shù)的參數(shù)(至于被調(diào)用函數(shù)的參數(shù)是放在調(diào)用函數(shù)的棧 
幀還是被調(diào)用函數(shù)棧幀, 則依賴于不同系統(tǒng)的實(shí)現(xiàn)), 
它的局部變量以及恢復(fù)調(diào)用該函數(shù)的函數(shù)的棧幀(也就是前一個(gè)棧幀)所需要的 
數(shù)據(jù), 其中包含了調(diào)用函數(shù)的下一條執(zhí)行指令的地址. 
v) 非初始化數(shù)據(jù)(BSS)區(qū)用于存放程序的靜態(tài)變量, 這部分內(nèi)存都是被初始化為零的. 
初始化數(shù)據(jù)區(qū)用于存放可執(zhí)行文件里的初始化數(shù)據(jù). 
這兩個(gè)區(qū)統(tǒng)稱為數(shù)據(jù)區(qū). 
vi) Text(文本區(qū))是個(gè)只讀區(qū), 任何嘗試對(duì)該區(qū)的寫操作會(huì)導(dǎo)致段違法出錯(cuò). 文本區(qū) 
是被多個(gè)運(yùn)行該可執(zhí)行文件的進(jìn)程所共享的. 文本區(qū)存放了程序的代碼. 

2) 函數(shù)的棧幀. 
函數(shù)調(diào)用時(shí)所建立的棧幀包含了下面的信息: 
i) 函數(shù)的返回地址. 返回地址是存放在調(diào)用函數(shù)的棧幀還是被調(diào)用函數(shù)的棧幀里, 
取決于不同系統(tǒng)的實(shí)現(xiàn). 
ii) 調(diào)用函數(shù)的棧幀信息, 即棧頂和棧底. 
iii) 為函數(shù)的局部變量分配的空間 
iv) 為被調(diào)用函數(shù)的參數(shù)分配的空間--取決于不同系統(tǒng)的實(shí)現(xiàn). 

3) 緩沖區(qū)溢出的利用. 
從函數(shù)的棧幀結(jié)構(gòu)可以看出: 
由于函數(shù)的局部變量的內(nèi)存分配是發(fā)生在棧幀里的, 所以如果我們?cè)谀骋粋(gè)函數(shù)里定義 
了緩沖區(qū)變量, 則這個(gè)緩沖區(qū)變量所占用的內(nèi)存空間是在該函數(shù)被調(diào)用時(shí)所建立的棧幀里. 

由于對(duì)緩沖區(qū)的潛在操作(比如字串的復(fù)制)都是從內(nèi)存低址到高址的, 而內(nèi)存中所保存 
的函數(shù)調(diào)用返回地址往往就在該緩沖區(qū)的上方(高地址)--這是由于棧的特性決定的, 這 
就為復(fù)蓋函數(shù)的返回地址提供了條件. 當(dāng)我們有機(jī)會(huì)用大于目標(biāo)緩沖區(qū)大小的內(nèi)容來向 
緩沖區(qū)進(jìn)行填充時(shí), 就有可以改寫函數(shù)保存在函數(shù)棧幀中的返回地址, 從而使程序的執(zhí) 
行流程隨著我們的意圖而轉(zhuǎn)移. 換句話來說, 進(jìn)程接受了我們的控制. 我們可以讓進(jìn)程 
改變?cè)瓉淼膱?zhí)行流程, 去執(zhí)行我們準(zhǔn)備好的代碼. 

這是馮.諾曼計(jì)算機(jī)體系結(jié)構(gòu)的缺陷. 

下面是緩沖區(qū)溢出利用的示意圖: 
i) 函數(shù)對(duì)字串緩沖區(qū)的操作, 方向一般都是從內(nèi)存低址向高址的. 
如: strcpy(s, "AAA....."); 

s s+1 s+2 s+3 ... 
+---+---+---+--------+---+...+ 
(內(nèi)存低址)   A   A   A   ......   A  ...  (內(nèi)存高址) 
+---+---+---+--------+---+...+ 

ii) 函數(shù)返回地址的復(fù)蓋 

/   ......   (內(nèi)存高址) 
/ +--------------------+ 
調(diào)用函數(shù)棧幀   0x41414141   
\ +--------------------+ 
\   0x41414141   調(diào)用函數(shù)的返回地址 
\+--------------------+ 
/  ......   
/ +--------------------+ s+8 
/   0x41414141   
/ +--------------------+ s+4 
被調(diào)用函數(shù)棧幀   0x41414141   
\ +--------------------+ s 
\   0x41414141   
\ +--------------------+ 
\  ......   
+....................+ 
 ......   (內(nèi)存低址) 

注: 字符A的十六進(jìn)制ASCII碼值為0x41. 

iii) 從上圖可以看出: 如果我們用的是進(jìn)程可以訪問的某個(gè)地址而不是0x41414141 
來改寫調(diào)用函數(shù)的返回地址, 而這個(gè)地址正好是我們準(zhǔn)備好的代碼的入口, 那么 
進(jìn)程將會(huì)執(zhí)行我們的代碼. 否則, 如果用的是進(jìn)程無法訪問的段的地址, 將會(huì)導(dǎo) 
致進(jìn)程崩饋--Segment Fault Core dumped (段出錯(cuò)內(nèi)核轉(zhuǎn)儲(chǔ)); 如果該地址處有 
無效的機(jī)器指令數(shù)據(jù), 將會(huì)導(dǎo)致非法指令(Illigal Instruction)錯(cuò)誤, 等等. 

4) 緩沖區(qū)在Heap(堆)區(qū)或BBS區(qū)的情況 
i) 如果緩沖區(qū)的內(nèi)存空間是在函數(shù)里通過動(dòng)態(tài)申請(qǐng)得到的(如: 用malloc()函數(shù)申請(qǐng)), 那 
么在函數(shù)的棧幀中只是分配了存放指向Heap(堆)中相應(yīng)申請(qǐng)到的內(nèi)存空間的指針. 這種 
情況下, 溢出是發(fā)生在(Heap)堆中的, 想要復(fù)蓋相應(yīng)的函數(shù)返回地址, 看來幾乎是不可 
能的. 這種情況的利用可能性要看具體情形, 但不是不可能的. 
ii) 如果緩沖區(qū)在函數(shù)中定義為靜態(tài)(static), 則緩沖區(qū)內(nèi)存空間的位置在非初始化(BBS)區(qū), 
和在Heap(堆)中的情況差不多, 利用是可能的. 但還有一種特姝情況, 就是可以利用它來 
復(fù)蓋函數(shù)指針, 讓進(jìn)程后來調(diào)用相應(yīng)的函數(shù)變成調(diào)用我們所指定的代碼. 


3. 從緩沖區(qū)溢出的利用可以得到什么? 
從上文我們看到, 緩沖區(qū)溢出的利用可以使我們能夠改寫相關(guān)內(nèi)存的內(nèi)容及函數(shù)的返回地址, 從而 
改變代碼的執(zhí)行流程, 讓進(jìn)程去執(zhí)行我們準(zhǔn)備好的代碼. 

但是, 進(jìn)程是以我們當(dāng)前登錄的用戶身份來運(yùn)行的. 能夠執(zhí)行我們準(zhǔn)備好的代碼又怎樣呢? 我們還 
是無法突破系統(tǒng)對(duì)當(dāng)前用戶的權(quán)限設(shè)置, 無法干超越權(quán)限的事. 

換句話來說, 要想利用緩沖區(qū)溢出得到更高的權(quán)限, 我們還得利用系統(tǒng)的一些特性. 

對(duì)于UNIX來講, 有兩個(gè)特性可以利用. 
i) SUID及SGID程序 
UNIX是允許其他用戶可以以某個(gè)可執(zhí)行文件的文件擁有者的用戶ID或用戶組ID的身份來執(zhí)行該 
文件的,這是通過設(shè)置該可執(zhí)行文件的文件屬性為SUID或SGID來實(shí)現(xiàn)的. 
也就是說如果某個(gè)可執(zhí)行文件被設(shè)了SUID或SGID, 那么當(dāng)系統(tǒng)中其他用戶執(zhí)行該文件時(shí)就相當(dāng) 
于以該文件屬主的用戶或用戶組身份來執(zhí)行該文件. 
如果某個(gè)可執(zhí)行文件的屬主是root, 而這個(gè)文件被設(shè)了SUID, 那么如果該可執(zhí)行文件存在可利 
用的緩沖區(qū)溢出漏洞, 我們就可以利用它來以root的身份執(zhí)行我們準(zhǔn)備好的代碼. 沒有比讓它 
為我們產(chǎn)生一個(gè)具有超級(jí)用戶root身份的SHELL更吸引人了, 是不是? 

ii) 各種端口守護(hù)(服務(wù))進(jìn)程 
UNIX中有不少守護(hù)(服務(wù))進(jìn)程是以root的身份運(yùn)行的, 如果這些程序存在可利用的緩沖區(qū)溢出, 
那么我們就可以讓它們以當(dāng)前運(yùn)行的用戶身份--root去執(zhí)行我們準(zhǔn)備被好的代碼. 
由于守護(hù)進(jìn)程已經(jīng)以root的身份在運(yùn)行, 我們并不需要相對(duì)應(yīng)的可執(zhí)行文件為SUID或SGID屬性. 
又由于此類利用通常是從遠(yuǎn)程機(jī)器上向目標(biāo)機(jī)器上的端口發(fā)送有惡意的數(shù)據(jù)造成的, 所以叫做 
"遠(yuǎn)程溢出"利用. 

4. 一個(gè)有問題的程序 
以下例程純屬虛構(gòu), 如有雷同, 純屬巧合. 

/* 
* 文件名 : p.c 
* 編譯 : gcc -o p p.c 
*/ 

#include <stdio.h> 

void vulFunc(char* s) 

char buf[10]; 
strcpy(buf, s); 
printf("String=%s\n", buf); 


main(int argc, char* argv[]) 

if(argc == 2) 

vulFunc(argv[1]); 

else 

printf("Usage: %s <A string>\n", argv[0]); 




這個(gè)例程接受用戶在命令行的字串輸入, 然后在標(biāo)準(zhǔn)輸出(屏幕)上打印出來. 我們可以看出在 
vulFunc()這個(gè)函數(shù)里, 定義了一個(gè)最多可以裝十個(gè)字符的緩沖區(qū)buf. 如果我們?cè)诿钚休斎?nbsp;
小于等于十個(gè)字符的字串, 則一切都很正常. 但是, 如果我們輸入的字串長度大于十呢? 情況 
會(huì)怎樣? 緩沖區(qū)太小裝不下了, 所以溢出了? 答案有待于具體分析一下才知道. 

對(duì)于這個(gè)程序在不同操作系統(tǒng)下的分析和模擬攻擊. 請(qǐng)看第二部份基楚篇 


第二部份 基楚篇 
5. Linux x86 平臺(tái) 
本文使用了如下Linux平臺(tái): 
Red Hat Linux release 6.2 (Zoot) 
Kernel 2.2.14-12 on an i586 

所使用的編譯器及版本: 
bash$ gcc -v 
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs 
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) 

注意: 不同版本的編譯器編譯相同代碼所生成的機(jī)器指令可能不同. 

1) 例程p.c在Linux x86平臺(tái)下的剖析. 
i) 首先我們編譯p.c并用gdb對(duì)相關(guān)函數(shù)進(jìn)行反匯編 
結(jié)果見如下清單: 

bash$ gcc -o p p.c 
bash$ gdb p 
GNU gdb 19991004 
Copyright 1998 Free Software Foundation, Inc. 
GDB is free software, covered by the GNU General Public License, and you are 
welcome to change it and/or distribute copies of it under certain conditions. 
Type "show copying" to see the conditions. 
There is absolutely no warranty for GDB. Type "show warranty" for details. 
This GDB was configured as "i386-redhat-linux"... 
(gdb) disas main 
Dump of assembler code for function main: 
0x804842c <main>: push %ebp 
0x804842d <main+1>: mov %esp,%ebp 
0x804842f <main+3>: cmpl $0x2,0x8(%ebp) 
0x8048433 <main+7>: jne 0x8048448 <main+28> 
0x8048435 <main+9>: mov 0xc(%ebp),%eax 
0x8048438 <main+12>: add $0x4,%eax 
0x804843b <main+15>: mov (%eax),%edx 
0x804843d <main+17>: push %edx 
0x804843e <main+18>: call 0x8048400 <vulFunc> 
0x8048443 <main+23>: add $0x4,%esp 
0x8048446 <main+26>: jmp 0x804845b <main+47> 
0x8048448 <main+28>: mov 0xc(%ebp),%eax 
0x804844b <main+31>: mov (%eax),%edx 
0x804844d <main+33>: push %edx 
0x804844e <main+34>: push $0x80484bb 
0x8048453 <main+39>: call 0x8048330 <printf> 
0x8048458 <main+44>: add $0x8,%esp 
0x804845b <main+47>: leave 
0x804845c <main+48>: ret 
0x804845d <main+49>: nop 
0x804845e <main+50>: nop 
0x804845f <main+51>: nop 
End of assembler dump. 
(gdb) disas vulFunc 
Dump of assembler code for function vulFunc: 
0x8048400 <vulFunc>: push %ebp 
0x8048401 <vulFunc+1>: mov %esp,%ebp 
0x8048403 <vulFunc+3>: sub $0xc,%esp 
0x8048406 <vulFunc+6>: mov 0x8(%ebp),%eax 
0x8048409 <vulFunc+9>: push %eax 
0x804840a <vulFunc+10>: lea 0xfffffff4(%ebp),%eax 
0x804840d <vulFunc+13>: push %eax 
0x804840e <vulFunc+14>: call 0x8048340 <strcpy> 
0x8048413 <vulFunc+19>: add $0x8,%esp 
0x8048416 <vulFunc+22>: lea 0xfffffff4(%ebp),%eax 
0x8048419 <vulFunc+25>: push %eax 
0x804841a <vulFunc+26>: push $0x80484b0 
0x804841f <vulFunc+31>: call 0x8048330 <printf> 
0x8048424 <vulFunc+36>: add $0x8,%esp 
0x8048427 <vulFunc+39>: leave 
0x8048428 <vulFunc+40>: ret 
0x8048429 <vulFunc+41>: lea 0x0(%esi),%esi 
End of assembler dump. 


這里我們只對(duì)所關(guān)心的main和vulFunc兩個(gè)函數(shù)進(jìn)行反匯編分析. 

ii) 進(jìn)程的運(yùn)行及其在內(nèi)存中的情況分析 
我們用gdb來跟蹤看看進(jìn)程是如何在內(nèi)存中運(yùn)行的. 

首先把程序調(diào)入. 
bash$ gdb p 
GNU gdb 19991004 
Copyright 1998 Free Software Foundation, Inc. 
GDB is free software, covered by the GNU General Public License, and you are 
welcome to change it and/or distribute copies of it under certain conditions. 
Type "show copying" to see the conditions. 
There is absolutely no warranty for GDB. Type "show warranty" for details. 
This GDB was configured as "i386-redhat-linux"... 
(gdb) 

把斷點(diǎn)設(shè)到main的第一條可執(zhí)行匯編指令上 
(gdb) b *0x804842c 
Breakpoint 1 at 0x804842c 

運(yùn)行程序 
(gdb) r AAAAAAAA 
Starting program: /home/vcat/p AAAAAAAA 

Breakpoint 1, 0x804842c in main () 

在斷點(diǎn)處停下來了. 
看一下這時(shí)各寄存器的值 
(gdb) i reg 
eax 0x4010b3f8 1074836472 
ecx 0x804842c 134513708 
edx 0x4010d098 1074843800 
ebx 0x4010c1ec 1074840044 
esp 0xbffff6bc -1073744196 
ebp 0xbffff6d8 -1073744168 
esi 0x4000ae60 1073786464 
edi 0xbffff704 -1073744124 
eip 0x804842c 134513708 
eflags 0x246 582 
cs 0x23 35 
ss 0x2b 43 
ds 0x2b 43 
es 0x2b 43 
fs 0x0 0 
gs 0x0 0 
cwd 0xffff037f -64641 
swd 0xffff0000 -65536 
twd 0xffffffff -1 
fip 0x40034d70 1073958256 
fcs 0x35d0023 56426531 
fopo 0xbfffe400 -1073748992 
fos 0xffff002b -65493 

我們這里關(guān)心的是棧底(ebp), 棧頂(esp)及指令寄存器(eip). 
此時(shí), ebp的值為0xbffff6d8, esp的值為0xbffff6bc, 相差28個(gè)字節(jié). 
eip的值為0x804842c, 正好是我們所設(shè)的斷點(diǎn). 
(注: 這里的值可能會(huì)隨著程序運(yùn)行在不同的系統(tǒng)環(huán)境而不同) 

我們?cè)倏纯串?dāng)前棧幀里有什么內(nèi)容? 
(gdb) x/8x $esp 
0xbffff6bc: 0x400349cb 0x00000002 0xbffff704 0xbffff710 
0xbffff6cc: 0x40013868 0x00000002 0x08048350 0x00000000 

也就是說, main函數(shù)剛被調(diào)用時(shí)進(jìn)程在內(nèi)存中的相關(guān)部份的影像是這樣的: 
(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- ebp (調(diào)用main函數(shù)前的ebp) 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  
+--------+ 
00000002  
+--------+ 
400349cb  
0xbffff6bc +--------+ <-- esp (調(diào)用main函數(shù)前的esp) 
 ......   
(內(nèi)存低址) 


我們看看接下來的指令做了些什么? 
0x804842c <main>: push %ebp ; esp的值等于esp-4(因?yàn)閑bp是32位); 
; 把ebp的值放入esp所指的32位內(nèi)存單 
; 元(注: 這里保存棧底). 
0x804842d <main+1>: mov %esp,%ebp ; ebp的值等于esp的值(注: 這里把原來 
; 的棧頂做為新的棧底). 

運(yùn)行這兩條指令, 然后看一下寄存器內(nèi)容和棧的情況. 
(gdb) si 
0x804842d in main () 
(gdb) si 
0x804842f in main () 
(gdb) i reg 
eax 0x4010b3f8 1074836472 
ecx 0x804842c 134513708 
edx 0x4010d098 1074843800 
ebx 0x4010c1ec 1074840044 
esp 0xbffff6b8 -1073744200 
ebp 0xbffff6b8 -1073744200 
esi 0x4000ae60 1073786464 
edi 0xbffff704 -1073744124 
eip 0x804842f 134513711 
eflags 0x346 838 
cs 0x23 35 
ss 0x2b 43 
ds 0x2b 43 
es 0x2b 43 
fs 0x0 0 
gs 0x0 0 
cwd 0xffff037f -64641 
swd 0xffff0000 -65536 
twd 0xffffffff -1 
fip 0x40034d70 1073958256 
fcs 0x35d0023 56426531 
fopo 0xbfffe400 -1073748992 
fos 0xffff002b -65493 
(gdb) x/9x $esp 
0xbffff6b8: 0xbffff6d8 0x400349cb 0x00000002 0xbffff704 
0xbffff6c8: 0xbffff710 0x40013868 0x00000002 0x08048350 
0xbffff6d8: 0x00000000 

此時(shí)進(jìn)程的相關(guān)影像為: 
(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  
+--------+ 
00000002  
+--------+ 
400349cb  
0xbffff6bc +--------+ <-- 調(diào)用main函數(shù)前的esp 
bffff6d8  
0xbffff6b8 +--------+ <-- ebp, esp 
 ......   
(內(nèi)存低址) 

接下來的兩條指令: 
0x804842f <main+3>: cmpl $0x2,0x8(%ebp) ; 2和ebp+8所指向的內(nèi)存(32位--4 
; 個(gè)字節(jié))里面所放的內(nèi)容比較. 
0x8048433 <main+7>: jne 0x8048448 <main+28> ; 如果不等則跳到0x08048448地址 
; 處繼續(xù)執(zhí)行, 否則執(zhí)行下條指令. 
這里我們可以看到這是C語言語句 
if(argc == 2) 

... 

else 

... 

的等價(jià)匯編語句. 內(nèi)存地址ebp+8處存放的是argc的值. 
(gdb) x/x $ebp+8 
0xbffff6c0: 0x00000002 

我們來看看在調(diào)用vulFunc函數(shù)前的指令: 
0x8048435 <main+9>: mov 0xc(%ebp),%eax ; 把內(nèi)存地址ebp+12處的四個(gè)字節(jié)的 
; 內(nèi)容放到eax里. 
0x8048438 <main+12>: add $0x4,%eax ; eax等于eax+4. 
0x804843b <main+15>: mov (%eax),%edx ; 把eax指向的四個(gè)內(nèi)存字節(jié)單元里 
; 的內(nèi)容賦給edx 
0x804843d <main+17>: push %edx ; esp等于esp-4, 把edx的值放到esp 
; 所指的內(nèi)存地址的四個(gè)字節(jié)單元里. 

看看ebp+12處放的是什么? 
(gdb) x/x $ebp+12 
0xbffff6c4: 0xbffff704 
懷疑這里放的是指向argv[0]字串的地址的地址, 看看是不是 
(gdb) x/x 0xbffff704 
0xbffff704: 0xbffff83e 
(gdb) x/1s 0xbffff83e 
0xbffff83e: "/home/vcat/p" 
果然是. 那么$ebp+12的所指的四個(gè)字節(jié)的內(nèi)容(argv[0]字串的地址)加上四, 應(yīng)該就是指向 
argv[1]字串的地址了. 
(gdb) x/x 0xbffff704+4 
0xbffff708: 0xbffff856 
(gdb) x/1s 0xbffff856 
0xbffff856: "AAAAAAAA" 

可以看出, 這四條指令是用來計(jì)算argv[1](即所輸入的字串"AAAAAAAA"在內(nèi)存中的起始地址), 
然后把該地址壓入棧中做為參數(shù)傳給即將被調(diào)用的函數(shù)vulFunc的. 

設(shè)個(gè)斷點(diǎn)在0x804843e, 讓程序繼續(xù)執(zhí)行到調(diào)用vulFunc函數(shù)之前. 
(gdb) b *0x804843e 
Breakpoint 2 at 0x804843e 
(gdb) c 
Continuing. 

(gdb) i reg 
eax 0xbffff708 -1073744120 
ecx 0x804842c 134513708 
edx 0xbffff856 -1073743786 
ebx 0x4010c1ec 1074840044 
esp 0xbffff6b4 -1073744204 
ebp 0xbffff6b8 -1073744200 
esi 0x4000ae60 1073786464 
edi 0xbffff704 -1073744124 
eip 0x804843e 134513726 
eflags 0x282 642 
(以下省略) 
... 

(gdb) x/10x $esp 
0xbffff6b4: 0xbffff856 0xbffff6d8 0x400349cb 0x00000002 
0xbffff6c4: 0xbffff704 0xbffff710 0x40013868 0x00000002 
0xbffff6d4: 0x08048350 0x00000000 

此時(shí)的進(jìn)程在內(nèi)存中的相關(guān)影像為: 
(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  argv的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  
0xbffff6bc +--------+ <-- 調(diào)用main函數(shù)前的esp 
bffff6d8  調(diào)用main函數(shù)前的ebp 
0xbffff6b8 +--------+ <-- main函數(shù)的ebp 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的開始地址 
0xbffff6b4 +--------+ <-- main函數(shù)的esp 
 ......   
(內(nèi)存低址) 

單步執(zhí)行 
(gdb) si 
0x8048400 in vulFunc () 

好, 現(xiàn)在進(jìn)入vulFunc函數(shù)了. 
(gdb) i reg 
eax 0xbffff708 -1073744120 
ecx 0x804842c 134513708 
edx 0xbffff856 -1073743786 
ebx 0x4010c1ec 1074840044 
esp 0xbffff6b0 -1073744208 
ebp 0xbffff6b8 -1073744200 
esi 0x4000ae60 1073786464 
edi 0xbffff704 -1073744124 
eip 0x8048400 134513664 
eflags 0x382 898 
(以下省略) 
... 

這時(shí)esp已經(jīng)變?yōu)?xbffff6b0, 和以前的值0xbffff6b4比較相差四個(gè)字節(jié). 
我們來看看到底壓了什么東西入棧. 
(gdb) x/11x $esp 
0xbffff6b0: 0x08048443 0xbffff856 0xbffff6d8 0x400349cb 
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868 
0xbffff6d0: 0x00000002 0x08048350 0x00000000 

原來是main函數(shù)里調(diào)用vulFunc函數(shù)的指令的后續(xù)指令的地址--即vulFunc函數(shù)的返回地址. 
這是我們的第一個(gè)焦點(diǎn). 

... 
0x804843e <main+18>: call 0x8048400 <vulFunc> 
0x8048443 <main+23>: add $0x4,%esp 
... 

我們接著分析vulFunc函數(shù). 
0x8048400 <vulFunc>: push %ebp 
0x8048401 <vulFunc+1>: mov %esp,%ebp 
0x8048403 <vulFunc+3>: sub $0xc,%esp ; esp等于esp-12, 棧幀大小增加12個(gè)字節(jié). 

前面兩條指令的功能和main函數(shù)的一樣, 用來保存調(diào)用函數(shù)棧幀的棧底ebp和設(shè)置被調(diào)用函 
數(shù)棧幀棧底. 
即: 保存調(diào)用函數(shù)的棧幀棧底, 調(diào)用函數(shù)棧幀的棧頂變?yōu)楸徽{(diào)用函數(shù)的棧底. 可以看出當(dāng)前 
(被調(diào)用函數(shù))的棧幀為空時(shí), ebp和esp的值相等. 


第三條指令在棧幀中分配了0xc(十二)個(gè)字節(jié)的內(nèi)存空間, 注意到里面的內(nèi)容是垃圾. 
(gdb) si 
0x8048401 in vulFunc () 
(gdb) si 
0x8048403 in vulFunc () 
(gdb) si 
0x8048406 in vulFunc () 

(gdb) x/15x $esp 
0xbffff6a0: 0x4000ae60 0xbffff704 0xbffff6b8 0xbffff6b8 
0xbffff6b0: 0x08048443 0xbffff856 0xbffff6d8 0x400349cb 
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868 
0xbffff6d0: 0x00000002 0x08048350 0x00000000 

此時(shí)進(jìn)程在內(nèi)存中相關(guān)的影像為: 
(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  argv的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  
0xbffff6bc +--------+ <-- 調(diào)用main函數(shù)前的esp 
bffff6d8  調(diào)用main函數(shù)前的ebp 
0xbffff6b8 +--------+ <-- main函數(shù)的ebp 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff6b4 +--------+ 
08048443  vulFunc函數(shù)的返回地址 
0xbffff6b0 +--------+ <-- 調(diào)用vulFunc函數(shù)前的esp 
bffff6b8  main函數(shù)的ebp 
0xbffff6ac +--------+ <-- vulFunc函數(shù)的ebp 
bffff6b8  (垃圾) 
0xbffff6a8 +--------+ 
bffff704  (垃圾) 
0xbffff6a4 +--------+ 
4000ae60  (垃圾) 
0xbffff6a0 +--------+ <-- vulFunc的當(dāng)前esp 
 ......   
(內(nèi)存低址) 

再看看下面的四條指令. 
0x8048406 <vulFunc+6>: mov 0x8(%ebp),%eax ; 把ebp+8指向的內(nèi)存單元(4字節(jié))里 
; 的內(nèi)容賦給eax. 
從上圖看出vulFunc函數(shù)棧幀的ebp+8四字節(jié)內(nèi)存單元里放的是指向"AAAAAAAA"字符串的起始地址. 

0x8048409 <vulFunc+9>: push %eax ; eax的值入棧. 
把指向"AAAAAAAA"字符串的起始地址入棧. 

0x804840a <vulFunc+10>: lea 0xfffffff4(%ebp),%eax 
哇! 好嚇人呀! 這條指令是干什么的? 讓我們慢慢來分析一下. 
這條指令是把ebp+0xfffffff4做為地址值賦給eax. 
但是ebp的值加上0xfffffff4指向那里呀, 這是我們要弄清楚的. 
這里如果我們按正數(shù)來加, 那是不行的. 
實(shí)際上這個(gè)十六進(jìn)制的0xfffffff4所表示的是負(fù)數(shù), 要知道它的值, 讓我們來算一下. 

F F F F F F F 4 
+----+----+----+----+----+----+----+----+ 
1111 1111 1111 1111 1111 1111 1111 0100  
+----+----+----+----+----+----+----+----+ 

取反 

0 0 0 0 0 0 0 B 
+----+----+----+----+----+----+----+----+ 
0000 0000 0000 0000 0000 0000 0000 1011  
+----+----+----+----+----+----+----+----+ 

加一 

0 0 0 0 0 0 0 C 
+----+----+----+----+----+----+----+----+ 
0000 0000 0000 0000 0000 0000 0000 1100  
+----+----+----+----+----+----+----+----+ 

也就是負(fù)的0xc. ebp+0xfffffff4, 即ebp-0xc. 

所以ebp+0xfffffff4, 就是現(xiàn)在棧頂指向的那十二個(gè)字節(jié)的起始地址. 

0x804840d <vulFunc+13>: push %eax 
接著把得到的地址入棧. 

讓程序運(yùn)行到調(diào)用strcpy函數(shù)之前看看 
(gdb) b *0x804840e 
Breakpoint 3 at 0x804840e 
(gdb) c 
Continuing. 

Breakpoint 3, 0x804840e in vulFunc () 
(gdb) x/17x $esp 
0xbffff698: 0xbffff6a0 0xbffff856 0x4000ae60 0xbffff704 
0xbffff6a8: 0xbffff6b8 0xbffff6b8 0x08048443 0xbffff856 
0xbffff6b8: 0xbffff6d8 0x400349cb 0x00000002 0xbffff704 
0xbffff6c8: 0xbffff710 0x40013868 0x00000002 0x08048350 
0xbffff6d8: 0x00000000 

這時(shí)進(jìn)程在內(nèi)存的相關(guān)影像為: 
(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  argv的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  
0xbffff6bc +--------+ <-- 調(diào)用main函數(shù)前的esp 
bffff6d8  調(diào)用main函數(shù)前的ebp 
0xbffff6b8 +--------+ <-- main函數(shù)的ebp 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff6b4 +--------+ 
08048443  vulFunc函數(shù)的返回地址 
0xbffff6b0 +--------+ <-- 調(diào)用vulFunc函數(shù)前的esp 
bffff6b8  main函數(shù)的ebp 
0xbffff6ac +--------+ <-- vulFunc函數(shù)的ebp 
bffff6b8  (垃圾) 
0xbffff6a8 +--------+ 
bffff704  (垃圾) 
0xbffff6a4 +--------+ 
4000ae60  (垃圾) 
0xbffff6a0 +--------+ 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff69c +--------+ 
bffff6a0  vulFunc函數(shù)棧幀中分配的十二個(gè)字節(jié)起始地址 
0xbffff698 +--------+ <-- vulFunc的當(dāng)前esp 
 ......   
(內(nèi)存低址) 

我們這里不關(guān)心strcpy函數(shù)具體運(yùn)行, 把斷點(diǎn)設(shè)到調(diào)用它的后續(xù)指令. 
(gdb) b *0x8048413 
Breakpoint 4 at 0x8048413 
(gdb) c 
Continuing. 

Breakpoint 4, 0x8048413 in vulFunc () 
(gdb) x/17x $esp 
0xbffff698: 0xbffff6a0 0xbffff856 0x41414141 0x41414141 
0xbffff6a8: 0xbffff600 0xbffff6b8 0x08048443 0xbffff856 
0xbffff6b8: 0xbffff6d8 0x400349cb 0x00000002 0xbffff704 
0xbffff6c8: 0xbffff710 0x40013868 0x00000002 0x08048350 
0xbffff6d8: 0x00000000 

這時(shí)進(jìn)程在內(nèi)存中的相關(guān)影像為: 
(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  argv的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  
0xbffff6bc +--------+ <-- 調(diào)用main函數(shù)前的esp 
bffff6d8  調(diào)用main函數(shù)前的ebp 
0xbffff6b8 +--------+ <-- main函數(shù)的ebp 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff6b4 +--------+ 
08048443  vulFunc函數(shù)的返回地址 
0xbffff6b0 +--------+ <-- 調(diào)用vulFunc函數(shù)前的esp 
bffff6b8  main函數(shù)的ebp 
0xbffff6ac +--------+ <-- vulFunc函數(shù)的ebp 
bffff600  
0xbffff6a8 +--------+ 
41414141  
0xbffff6a4 +--------+ 
41414141  
0xbffff6a0 +--------+ 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff69c +--------+ 
bffff6a0  vulFunc函數(shù)棧幀中分配的十二個(gè)字節(jié)起始地址 
0xbffff698 +--------+ <-- vulFunc的當(dāng)前esp 
 ......   
(內(nèi)存低址) 

我們注意到在vulFunc函數(shù)棧幀中所分配的那十二個(gè)字節(jié), 從傳遞給strcpy函數(shù)的起始 
地址處被我們所輸入的八個(gè)'A'(十六進(jìn)制0x41)填充了. 

這是我們的第二個(gè)焦點(diǎn). 

同時(shí)也注意到, 內(nèi)存地址0xbffff6a8所指向的四個(gè)字節(jié)的內(nèi)容由原來的垃圾數(shù)據(jù)0xbffff6b8 
變成了bffff600. 

低字節(jié)的00應(yīng)該就是字符串"AAAAAAAA"的零結(jié)尾字節(jié). 

所以得出結(jié)論: vulFunc函數(shù)棧幀中分配的那十二個(gè)字節(jié)是給局部變量buf(緩沖區(qū))的. 
這里會(huì)奇怪: 程序中buf緩沖區(qū)只定義了十個(gè)字節(jié)的大小, 為什么為它分配了十二個(gè)字 
節(jié)? 原因是: 內(nèi)存的分配是以四字節(jié)為單位的.所以十個(gè)字節(jié)(4+4+2)要用三個(gè)內(nèi)存分 
配單元, 3*4=12. 

如果我們?cè)诿钚刑峁┑淖执L度為十(多兩個(gè)字符, 剛好是程序中定義的緩沖區(qū)的大 
小), 那么內(nèi)存地址0xbffff6a8所指向的四個(gè)字節(jié)的內(nèi)容將是bf004141; 如果增加到十 
一個(gè), 內(nèi)存地址0xbffff6a8所指向的四個(gè)字節(jié)的內(nèi)容為00414141, 剛好填滿棧幀中分配 
給buf的內(nèi)存空間. 可以看出, 在命令行中提供的字串長度小于12, 程序是不會(huì)出錯(cuò)的. 

現(xiàn)在讓我們看看字串長度等于十二的情況, 這時(shí)0xbffff6a8所指向的四個(gè)字節(jié)的內(nèi)存單 
元已被41414141填滿.0xbffff6ac所指向的四個(gè)字節(jié)的內(nèi)存單元的低字節(jié)被00所填, 其內(nèi) 
容變?yōu)閎ffff600, 從上面的影像圖可知: 這個(gè)內(nèi)存單元里保存的是調(diào)用函數(shù)的ebp. 也就 
是說, 當(dāng)字串長度大于或等于十二時(shí), 調(diào)用函數(shù)的ebp被復(fù)蓋. 

從進(jìn)程的影像圖可以看出, 要想全面復(fù)蓋vulFunc函數(shù)的返回地址, 則字節(jié)串的長度至少 
要二十(12+8)個(gè)字節(jié). 

我們繼續(xù)分析后面的指令: 
0x8048413 <vulFunc+19>: add $0x8,%esp ; 棧幀縮小8個(gè)字節(jié)--放棄了兩個(gè)內(nèi)存存儲(chǔ)單元. 

可以看到, 在調(diào)用strcpy前, 依次壓了s和buf的地址入棧, 現(xiàn)在這條指令是把這兩個(gè)地址拋棄. 

所以可以得出, Linux x86系統(tǒng)在調(diào)用函數(shù)時(shí)(其實(shí)是編譯器所生成的機(jī)器指令), 所傳給 
被調(diào)用函數(shù)的參數(shù)是由調(diào)用函數(shù)從右到左依次入棧的. 
如現(xiàn)在的strcpy(buf, s), 首先是s先入棧, 然后是buf. 參數(shù)的出棧也由調(diào)用函數(shù)負(fù)責(zé). 

0x8048416 <vulFunc+22>: lea 0xfffffff4(%ebp),%eax 
0x8048419 <vulFunc+25>: push %eax 
這兩條指令和前面的一樣, 把a(bǔ)rgv[1](即"AAAAAAAA"字串)的起始地址入棧. 

0x804841a <vulFunc+26>: push $0x80484b0 
先看一下0x80484b0里面放的是什么, 雖然很明顯是即將調(diào)用的printf函數(shù)的第一個(gè)參數(shù)的地址. 
(gdb) x/1s 0x80484b0 
0x80484b0 <_IO_stdin_used+4>: "String=%s\n" 
果然是. 

下面的兩條指令就是調(diào)用printf函數(shù)和拋棄在棧中的兩個(gè)參數(shù)了. 
0x804841f <vulFunc+31>: call 0x8048330 <printf> 
0x8048424 <vulFunc+36>: add $0x8,%esp 

我們?cè)?x08048427 leave 指令的前面設(shè)個(gè)斷點(diǎn)并繼續(xù)運(yùn)行. 
(gdb) b *0x8048427 
Breakpoint 5 at 0x8048427 
(gdb) c 
Continuing. 
String=AAAAAAAA 

Breakpoint 5, 0x8048427 in vulFunc () 
屏幕輸出了"String=AAAAAAAA". 

這時(shí)棧幀的內(nèi)容為: 
(gdb) x/15x $esp 
0xbffff6a0: 0x41414141 0x41414141 0xbffff600 0xbffff6b8 
0xbffff6b0: 0x08048443 0xbffff856 0xbffff6d8 0x400349cb 
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868 
0xbffff6d0: 0x00000002 0x08048350 0x00000000 

進(jìn)程在內(nèi)存中的相關(guān)影像為: 
(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  argv的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  
0xbffff6bc +--------+ <-- 調(diào)用main函數(shù)前的esp 
bffff6d8  調(diào)用main函數(shù)前的ebp 
0xbffff6b8 +--------+ <-- main函數(shù)的ebp 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff6b4 +--------+ 
08048443  vulFunc函數(shù)的返回地址 
0xbffff6b0 +--------+ <-- 調(diào)用vulFunc函數(shù)前的esp 
bffff6b8  main函數(shù)的ebp 
0xbffff6ac +--------+ <-- vulFunc函數(shù)的ebp 
bffff600  
0xbffff6a8 +--------+ 
41414141  
0xbffff6a4 +--------+ 
41414141  
0xbffff6a0 +--------+ <-- vulFunc的當(dāng)前esp 
bffff856  (垃圾) 字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff69c +--------+ 
bffff6a0  (垃圾) vulFunc函數(shù)棧幀中分配的十二個(gè)字節(jié)起始地址 
0xbffff698 +--------+ 
 ......   
(內(nèi)存低址) 

各寄存器的狀況: 
(gdb) i reg 
eax 0x10 16 
ecx 0x400 1024 
edx 0x4010a980 1074833792 
ebx 0x4010c1ec 1074840044 
esp 0xbffff6a0 -1073744224 
ebp 0xbffff6ac -1073744212 
esi 0x4000ae60 1073786464 
edi 0xbffff704 -1073744124 
eip 0x8048427 134513703 
eflags 0x296 662 
(以下省略) 
... 

請(qǐng)注意: 此時(shí)esp的內(nèi)容為0xbffff6a0, ebp的內(nèi)容為0xbffff6ac 
單步運(yùn)行l(wèi)eave指令, 然后看一下寄存器的情況. 
(gdb) si 
0x8048428 in vulFunc () 
(gdb) i reg 
eax 0x10 16 
ecx 0x400 1024 
edx 0x4010a980 1074833792 
ebx 0x4010c1ec 1074840044 
esp 0xbffff6b0 -1073744208 
ebp 0xbffff6b8 -1073744200 
esi 0x4000ae60 1073786464 
edi 0xbffff704 -1073744124 
eip 0x8048428 134513704 
eflags 0x396 918 
(以下省略) 
... 

此時(shí)的esp的內(nèi)容為0xbffff6b0, 即執(zhí)行l(wèi)eave指令前的ebp內(nèi)容0xbffff6ac+4; 
ebp的內(nèi)容為0xbffff6b8, 這個(gè)值從那來的呢? 看一下此時(shí)進(jìn)程在內(nèi)存中的影像, 正好是 
vulFunc函數(shù)的ebp指向的內(nèi)存的內(nèi)容, 而隨著這個(gè)值的出棧, esp的值正好為0xbffff6b0. 

由此可見, leave指令其實(shí)等價(jià)于 
mov %ebp,%esp 
pop %ebp 
這兩條指令, 正好和剛進(jìn)入被調(diào)用函數(shù)時(shí) 
push %ebp 
mov %esp,%ebp 
這兩條指令的功能相反. 
也就是說leave指令拋棄了被調(diào)用函數(shù)的棧幀, 恢復(fù)了調(diào)用函數(shù)的棧幀. 

此時(shí)棧中相關(guān)的內(nèi)容: 
(gdb) x/11x $esp 
0xbffff6b0: 0x08048443 0xbffff856 0xbffff6d8 0x400349cb 
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868 
0xbffff6d0: 0x00000002 0x08048350 0x00000000 

進(jìn)程在內(nèi)存中的相關(guān)影像: 

(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  argv的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  
0xbffff6bc +--------+ <-- 調(diào)用main函數(shù)前的esp 
bffff6d8  調(diào)用main函數(shù)前的ebp 
0xbffff6b8 +--------+ <-- main函數(shù)的ebp 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff6b4 +--------+ 
08048443  vulFunc函數(shù)的返回地址 
0xbffff6b0 +--------+ <-- 當(dāng)前esp 
bffff6b8  (垃圾) main函數(shù)的ebp 
0xbffff6ac +--------+ 
bffff600  (垃圾) 
0xbffff6a8 +--------+ 
41414141  (垃圾) 
0xbffff6a4 +--------+ 
41414141  (垃圾) 
0xbffff6a0 +--------+ 
bffff856  (垃圾) 字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff69c +--------+ 
bffff6a0  (垃圾) vulFunc函數(shù)棧幀中分配的十二個(gè)字節(jié)起始地址 
0xbffff698 +--------+ 
 ......   
(內(nèi)存低址) 

繼續(xù)執(zhí)行下條指令: ret 
(gdb) si 
0x8048443 in main () 
(gdb) i reg 
eax 0x10 16 
ecx 0x400 1024 
edx 0x4010a980 1074833792 
ebx 0x4010c1ec 1074840044 
esp 0xbffff6b4 -1073744204 
ebp 0xbffff6b8 -1073744200 
esi 0x4000ae60 1073786464 
edi 0xbffff704 -1073744124 
eip 0x8048443 134513731 
eflags 0x396 918 
(以下省略) 
... 

可以看出, 從棧中彈出0x8048443(vulFunc函數(shù)調(diào)用的返回地址)給了eip. 
至此vulFunc函數(shù)調(diào)用完畢, 返回到main函數(shù)繼續(xù)執(zhí)行. 

值得注意的是: 如果象上面所說的, 我們輸入的字串長度為二十個(gè)'A'--剛好復(fù)蓋完0xbffff6b0 
所指的單元, 那么此時(shí)從棧中彈出給eip的內(nèi)容將是0x41414141, 而不是0x8048443, 程序 
將跳到0x41414141去執(zhí)行那里的指令, 由于0x41414141對(duì)于當(dāng)前進(jìn)程來說是不可訪問的, 
所以導(dǎo)致段出錯(cuò)(Segmentation fault), 進(jìn)程停止執(zhí)行. 

這是我們的第三個(gè)焦點(diǎn). 

如果我們能計(jì)算好位移(offset), 用我們準(zhǔn)備好的代碼的入口地址來覆蓋0xbffff6b0所 
指的單元, 那么從棧中彈出給eip的內(nèi)容就是我們的代碼的入口地址, 程序?qū)⑻轿覀兊?nbsp;
代碼去繼續(xù)執(zhí)行. 

分析到這里, 我們已經(jīng)清楚了C語言函數(shù)調(diào)用的機(jī)制了. main函數(shù)的后續(xù)指令對(duì)于我們的 
分析已無關(guān)緊要. 但是為了保持文章的完整, 我們繼續(xù)再往下看看. 

此時(shí)棧的情況: 
(gdb) x/10x $esp 
0xbffff6b4: 0xbffff856 0xbffff6d8 0x400349cb 0x00000002 
0xbffff6c4: 0xbffff704 0xbffff710 0x40013868 0x00000002 
0xbffff6d4: 0x08048350 0x00000000 

進(jìn)程在內(nèi)存中的相關(guān)影像: 

(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  argv的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  
0xbffff6bc +--------+ <-- 調(diào)用main函數(shù)前的esp 
bffff6d8  調(diào)用main函數(shù)前的ebp 
0xbffff6b8 +--------+ <-- main函數(shù)的ebp 
bffff856  字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff6b4 +--------+ <-- 當(dāng)前esp 
08048443  (垃圾) vulFunc函數(shù)的返回地址 
0xbffff6b0 +--------+ 
bffff6b8  (垃圾) main函數(shù)的ebp 
0xbffff6ac +--------+ 
bffff600  (垃圾) 
0xbffff6a8 +--------+ 
41414141  (垃圾) 
0xbffff6a4 +--------+ 
41414141  (垃圾) 
0xbffff6a0 +--------+ 
bffff856  (垃圾) 字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff69c +--------+ 
bffff6a0  (垃圾) vulFunc函數(shù)棧幀中分配的十二個(gè)字節(jié)起始地址 
0xbffff698 +--------+ 
 ......   
(內(nèi)存低址) 


再看看后續(xù)的指令做了些什么? 
0x8048443 <main+23>: add $0x4,%esp ; 拋棄棧中為被調(diào)用函數(shù)準(zhǔn)備的參數(shù). 
0x8048446 <main+26>: jmp 0x804845b <main+47> ; 跳轉(zhuǎn)到0x804845b繼續(xù)執(zhí)行 
0x8048448 <main+28>: mov 0xc(%ebp),%eax ; 0x8048433 jne的條件判斷跳轉(zhuǎn) 
; 入口(即argc!=2的情況) 
; 把ebp+0xc所指向的內(nèi)存單元的 
; 內(nèi)容賦給eax, 從上面的分析我 
; 們知道里面放的是argv的地址 
0x804844b <main+31>: mov (%eax),%edx ; 把eax指向的地址的內(nèi)存單元里 
; 的內(nèi)容賦給edx, 我們知道argv 
; 是個(gè)數(shù)組, argv的值就是argv[0] 
0x804844d <main+33>: push %edx ; 把a(bǔ)rgv[0]入棧. 注意這里的 
; argv[0]其實(shí)是個(gè)地址值. 
0x804844e <main+34>: push $0x80484bb ; 把常數(shù)0x80484bb入棧 
; 以上為調(diào)用printf函數(shù)準(zhǔn)備參數(shù). 
0x8048453 <main+39>: call 0x8048330 <printf> ; 調(diào)用printf函數(shù) 
0x8048458 <main+44>: add $0x8,%esp ; 拋棄為調(diào)用printf函數(shù)準(zhǔn)備的參數(shù) 
0x804845b <main+47>: leave ; 恢復(fù)調(diào)用main函數(shù)的函數(shù)的棧幀 
0x804845c <main+48>: ret ; 返回到調(diào)用main函數(shù)的函數(shù) 

估計(jì)0x80484bb指向的是printf函數(shù)的format字串, 看看是不是? 
(gdb) x/1s 0x80484bb 
0x80484bb <_IO_stdin_used+15>: "Usage: %s <A string>\n" 
果然是. 那從0x8048448到0x8048458這段指令就是C語言 
printf("Usage: %s <A string>\n", argv[0]); 
的等價(jià)匯編語句了. 

我們把斷點(diǎn)設(shè)到0x804845b, 再繼續(xù)執(zhí)行. 
(gdb) b *0x804845b 
Breakpoint 6 at 0x804845b 
(gdb) c 
Continuing. 

Breakpoint 6, 0x804845b in main () 

下一條指令是leave, 應(yīng)該是恢復(fù)調(diào)用函數(shù)的函數(shù)的棧幀. 
單步執(zhí)行一下, 看看寄存器及棧的情況. 
(gdb) si 
0x804845c in main () 
(gdb) i reg 
eax 0x10 16 
ecx 0x400 1024 
edx 0x4010a980 1074833792 
ebx 0x4010c1ec 1074840044 
esp 0xbffff6bc -1073744196 
ebp 0xbffff6d8 -1073744168 
esi 0x4000ae60 1073786464 
edi 0xbffff704 -1073744124 
eip 0x804845c 134513756 
eflags 0x386 902 
(以下省略) 
... 

(gdb) x/8x $esp 
0xbffff6bc: 0x400349cb 0x00000002 0xbffff704 0xbffff710 
0xbffff6cc: 0x40013868 0x00000002 0x08048350 0x00000000 

下一條指令是ret, 我們知道棧頂放的是main函數(shù)的返回地址(0x400349cb). 

此時(shí)進(jìn)程在內(nèi)存中的相關(guān)影像: 

(內(nèi)存高址) 
 ......   
+--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  
+--------+ 
00000002  
+--------+ 
40013868  
+--------+ 
bffff710  
+--------+ 
bffff704  argv的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  main函數(shù)的返回地址 
0xbffff6bc +--------+ <-- 當(dāng)前esp 
bffff6d8  (垃圾) 調(diào)用main函數(shù)前的ebp 
0xbffff6b8 +--------+ 
bffff856  (垃圾) 字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff6b4 +--------+ 
08048443  (垃圾) vulFunc函數(shù)的返回地址 
0xbffff6b0 +--------+ 
bffff6b8  (垃圾) main函數(shù)的ebp 
0xbffff6ac +--------+ 
bffff600  (垃圾) 
0xbffff6a8 +--------+ 
41414141  (垃圾) 
0xbffff6a4 +--------+ 
41414141  (垃圾) 
0xbffff6a0 +--------+ 
bffff856  (垃圾) 字符串"AAAAAAAA"在內(nèi)存中的起始地址 
0xbffff69c +--------+ 
bffff6a0  (垃圾) vulFunc函數(shù)棧幀中分配的十二個(gè)字節(jié)起始地址 
0xbffff698 +--------+ 
 ......   
(內(nèi)存低址) 

再單步執(zhí)行, 返回到調(diào)用main函數(shù)的函數(shù) 
(gdb) si 
0x400349cb in __libc_start_main (main=0x804842c <main>, argc=2, argv=0xbffff704, init=0x80482c0 <_init>, 
fini=0x804848c <_fini>, rtld_fini=0x4000ae60 <_dl_fini>, stack_end=0xbffff6fc) 
at ../sysdeps/generic/libc-start.c:92 
92 ../sysdeps/generic/libc-start.c: No such file or directory. 

原來是 __libc_start_main 函數(shù)調(diào)用了我們的main函數(shù), 看來和概述里說的有些出入, 
但這對(duì)于我們來講不是很重要. 如果想看源碼, 請(qǐng)到../sysdeps/generic/libc-start.c 
文件中找. 
(gdb) x/16x $esp 
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868 
0xbffff6d0: 0x00000002 0x08048350 0x00000000 0x08048371 
0xbffff6e0: 0x0804842c 0x00000002 0xbffff704 0x080482c0 
0xbffff6f0: 0x0804848c 0x4000ae60 0xbffff6fc 0x40013e90 

從上面可以看到, stack_end=0xbffff6fc, 也就是說我們的進(jìn)程的棧底地址為0xbffff6fc, 
在調(diào)用__libc_start_main函數(shù)前依次推了如下七個(gè)參數(shù)入棧: 
0xbffff6fc -> 進(jìn)程的棧底 
0x4000ae60 -> _dl_fini函數(shù)的入口地址. 
0x0804848c -> _fini函數(shù)的入口地址 
0x080482c0 -> _init函數(shù)的入口地址 
0xbffff704 -> argv命令行參數(shù)地址的地址 
0x00000002 -> argc命令行參數(shù)個(gè)數(shù)值 
0x0804842c -> 我們的main函數(shù)入口 

從上面的分析可推出, 在內(nèi)存地址0xbffff6dc的內(nèi)容0x08048371就是__libc_start_main函數(shù) 
的返回地址了. 
我們來看看是什么函數(shù)調(diào)用了__libc_start_main. 
(gdb) disas 0x08048371 
Dump of assembler code for function _start: 
0x8048350 <_start>: xor %ebp,%ebp 
0x8048352 <_start+2>: pop %esi 
0x8048353 <_start+3>: mov %esp,%ecx 
0x8048355 <_start+5>: and $0xfffffff8,%esp 
0x8048358 <_start+8>: push %eax 
0x8048359 <_start+9>: push %esp 
0x804835a <_start+10>: push %edx 
0x804835b <_start+11>: push $0x804848c 
0x8048360 <_start+16>: push $0x80482c0 
0x8048365 <_start+21>: push %ecx 
0x8048366 <_start+22>: push %esi 
0x8048367 <_start+23>: push $0x804842c 
0x804836c <_start+28>: call 0x8048320 <__libc_start_main> 
0x8048371 <_start+33>: hlt 
0x8048372 <_start+34>: nop 
0x8048373 <_start+35>: nop 
(省略以下的nop) 
End of assembler dump. 

原來是_start函數(shù)調(diào)用了__libc_start_main函數(shù). 
至于_start函數(shù)調(diào)用__libc_start_main函數(shù)后, 接是如何調(diào)用_init函數(shù)和_dl_runtime_resove 
函數(shù)來調(diào)用共享庫函數(shù)和我們的main函數(shù)然后退出的, 已經(jīng)遠(yuǎn)遠(yuǎn)脫離了本文的主題, 這里不再繼 
續(xù)介紹. 

(gdb) x/1024x 0xbffff6f0 
0xbffff6f0: 0x0804848c 0x4000ae60 0xbffff6fc 0x40013e90 
0xbffff700: 0x00000002 0xbffff83e 0xbffff856 0x00000000 
0xbffff710: 0xbffff85f 0xbffff881 0xbffff88f 0xbffff89e 
0xbffff720: 0xbffff8c4 0xbffff8d2 0xbffff900 0xbffff91a 
0xbffff730: 0xbffff932 0xbffff94d 0xbffff9a8 0xbffff9df 
0xbffff740: 0xbffffaf3 0xbffffb06 0xbffffb11 0xbffffb31 
0xbffff750: 0xbffffb5a 0xbffffb68 0xbffffc72 0xbffffc7e 
0xbffff760: 0xbffffc8f 0xbffffca4 0xbffffcb4 0xbffffcbf 
0xbffff770: 0xbffffcd7 0xbffffcf5 0xbffffd0e 0xbffffd19 
0xbffff780: 0xbffffd23 0xbffffd6c 0xbffffd79 0xbffffda0 
0xbffff790: 0xbffffdb2 0xbffffdc1 0xbffffde6 0xbffffe08 
0xbffff7a0: 0xbffffe10 0xbfffffd3 0x00000000 0x00000003 
0xbffff7b0: 0x08048034 0x00000004 0x00000020 0x00000005 
0xbffff7c0: 0x00000006 0x00000006 0x00001000 0x00000007 
0xbffff7d0: 0x40000000 0x00000008 0x00000000 0x00000009 
0xbffff7e0: 0x08048350 0x0000000b 0x000001f5 0x0000000c 
0xbffff7f0: 0x000001f5 0x0000000d 0x00000004 0x0000000e 
0xbffff800: 0x00000004 0x00000010 0x008001bf 0x0000000f 
0xbffff810: 0xbffff839 0x00000000 0x00000000 0x00000000 
0xbffff820: 0x00000000 0x00000000 0x00000000 0x00000000 
0xbffff830: 0x00000000 0x00000000 0x38356900 0x682f0036 
0xbffff840: 0x2f656d6f 0x65776f74 0x74742f72 0x2f737775 
0xbffff850: 0x2f6c6469 0x41410070 0x41414141 0x4c004141 
0xbffff860: 0x4f535345 0x3d4e4550 0x73752f7c 0x69622f72 
... 
(省略) 
... 
0xbfffffd0: 0x54003a35 0x75413d5a 0x61727473 0x2f61696c 
0xbfffffe0: 0x0057534e 0x6d6f682f 0x6f742f65 0x2f726577 
0xbffffff0: 0x77757474 0x64692f73 0x00702f6c 0x00000000 
0xc0000000: Cannot access memory at address 0xc0000000 

我們知道內(nèi)存單元0xbffff704放的是指argv[0]的地址, 那么0xbffff708放的就是argv[1] 
的地址了. 0xbffff700里放的是argc的值. 

那么0xbffff710里放的是什么呢? 看樣子象是指向字符串的地址, 讓我們來看看. 
(gdb) x/1s 0xbffff85f 
0xbffff85f: "LESSOPEN= /usr/bin/lesspipe.sh %s" 
(gdb) 
0xbffff881: "HISTSIZE=1000" 
... 

再看看最后一個(gè). 
(gdb) x/1s 0xbfffffd3 
0xbfffffd3: "TZ=Australia/NSW" 
0xc0000000以后的地址空間已不是進(jìn)程能合法訪問的了. 

原來都是些SHELL的環(huán)境變量字符串. 

這一片東西是從內(nèi)存地址0xbffff839開始的, 讓我們?cè)倏纯? 
(gdb) x/1s 0xbffff839 
0xbffff839: "i586" 
(gdb) 
0xbffff83e: "/home/vcat/p" ===> 細(xì)心的朋友會(huì)發(fā)現(xiàn)這里已被俺改掉了, 
讓俺保留一點(diǎn)私隱吧 ;) 
(gdb) 
0xbffff856: "AAAAAAAA" 
(gdb) 
0xbffff85f: "LESSOPEN= /usr/bin/lesspipe.sh %s" 
... 

我們得出結(jié)論: 0xbffff700放的是argc的值; 0xbffff704放的是argv[0]的地址, 
0xbffff708放的是argv[1]的地址; 0xbffff710--0xbffffa4放的是指向各個(gè)環(huán)境變量 
字符串起始地址的指針; 從內(nèi)存地址0xbffff839開始依次存放的是: 系統(tǒng)平臺(tái)信息字 
串; 命令行字串; 環(huán)境變量字串. 

至于0xbffff7a8--0xbffff838里放的是什么, 還有待研究. 由于對(duì)本文不是至關(guān)重要, 
暫時(shí)放一下. 

分析到這, 我們來組合一下進(jìn)程在內(nèi)存的影像: 

(內(nèi)存高址) 

 ......   ...省略了一些我們不需要關(guān)心的區(qū) 
+--------+ 
00000000 \ 
0xbffffffc +--------+ \ 
 ......   \ 

 ......   \ 
0xbffff844 +--------+ 系統(tǒng)平臺(tái)信息串(如:"i586")和命令行參數(shù)及環(huán)境變量字串 
2f656d6f  / 
0xbffff840 +--------+ / 
682f0036  / 
0xbffff83c +--------+ / 
38356900 / --> 從內(nèi)存地址0xbffff839開始, 0x69353836="i586" 
0xbffff838 +--------+ 
 ......  \ 
里面放的是什么? 還有待研究 
 ......  / 
0xbffff7a8 +--------+ 
bfffffd3 \ 
0xbffff7a4 +--------+ \ 
 ......   \ 
...... 環(huán)境變量指針 
 ......   / 
0xbffff714 +--------+ / 
bffff85f / 
0xbffff710 +--------+ 
00000000  
0xbffff70c +--------+ 
bffff856  argv[1]的地址 
0xbffff708 +--------+ 
bffff83e  argv[0]的地址 
0xbffff704 +--------+ 
00000002  argc的值 
0xbffff700 +--------+ 
40013e90  ???? (和_dl_starting_up函數(shù)有關(guān)) 
0xbffff6fc +--------+ <-- 進(jìn)程的棧底 
bffff6fc  stack_end(進(jìn)程的棧底) 
0xbffff6f8 +--------+ 
4000ae60  _dl_fini函數(shù)入口地址 
0xbffff6f4 +--------+ 
0804848c  _fini函數(shù)入口地址 
0xbffff6f0 +--------+ 
080482c0  _init函數(shù)入口地址 
0xbffff6ec +--------+ 
bffff704  argv地址的地址 
0xbffff6e8 +--------+ 
00000002  argc的值 
0xbffff6e4 +--------+ 
0804842c  main函數(shù)的入口地址 
0xbffff6e0 +--------+ 
08048371  __libc_start_main的返回地址(指令hlt), 正常情況不會(huì)返回到這. 
0xbffff6dc +--------+ 
00000000  
0xbffff6d8 +--------+ <-- 調(diào)用main函數(shù)前的ebp 
08048350  _start函數(shù)的入口地址 
+--------+ 
00000002  argc的值 
+--------+ 
40013868  ???? 
+--------+ 
bffff710  環(huán)境變量指針的地址 
+--------+ 
bffff704  argv地址的地址(即argv[0]的地址) 
0xbffff6c4 +--------+ 
00000002  argc的值 
0xbffff6c0 +--------+ 
400349cb  main函數(shù)的返回地址 
0xbffff6bc +--------+ <-- 當(dāng)前esp