首先寄存器使用慣例:
eip :指令地址寄存器鞍陨,保存程序計(jì)數(shù)器的值,當(dāng)前執(zhí)行的指令的下一條指令的地址值添怔,16位中為ip湾戳,32位為eip贤旷。eip不可以直接賦值广料,一般都是cpu自動(dòng)加1來更新,指令call和ret以及jmp可以改變eip的值幼驶。
另外匯編代碼格式有ATT和intel格式艾杏,gcc和objdump的默認(rèn)格式就是ATT。幾個(gè)小區(qū)別盅藻,1首先是指令A(yù)TT匯編指令后面有一個(gè)l购桑,比如intel格式為mov,ATT格式為movl
2寄存器氏淑,ATT格式有%勃蜘,比如intel格式為ebp,ATT格式為%ebp
3還有一個(gè)最主要的區(qū)別就是操作指令的假残,操作數(shù)的順序是相反的缭贡,源操作數(shù)和目的操作數(shù)的順序相反。
關(guān)于棧中的寄存器:
esp :棧指針辉懒,又叫棧頂寄存器阳惹,總是指向棧頂元素,已經(jīng)壓入棧的最頂上的那個(gè)元素眶俩,而不是待壓入的莹汤。棧指針可以移動(dòng),通過上下移動(dòng)實(shí)現(xiàn)棧的開辟和釋放颠印。棧是向小地址方向生長(zhǎng)的纲岭。esp寄存器中保存的是當(dāng)前棧的棧頂元素的地址抹竹。
ebp :幀指針,又叫椈挠拢基址寄存器柒莉,總是指向當(dāng)前棧的棧底元素。保存的是當(dāng)前棧的棧底元素的地址沽翔。幀指針不可以移動(dòng)兢孝,用來作為當(dāng)前棧的基址,通過對(duì)基址ebp的偏移來尋址訪問棧中的其他元素的值仅偎。比如:0x8(%ebp)ebp所指向的地址值在加上0x8的地址跨蟹。
ebp永遠(yuǎn)都是針對(duì)當(dāng)前棧的,所以當(dāng)一個(gè)函數(shù)調(diào)用了另外一個(gè)函數(shù)的時(shí)候橘沥,就需要先將調(diào)用函數(shù)的棧的ebp給入棧保存窗轩,這樣避免了與被調(diào)用函數(shù)的棧ebp沖突,然后在被調(diào)用函數(shù)的棧結(jié)束調(diào)用釋放的時(shí)候座咆,恢復(fù)現(xiàn)場(chǎng)的時(shí)候痢艺,在將調(diào)用函數(shù)的ebp彈出來,這樣又可以回到調(diào)用函數(shù)的棧了介陶。
棧通過棧指針棧頂esp和幀指針棧底ebp來固定椀淌妫框。
eax :保存函數(shù)的返回值的慣用寄存器
% :直接尋址寄存器
( ) :內(nèi)存間接尋址
$ :立即數(shù)
例如:movl $8, %eax #把立即數(shù)8存到寄存器eax中
movl $8, (%esp) #把立即數(shù)8存到內(nèi)存esp所指的內(nèi)存地址中哺呜。
這里主要來通過函數(shù)調(diào)用來徹底弄清楚棧的過程舌缤,這里直接利用bufbomb中的一段簡(jiǎn)單的c代碼,通過反匯編來分析一下其匯編代碼某残。這個(gè)c代碼很簡(jiǎn)單国撵,就是test函數(shù)中調(diào)用了getbuf函數(shù)。
其反匯編代碼為:
這里的匯編代碼比較長(zhǎng)我們來截取一下只看關(guān)于棧和函數(shù)調(diào)用的部分:
來看一下不管是test還是getbuf都有的幾句匯編語言:
函數(shù)的實(shí)現(xiàn)過程都是通過棧過程玻墅,一開始我們已經(jīng)講過了棧有兩個(gè)指針用來固定椊檠溃框的,ebp棧底指針和而esp棧頂指針澳厢。
push %ebp #保存舊的ebp的值环础,也就是保存當(dāng)前函數(shù)的調(diào)用者的棧的棧基址赏酥。(因?yàn)槊恳粋€(gè)棧都有一個(gè)ebp是不可以移動(dòng)的喳整,但是名字又一樣,怎么區(qū)分呢裸扶,那就是開始時(shí)先把原來的ebp保存起來框都,然后生成自己的,調(diào)用結(jié)束后,在把原來的恢復(fù)魏保,彈出來熬尺,自己的釋放掉,這樣人家原來的ebp又可以繼續(xù)在自己的棧中作為基址了谓罗。不然就覆蓋了回不去了)相對(duì)于test來說可能就是main之類的調(diào)用test的函數(shù)的棧的ebp粱哼,對(duì)于getbuf來說就是test的ebp。將ebp壓入棧中檩咱,這個(gè)時(shí)候esp自動(dòng)-4揭措,指向新壓入的ebp元素的位置處。
mov %esp,%ebp #使幀指針ebp指向當(dāng)前的esp處刻蚯,也就是初始化生成一個(gè)當(dāng)前棧的基址幀指針ebp绊含,也就是當(dāng)前棧的棧底表示出來固定住。對(duì)于test來說就是test的棧底炊汹,對(duì)于getbuf來說就是getbuf的棧的棧底躬充。
sub $0x38,%esp #通過棧指針esp向下移動(dòng)為當(dāng)前函數(shù)開辟自己的棧,esp指向了棧頂讨便,從ebp到esp為當(dāng)前函數(shù)的棾渖酰空間。
以上三條指令就是所有棧都有的棧開辟匯編指令霸褒。
接下來來看棧釋放的匯編指令:
這里是兩個(gè)函數(shù)中不同的棧釋放恢復(fù)指令伴找,有點(diǎn)不一樣但是原理是一樣的。
add $0x24,%esp #釋放當(dāng)前棧開辟的空間傲霸,在test函數(shù)開始的時(shí)候通過棧指針esp減0x24移動(dòng)來開辟了當(dāng)前test函數(shù)自己的椊澹空間∶挤矗現(xiàn)在將esp加上0x24也就是使esp從新移動(dòng)到了棧底ebp處昙啄,就將原來開辟的棧空間釋放掉了寸五。另外一條常用的匯編指令是:
mov %ebp,%esp #將ebp的值給esp梳凛,也就是把esp指向當(dāng)前的棧底
pop %ebx #把之前保存的寄存器ebx恢復(fù)
pop %ebp #彈出舊的ebp,也就是把調(diào)用test函數(shù)的函數(shù)的棧的ebp彈出恢復(fù)梳杏。
ret # 彈出返回地址韧拒。這一條指令相當(dāng)于
pop %eip 將指令寄存器恢復(fù),也就是讓調(diào)用test函數(shù)的函數(shù)的棧知道接下來應(yīng)該繼續(xù)執(zhí)行的哪一條指令十性。
leave # 是將當(dāng)前棧的空間釋放掉叛溢,彈出舊的ebp,相當(dāng)于下面兩條匯編指令:
mov %ebp,%esp
pop %ebp
由此可見棧的釋放就是三個(gè)過程:
釋放當(dāng)前棧的空間
彈出舊的ebp
彈出返回地址
過程與esp的移動(dòng)結(jié)合:(有很多人不明白劲适,我這里已經(jīng)用自己的話寫的非常白話了楷掉,我覺得最后的理解方式是通過gdb調(diào)試一下,那里不明白就調(diào)試出來里面到底是什么就會(huì)自己豁然開朗)
棧釋放主要包括:1將當(dāng)前開放的椣际疲空間釋放掉烹植,通過esp的向上移動(dòng)斑鸦,移動(dòng)到自己的棧底ebp處(保存的舊的調(diào)用者的ebp的位置處),2恢復(fù)原來的現(xiàn)場(chǎng)草雕,主要包括兩部分巷屿,一部分是將原來的棧基址幀指針ebp復(fù)原墩虹,也就是在自己棧中保存的ebp彈出來嘱巾,這個(gè)時(shí)候esp自動(dòng)加4,變到原來保存舊ebp處位置上上面一個(gè)位置(一般為調(diào)用者的棧中保存的返回地址的地方诫钓,返回地址是調(diào)用當(dāng)前函數(shù)時(shí)浓冒,結(jié)束后回來繼續(xù)應(yīng)該執(zhí)行的下一條指令的eip的地址。比如call指令的下一條指令的eip的地址
比如說上面test中call getbuf這條eip的下一條eip的地址為0x8048e50
在執(zhí)行call指令的時(shí)候相當(dāng)于:
push eip(0x8048e50) (系統(tǒng)自動(dòng)將返回地址壓入棧尖坤,輸入調(diào)用者的一部分稳懒,比如這里在test的棧中,而getbuf的棧是從getbuf壓入test的ebp開始慢味,也就是getbuf的棧底元素是test的ebp)
jmp getbuf(0x8049262)
)场梆,這個(gè)時(shí)候保存的舊的ebp已經(jīng)彈出,之前為當(dāng)前棧生成的棿柯罚基址幀指針ebp也就沒有了已經(jīng)或油。也就是被調(diào)用函數(shù)開辟的棧已經(jīng)完全復(fù)原了,像什么沒有發(fā)生一樣驰唬。第二個(gè)部分也是最后一個(gè)部分顶岸,就是調(diào)用者需要知道我應(yīng)該繼續(xù)執(zhí)行那一條指令(不然回來了找不到原來的指令執(zhí)行到哪里了),也就是把返回地址eip的地址彈出來叫编,esp自動(dòng)加4指向原來保存返回地址的位置上面一個(gè)位置辖佣。這樣調(diào)用函數(shù)又繼續(xù)正常執(zhí)行了。
圖解函數(shù)調(diào)用過程:
1首先是test函數(shù)的棧結(jié)構(gòu)搓逾,其中黃色是test函數(shù)的棧卷谈,綠色是調(diào)用test函數(shù)的函數(shù)的棧,比如main函數(shù)之類的霞篡。
2 test執(zhí)行到call指令:
call 8049262 <getbuf>
首先 系統(tǒng)自動(dòng)壓入返回地址 push eip 這里call的下一條eip的地址是0x8048e50
然后 Jmp到j(luò)mp getbuf(0x8049262)
隨著返回地址的入棧世蔗,esp自動(dòng)下移,esp-4:這個(gè)時(shí)候仍是黃色的朗兵,因?yàn)槲覀冊(cè)谇懊嬉呀?jīng)分析過污淋,返回地址是屬于調(diào)用者的棧結(jié)構(gòu)的。
3跳到getbuf的函數(shù)的入口地址以后開始getbuf的棧余掖,藍(lán)色的代表getbuf的棧
push %ebp #保存舊的ebp的值寸爆,也就是保存當(dāng)前函數(shù)的調(diào)用者的棧的棧基址。對(duì)于getbuf來說就是test的ebp而昨。將ebp壓入棧中救氯,這個(gè)時(shí)候esp自動(dòng)-4,指向新壓入的ebp元素的位置處歌憨。
mov %esp,%ebp #使幀指針ebp指向當(dāng)前的esp處着憨,也就是初始化生成一個(gè)當(dāng)前棧getbuf棧的基址幀指針ebp,也就是當(dāng)前棧的棧底表示出來固定住务嫡。對(duì)于getbuf來說就是getbuf的棧的棧底甲抖。
4 getbuf繼續(xù)棧開辟
sub $0x38,%esp 通過esp移動(dòng)開辟一個(gè)getbuf的棧空間心铃,esp此時(shí)指向getb這個(gè)棧的棧頂准谚,此時(shí)getbuf的棧框已經(jīng)固定住去扣。
接下來再來看函數(shù)調(diào)用完以后返回到test函數(shù)柱衔,現(xiàn)場(chǎng)恢復(fù):
1leave的第一步:
mov %ebp,%esp #將ebp的值給esp,也就是把esp指向當(dāng)前的棧底愉棱,把開辟的藍(lán)色空間收回
2 leave的第二步:
pop %ebp #彈出舊的ebp唆铐,也就是把調(diào)用test函數(shù)的函數(shù)的棧的ebp彈出恢復(fù)。此時(shí)藍(lán)色框已經(jīng)完全沒有了奔滑,ebp也沒有了艾岂,為了getbuf開的空間也已經(jīng)完全釋放了。
3 ret 彈出返回地址以后:
這里以上僅是簡(jiǎn)單的函數(shù)調(diào)用朋其,調(diào)用的函數(shù)不需要傳入?yún)?shù)王浴,還有調(diào)用的函數(shù)需要傳入?yún)?shù)的時(shí)候等,在bufbomb中我們會(huì)具體遇到梅猿。再具體分析氓辣。