函式的呼叫過程(棧幀結構)—C語言版
主要從三個方面來詳解棧幀
1. 在c語言中關於函式呼叫過程,用棧幀來分析
2. 可以通過棧幀來修改函式返回時的地址
3. 讓不能正常返回的函式可以返回
----------
測試環境 VS2008 win10
----------
1.函式的呼叫過程 棧幀分析(棧幀在C語言中是指每一個函式呼叫時棧區會自動地為其開闢一塊空間即為棧幀。棧幀中儲存了該函式的返回地址和區域性變數)
先來看一段程式碼:
<textarea readonly="readonly" name="code" class="C++"> #include <stdio.h> int Add(int x,int y) { int z = 0; z = x+y; return z; } int main() { int a = 0xaaaaaaaa; int b = 0xbbbbbbbb; int ret = Add(a,b); printf("ret = %d\n", ret); return 0; } </textarea>
`然後一直按F10除錯到 int c =Add(a,b)這一步,同時可以在窗口裡開啟反彙編 函式中的變數都是存在於棧區中(當然了用stactic定義的變數不存於棧區),並且這些變數會一直存在於程式執行期間
----------
ebp為棧底指標,esp為棧頂指標,EIP用於存放當前指令的下一條指令的地址
----------
棧區是向下生長的(從高地址到低地址),先將ESP向下生長然後對新的變數進行壓棧。下面來看a和b的反彙編程式碼<textarea readonly="readonly" name="code" class="彙編"> int a = 0xaaaaaaaa; 00E9141E mov dword ptr [a],0AAAAAAAAh int b = 0xbbbbbbbb; 00E91425 mov dword ptr [b],0BBBBBBBBh int ret = Add(a,b); 00E9142C mov eax,dword ptr [b] 00E9142F push eax 00E91430 mov ecx,dword ptr [a] 00E91433 push ecx 00E91434 call @ILT+215(_Add) (0E910DCh) 00E91439 add esp,8 00E9143C mov dword ptr [ret],eax </textarea>
可以看出即將呼叫Add函式時是將b先壓棧,a後壓棧,隨後呼叫call指令(call指令有兩個作用 1> 將call指令的下一條指令的地址存到棧中 2> 用目標函式的地址覆蓋EIP的值)
<img src="https://img-blog.csdn.net/2017094030746?watmark/2/text/aHR0cDovL2cuY3Nkbi5uZXQvV5bm1hbjIzMw
==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"width="40%">
然後接著用F11進入Add函式內部
<textarea readonly="readonly" name="code" class="彙編"> int Add(int x,int y) { 00E913B0 push ebp 00E913B1 mov ebp,esp 00E913B3 sub esp,0CCh 00E913B9 push ebx 00E913BA push esi 00E913BB push edi 00E913BC lea edi,[ebp-0CCh] 00E913C2 mov ecx,33h 00E913C7 mov eax,0CCCCCCCCh 00E913CC rep stos dword ptr es:[edi] int z = 0; 00E913CE mov dword ptr [z],0 z = x+y; 00E913D5 mov eax,dword ptr [x] 00E913D8 add eax,dword ptr [y] 00E913DB mov dword ptr [z],eax </textarea>
這段彙編是將EBP先壓棧,此時的EBP指向的是main函式的棧底,然後使ESP的內容移動到EBP中,此時相當於EBP指向了另一個棧幀的棧底,而使得ESP減去一個數,,這樣一來開闢了一塊新的棧幀,而這個新的棧幀即為Add函式的棧幀返回值z,將其先儲存到EAX暫存器裡,然後移動EBP,ESP,使得回到main函式的棧幀中。而呼叫ret時和call相反
2.可以通過棧幀來修改函式返回時的地址
<textarea readonly="readonly" name="code" class="C"> #include <stdio.h> #include <windows.h> void bug() { printf("I am a bug"); Sleep(3000); } int Add(int x, int y) { int *p = &x; p--; *p = bug; printf("Add is a 函式\n"); } int main() { int a = 0; int b = 0; int ret = Add(a,b); printf("ret: %d\n", ret); return 0; } </textarea>
將call指令的下一條指令的地址用bug函式的地址覆蓋掉,達到了跳轉到bug函式執行
執行結果:
Add is a 函式
I am a bug請按任意鍵繼續
3. 讓不能正常返回的函式可以返回
在上面的 bug函式中,當bug函式呼叫完畢後不知道返回時的地址,所以我們可以在進入bug函式之前儲存一下call指令的下一條指令的地址
#include <stdio.h>
#include <windows.h>
void *ret = NULL;
void bug()
{
int c = 0;
int *p = &c;
p+=2;
*p = (int)ret;
printf("I am a bug");
Sleep(3000);
}
int Add(int x, int y)
{
int *p = &x;
p--;
ret = *p;
*p = bug;
printf("Add is a 函式\n");
return 0;
}
int main()
{
int a = 0xaaaaaaaa;
int b = 0xbbbbbbbb;
int ret = Add(a,b);
printf("main is running\n");
return 0;
}