要反彙編程序,不可避免要接觸到堆棧,你首先得會查看堆棧,知道堆棧在某一時刻的確切內容。首先,我們講述一些與堆棧相關的基礎知識。
1、堆棧基礎
彙編語言中的“堆棧”的含義與數據結構中堆棧的含義不同,儘管從操作上來說,它們都是“後進先出”,這個不用贅述。彙編中有一個寄存器esp指向當前棧頂,而棧底的位置是不變的,整個程序運行過程中,通過操作esp來操作堆棧,進行堆棧的壓入、彈出及平衡操作。
一些堆棧操作術語:
- 壓入:將一個變量壓入到堆棧;
- 彈出:將一個變量從堆棧中彈出;
- 平衡堆棧:當函數調用完成後,進行局部變量的釋放、返回值的正確處理等;
2、堆棧類型
根據架構的不同,有兩種基本的堆棧類型:向上生長和向下生長的堆棧。
- 向上生長堆棧:堆棧向高地址增長,當向棧中壓入元素時,esp增加。棧頂地址 >= 棧底地址。
- 向下生長堆棧:堆棧向低地址增長,當向棧中壓入元素時,esp減小。棧頂地址 <= 棧底地址。
- 將函數參數入棧;
- 將返回處的代碼地址入棧(段內調用一般將eip入棧,段間調用將 cs、eip 依次入棧);
- jmp到被調函數代碼地址處開始執行;
- 被調函數分配局部變量內存;
- 執行被調函數代碼,存好返回值;
- 平衡堆棧。
int f1()
{
cout<<"In f1()"<<endl;
return 1;
}
int f2()
{
cout<<"In f2()"<<endl;
return 2;
}
int foo(int a, int b)
{
// ...
}
foo(f1(), f2());
像這段代碼,我們不知道先執行f1還是先執行f2,因爲不同的編譯器編譯出的結果可能不同。用Microsoft
Visual C++ 編譯出的代碼,先執行f2,後執行f1,也就是說,MSVC編出的代碼,參數從右往左依次入棧。- PASCAL:參數從左往右入棧,被調用者平衡堆棧;
- __cdecl:即 C 調用約定,參數從右往左入棧,調用者平衡堆棧;
- __stdcall:即標準調用約定,參數從右往左入棧,被調者平衡堆棧;