當進程被加載到內存時,會被分成很多段
l 代碼段:保存程序文本,指令指針EIP就是指向代碼段,可讀可執行不可寫,如果發生寫操作則會提示segmentation fault
l 數據段:保存初始化的全局變量和靜態變量,可讀可寫不可執行
l BSS:未初始化的全局變量和靜態變量
l 堆(Heap):動態分配內存,向地址增大的方向增長,可讀可寫可執行
l 棧(Stack):存放局部變量,函數參數,當前狀態,函數調用信息等,向地址減小的方向增長,可讀可寫可執行
l 環境/參數段(environment/argumentssection):用來存儲系統環境變量的一份複製文件,進程在運行時可能需要。例如,運行中的進程,可以通過環境變量來訪問路徑、shell 名稱、主機名等信息。該節是可寫的,因此在緩衝區溢出(buffer overflow)攻擊中都可以使用該段
寄存器
EAX:累加(Accumulator)寄存器,常用於函數返回值
EBX:基址(Base)寄存器,以它為基址訪問內存
ECX:計數器(Counter)寄存器,常用作字符串和循環操作中的計數器
EDX:數據(Data)寄存器,常用於乘除法和I/O指針
ESI:源變址寄存器
DSI:目的變址寄存器
ESP:堆棧(Stack)指針寄存器,指向堆棧頂部
EBP:基址指針寄存器,指向當前堆棧底部
EIP:指令寄存器,指向下一條指令的地址
入棧push和出棧pop
push ebp就等於將ebp的值保存到棧中,並且將當前esp下移
pop ebp就等於將ebp的值從棧中取出來,將ebp指向這個值
下面用一個例子來講函數調用過程中棧的變化
int sum(int _a,int _b)
{
int c=0;
c=_a+_b;
return c;
}
int main()
{
int a=10;
int b=20;
int ret=0;
ret=sum(a,b);
return 0;
}
main函數的棧在調用之前如圖:
Ok現在講一講ret=sum(a,b);的執行過程
Step 1:
函數參數從右至左入棧
Step 2:
ret=sum(a,b);
call @ILT+0(sum) (00401005) call指令實際上分兩步
push EIP 將下一條指令入棧保存起來
esp-4 esp指針下移
Step 3:
push ebp 將main函數基指針入棧保存
mov ebp esp 將esp的值存入ebp也就等於將ebp指向esp
sub esp 44H將esp下移動一段空間創建sum函數的棧棧幀
Step 4:
push ebx
push esi
push edi
lea edi,[ebp-44h] 從ebp-44h的地方開始拷貝
mov ecx,11h 拷貝11次
mov eax,0CCCCCCCCh 拷貝內容為0CCCCCCCCh
rep stos dword ptr [edi] 每次拷貝雙字
Step 5:
int c = 0;
mov dword ptr [ebp-4],0 將sum的局部變量c放入[ebp-4]的空間內
Step 6:
執行函數操作
Step 7:
return c = 0;
mov eax,dword ptr [ebp-4] 將返回值(變量c所在的地址的內容)放入eax寄存器保存住
Step 8:
pop edi //將之前入棧的值重新返回給edi寄存器
0040104C pop exi ////將之前入棧的值重新返回給exi寄存器
0040104D pop ebx ////將之前入棧的值重新返回給ebx寄存器
Step 9:
mov esp ebp //將ebp的值賦給esp,也就等於將esp指向ebp,銷燬sum函數棧幀
Step 10:
pop ebp //ebp出棧,將棧中保存的main函數的基址賦值給ebp
Step 11:
ret //ret相當於pop eip 就是把之前保存的函數返回地址(也就是main函數中下一條該執行的指令的地址)出棧
Step 12:
add esp,8 //此時若傳入sum函數的參數已經不需要了,我們將esp指針上移
此時函數整個調用過程就結束了,main函數棧恢復到了調用之前的狀態
閱讀更多 芹澤多魔雄 的文章