原作者Eli Bendersky's website (https://eli.thegreenplace.net/)
背景
對軟件進(jìn)行逆向分析時,通常對函數(shù)堆??臻g的分析是第一步,也是重要的步驟,文章主要對x-86架構(gòu)的棧空間問題進(jìn)行了基礎(chǔ)性的描述,作為初學(xué)逆向很值得學(xué)習(xí):
The stack analogy ??臻g類比
回到基本概念,??臻g通常被比喻一推疊放起來的碟子,無論放新的盤子還是取走一個盤子,都是從這推疊放的頂端操作,因此堆棧的操作部分也成為棧頂top of stack,棧的操作也類似分為入棧push和出棧pop。

Hardware stacks 實(shí)際硬件中的棧
一般來講,棧是來自于memory的一段空間,入棧就是在把新的數(shù)據(jù)放到棧頂,而出棧就是把棧頂上的數(shù)據(jù)取走,然而這么說并沒有明確究竟棧頂和memory的關(guān)系和位置。
The stack in x86
這里將解釋上面困惑的原因,在于x86架構(gòu)下棧的方向是“頭朝下”,即從某一地址開始向低地址方向生長:

因此,我們說x86架構(gòu)的棧頂值得是從memory中申請到的這段堆棧的地址的最低部分。當(dāng)我們討論它時,讓我們看看x86匯編編程的一些常見習(xí)語如何映射到該圖形表示形式。
Pushing and popping data with the stack pointer 通過SP指針壓棧和出棧
x86架構(gòu)保留以個特殊的寄存器用于棧的操作--ESP(Extend Stack Point)。SP指針的作用即一直指向棧頂:

在上圖中,0x9080ABCC即是棧頂?shù)刂?,esp指向的數(shù)據(jù)"foo"就位于棧頂位置。
使用“push”想棧頂壓入數(shù)據(jù),push操作首先對esp的地址值減4,并且將操作數(shù)存放到esp指向的位置,因此:
push eax 等價于
sub esp, 4
mov [esp], eax
按照之前棧結(jié)構(gòu)圖示中的esp位置,假設(shè)eax保存新的壓棧數(shù)據(jù)0xDEADBEEF,push操作之后??臻g變?yōu)椋?br>

類似的,執(zhí)行pop指令是從頂取走數(shù)據(jù)并替換其操作數(shù),先保存棧頂位置之后地址值增加,換句話:
pop eax等價于
mov eax, [esp]
add esp, 4
接上面的??臻g結(jié)構(gòu)(push操作后),pop eax后的結(jié)果為:

這樣,數(shù)值0xDEADBEEF就被寫入了eax,同時發(fā)現(xiàn)0xDEADBEEF 的地址為0x9080ABC8。
Stack frames and calling conventions 棧結(jié)構(gòu)和條用規(guī)則
分析以下C編寫的匯編程序,可以發(fā)現(xiàn)很多有趣的模式。我們關(guān)心的主要是函數(shù)的參數(shù)是如何通過過棧傳入的(當(dāng)然在其他的一些架構(gòu)或者調(diào)用方式中,參數(shù)是放在寄存器里),以及本地參數(shù)在??臻g的分布規(guī)則。
分析以下C程序代碼
int foobar(int a, int b, int c)
{
int xx = a + 2;
int yy = b + 3;
int zz = c + 4;
int sum = xx + yy + zz;
return xx * yy * zz + sum;
}
int main()
{
return foobar(77, 88, 99);
}
當(dāng)foobar函數(shù)被調(diào)用時,包括傳入foobar的參數(shù)、函數(shù)的本地變量以及其他數(shù)據(jù)都將被保存到棧空間中。這一系列的數(shù)據(jù)都被稱為函數(shù)的棧幀,那么在return函數(shù)執(zhí)行前,foobar函數(shù)的棧幀結(jié)構(gòu)為:

當(dāng)調(diào)用函數(shù)時,首先將綠色的數(shù)據(jù)分配一段空間存儲參數(shù),藍(lán)色的數(shù)據(jù)則是函數(shù)本身的壓棧操作用于保存本地變量,使用gcc進(jìn)行編譯:
gcc -masm=intel -S z.c -o z.s
得到函數(shù)foobar的匯編代碼,我們對其進(jìn)行了注釋:
_foobar:
; ebp must be preserved across calls. Since
; this function modifies it, it must be
; saved.
;
push ebp
; From now on, ebp points to the current stack
; frame of the function
;
mov ebp, esp
; Make space on the stack for local variables
;
sub esp, 16
; eax <-- a. eax += 2. then store eax in xx
;
mov eax, DWORD PTR [ebp+8]
add eax, 2
mov DWORD PTR [ebp-4], eax
; eax <-- b. eax += 3. then store eax in yy
;
mov eax, DWORD PTR [ebp+12]
add eax, 3
mov DWORD PTR [ebp-8], eax
; eax <-- c. eax += 4. then store eax in zz
;
mov eax, DWORD PTR [ebp+16]
add eax, 4
mov DWORD PTR [ebp-12], eax
; add xx + yy + zz and store it in sum
;
mov eax, DWORD PTR [ebp-8]
mov edx, DWORD PTR [ebp-4]
lea eax, [edx+eax]
add eax, DWORD PTR [ebp-12]
mov DWORD PTR [ebp-16], eax
; Compute final result into eax, which
; stays there until return
;
mov eax, DWORD PTR [ebp-4]
imul eax, DWORD PTR [ebp-8]
imul eax, DWORD PTR [ebp-12]
add eax, DWORD PTR [ebp-16]
; The leave instruction here is equivalent to:
;
; mov esp, ebp
; pop ebp
;
; Which cleans the allocated locals and restores
; ebp.
;
leave
ret
可以看到首先需要在內(nèi)存上開辟一段??臻g,用于函數(shù)中各個參數(shù)(此函數(shù)為return, a, b, c),ebp永遠(yuǎn)指向返回地址,參戰(zhàn)按照ebp不斷增加;之后函數(shù)將本地變量(xx,yy,zz,sum)壓棧。函數(shù)參數(shù)在堆棧中高于ebp(因此訪問它們時為正偏移量),而局部變量在堆棧中低于ebp。