彙編層面分析函式呼叫
前言
組合語言是學習逆向的基礎,本文通過從彙編的層面分析 函式呼叫 來了解 壓棧、跳轉、執行、返回 的具體實現流程以及對 堆疊 的應用。
知識有限,如果有錯誤或則不清楚的地方還請您指出。
您的鼓勵是我寫文章的動力。
1. 函式呼叫的說明
在介紹函式呼叫的具體流程前,我們先來了解一下幾個知識點。
-
1.1 程式的執行流程
程式是順序執行的,cpu是怎麼進行實現的呢?
程式的執行離不開一個關鍵的暫存器。
我們首先來了解一下關於 程式計數器 的概念。
百度百科:
由此可見,程式計數器的功能就是存放下一條要執行指令的地址。
在X86彙編中,執行程式計數功能的暫存器叫做 EIP ,指令指標暫存器。
我們可以寫個簡單的程式,檢視反彙編瞭解一下。
EIP暫存器裡面存放的就是即將要執行指令的地址。
-
1.2 程式的棧結構
在經典的作業系統裡,棧總是 向下增長 的。棧頂由 esp暫存器 定位。壓棧操作使棧頂的地址減小,彈出操作使棧頂地址增大。
函式的呼叫離不開棧,棧中儲存了函式呼叫當中的所有信息。
-
函式的返回地址和引數;
-
函式的非靜態的區域性變數;
-
儲存上下文:儲存一些不需要改變的暫存器。
為了能夠正確的處理函式呼叫的堆疊平衡,我們需要更正兩個暫存器ebp,esp的位置:
1. 把ebp提升到esp的位置。
2. esp的值是動態變化的,隨著申請更多的臨時變數,e's'p會不斷減小。
3. 呼叫函式後棧中的結構圖。
-
1.3 呼叫約定及堆疊平衡
在函式呼叫過程中,引數需要提前壓棧,而在函式呼叫結束後,之前壓棧的引數也需要退棧。
這樣就有一個問題,退棧的工作是交給 呼叫者 完成還是交給 被呼叫函式 完成?
這就需要有個約定。
常見呼叫約定的堆疊平衡方式:
呼叫約定 | 堆疊平衡方式 |
__stdcall | 函式自己平衡 |
__cdecl | 呼叫者負責平衡 |
__thiscall | 呼叫者負責平衡 |
__fastcall | 呼叫者負責平衡 |
__naked | 有編寫者負責 |
2. 函式呼叫的流程
瞭解了基本的概念之後,回到主題: 函式呼叫
-
壓棧:函式引數壓棧 返回地址壓棧
-
跳轉:跳轉到函式所在程式碼處執行
-
執行:執行函式程式碼
-
返回:平衡堆疊 找出返回地址並跳回
-
2.1 call指令
0x210000 call swap; 0x210005 mov ecx,eax;
call指令可以分解成兩個指令:
push 0x210005; jmp swap;
首先push當前指令下一條指令的地址,目的是呼叫完函式後可以跳回來;
然後修改 eip, 跳到函式的地址。
-
2.2 ret指令
ret指令表示取出當前棧頂值作為返回地址,將 eip 修改為該地址。
執行完這一步一個基本的函式呼叫就完成了。
3. 函式調用匯編實驗
測試程式
在main函式中呼叫一個交換兩個數的函式。
#include <stdio.h> void _stdcall swap(int& a,int& b); //交換兩個數 void _stdcall swap(int& a, int& b) { int c = a; a = b; b = c; } void main() { int a = 1; int b = 2; printf("交換前:a =%d,b=%d\n",a,b); swap(a,b); printf("交換後:a= %d,b=%d\n",a,b); getchar(); }
程式的執行結果:
-
3.1 壓棧
-
3.1.1 函式引數壓棧
真正呼叫函式前先將引數進行壓棧。
我們通過觀察暫存器和記憶體來檢視變化過程:
發現引數已經被壓入棧中。
-
3.1.2 返回地址壓入棧中
F11進入swap函式後,可以發現記憶體當中憑空出現一段數,它是什麼?
其實它就是call指令的下一條指令的地址:
-
3.2 跳轉
跳轉到swap函式內部,下面是詳細註解
-
3.3 返回
執行ret前。
執行ret後, 帖前先滑動左側填充拼圖:
由於使用的是 __stdcall 的呼叫約定,在被呼叫函式內部進行堆疊平衡。
4. 結語
這樣,一個函式呼叫的實現就分析完成了。有好多細節的問題沒有進行闡述。這樣一篇文章用來總結匯編的知識點,希望遇到同道中人。
成為逆向大牛的過程是艱辛的,你已經邁出了第一步,加油。
- End -

看雪ID:Jabez
https://bbs.pediy.com/user-825190.htm
本文由看雪論壇 Jabez 原創
轉載請註明來自看雪社群