棧幀與函式呼叫
1.什麼是棧幀
在理解棧幀之前,我們需要理解什麼是棧。
棧在資料結構中是一種運算受限的線性表,即我們只能對錶尾進行操作,稱之為棧頂,相對的,另一端稱為棧底。它按照先進後出的原則儲存資料,先進入的資料儲存在棧底,後來的資料儲存在棧頂。
在計算機系統當中,棧是擁有在資料結構當中所有屬性的一塊動態記憶體區域,用來儲存函式內部的區域性變數和返回地址。
而棧幀,就是在程式執行的過程當中,儲存函式呼叫時所需要維護的資訊。
棧幀
簡言之,棧幀是利用EBP暫存器來訪問函式內部區域性變數、引數、函式的返回地址等。
棧幀結構
PUSH EBP ;函式開始(使用EBP先把已有值儲存在棧中)MOV EBP ESP ;儲存當前ESP值到EBP中 ;函式體 *** ;無論ESP值如何變化,EBP值不改變,可以安全訪 問函式內的區域性變數,引數 MOV ESP EBP ;將函式的起始地址(返回地址)返回到ESP中 POP EBP ;彈出儲存在棧中的EBP值 RETN ;函式終止
利用例項來理解棧幀(逆向工程核心原理:棧幀章)
#include<stdio.h> long add(long a , long b) { long x = a , y = b; return (x+y); } int main() { long a = 1 , b = 2 ; printf(" %d \n" , add( a , b )) ; return 0; }
組合語言如下所示
①我們先從main()函式來進行程式分析
我們需要密切關注棧的變化
當前ESP、EBP指標如圖所示(如下);
棧頂指標儲存19FF2C儲存著main()函式執行完後的返回地址(如下);
這條指令(如下)把EBP(棧幀指標)儲存在棧中(main()函式執行完畢,返回之前,該值會再次恢復)
main()函式的esp的值被儲存在ebp當中(下圖),當做開闢新棧之後的基址(同時作為執行完add()函式的返回地址)。從這條命令開始,ebp與esp持有相同的值,main()函式的棧幀就生成好了。
棧內ebp的指標儲存著main()函式開始執行時的初始值
②設定變數
sub是減法指令,esp-8意思是在棧內開闢一個八位元組的空間(long 型的a,b一個佔四位元組)
把ebp-4與ebp-8處各有一個四位元組的記憶體空間,用來儲存資料1和2。
執行完後棧內空間如下所示
③呼叫add()函式
以上五行彙編描述了呼叫main()函式的整個過程
函式add()接收a、b兩個長整型,所以呼叫前先把兩個引數壓入棧,如下圖所示
返回地址
執行call命令進入函式之前,需要把函式的返回地址壓入棧。在地址40103C中執行了call,它的下一條指令的地址是401041,即執行完add()函式後程序的執行流接著執行地址401041的命令,此處即為返回地址。
執行完call命令後,棧內如圖所示
④進入add()函式
生成add()函式的棧幀
棧內情況如圖所示:
⑤設定add()函式的區域性變數
[ebp+8]在棧內表示a的值,[ebp+c]表示b的值,[ebp-8]與[ebp-4]指向add()函式的兩個引數x,y
⑥add運算
運算完成後值被儲存在eax當中
⑦刪除add()函式的棧幀&函式的返回值
在返回值之前,需要先刪除棧幀。
mov指令用於將儲存在ebp當中的main()函式的esp的值恢復到esp當中
pop指令將add()函式開始執行時儲存的ebp的值彈出
這兩條指令完成後,棧內情況如下
ebp指向main()函式開始執行時的初始值
esp儲存函式執行完畢後的返回地址401041
⑧從棧中刪除add()函式的引數
retn執行後,程式重新執行到main()函式內執行add esp+8
這條命令的作用是刪除引數a,b
⑨printf()與return 0;
略
⑩刪除main()函式的棧幀
執行完後指標與棧內空間如下,main函式棧幀被刪除
retn指令執行後,返回到主函式執行完畢後的狀態