😊點此到文末驚喜??
概述基礎知識存放函數(shù)體
編譯后的二進制指令,并且是只讀的。存放常量
,并且是只讀的,如字符串、數(shù)字、const修飾的全局變量···存放編譯時即可確定存儲大小的靜態(tài)變量和全局變量
,可讀可寫存儲malloc動態(tài)分配的內存(new底層也是調用malloc)
,內存由低地址向高地址生長,由程序員進行內存的分配和釋放,系統(tǒng)維護開銷大,速度比棧慢存儲函數(shù)調用過程
,由操作系統(tǒng)進行自動的分配和釋放存放常量
,并且是只讀的,如字符串、數(shù)字、const修飾的全局變量···使用局部變量時,盡量進行初始化。
編譯器不會初始化局部變量,所以如果使用未初始化的局部變量,內部的值是垃圾值。但是debug調試模式下,運行時機制會將??臻g全部初始化為0mainCRTStartup()
,而main函數(shù)由啟動碼函數(shù)中的invoke_main()
調用call 子函數(shù)名
,即主函數(shù)調用子函數(shù)push ebp
,將主函數(shù)的棧底指針壓入棧中mov ebp esp
,使棧底指針指向棧頂,即構造子函數(shù)的棧底mov esp ebp
,用子函數(shù)的ebp給esp賦值,將棧頂指針指向棧底pop ebp
將棧頂?shù)纳弦粋€函數(shù)的ebp值彈出到ebp寄存器中ret
將棧頂?shù)闹担ㄖ骱瘮?shù)的調用斷點的下一條指令的地址)彈出到EIP中,EIP會執(zhí)行下一條指令,即返回主函數(shù)。編譯環(huán)境:vs2022&win10
編譯器問題:編譯器越高級,其中處理的越會繁瑣。在不同的編譯器下,函數(shù)的調用過程中的棧幀的創(chuàng)建是略有差異的,具體細節(jié)取決于編譯器的實現(xiàn)
前置知識
32位下反匯編(下面從0開始)
// 被調用的子函數(shù)
int sum(int a, int b) {// 5. 構造子函數(shù)棧幀
00451740 push ebp //把ebp寄存器的值入棧,此時的ebp中存放的是主調函數(shù)的?;?00451741 mov ebp,esp// 將當前棧頂指針esp賦值給棧基址寄存器ebp,即現(xiàn)在為子函數(shù)棧幀
00451743 sub esp,0CCh// 棧頂指針esp下移(棧由高向低生長),即由esp和ebp共同維護這一段子函數(shù)棧幀
00451749 push ebx //將寄存器ebx的值壓棧,esp-4
0045174A push esi //將寄存器esi的值壓棧,esp-4
0045174B push edi //將寄存器edi的值壓棧,esp-4
0045174C lea edi,[ebp-0Ch] //先把ebp-0E4h的地址,放在edi中
// 下三行將ebp指向的內存到值為ebx之間初始化成0CCCCCCCCh
0045174F mov ecx,3
00451754 mov eax,0CCCCCCCCh
00451759 rep stos dword ptr es:[edi]
// 下兩行為編譯器debug模式下調試用的cookie
0045175B mov ecx,offset _01833B24_TestCpp@cpp (045C008h)
00451760 call @__CheckForDebuggerJustMyCode@4 (045130Ch)
// 6. 執(zhí)行子函數(shù)功能
int c = a + b;
00451765 mov eax,dword ptr [a]
00451768 add eax,dword ptr [b]
0045176B mov dword ptr [c],eax
return c;
// 返回值放入eax中,函數(shù)調用返回時不會被覆蓋
0045176E mov eax,dword ptr [c]
}
00451771 pop edi //在棧頂彈出一個值,存放到edi中,esp+4
00451772 pop esi //在棧頂彈出一個值,存放到esi中,esp+4
00451773 pop ebx //在棧頂彈出一個值,存放到ebx中,esp+4
// 下三行為debug模式下的cookie
00451774 add esp,0CCh
0045177A cmp ebp,esp
0045177C call __RTC_CheckEsp (0451235h)
// 7. 將棧幀恢復為主函數(shù)的棧幀(ebp和esp)
00451781 mov esp,ebp // 將子函數(shù)的棧底指針賦值給棧頂指針esp,相當于回收棧,但是子函數(shù)棧幀仍然存在棧中
00451783 pop ebp //彈出棧頂?shù)闹荡娣诺絜bp,棧頂此時的值恰好就是main函數(shù)的ebp,esp+4,此時恢復了main函數(shù)的棧幀維護,esp指向main函數(shù)棧幀的棧頂,ebp指向了main函數(shù)棧幀的棧底。
// 8. 返回到主函數(shù)的調用點的下一條指令的地址
00451784 ret //ret指令的執(zhí)行,首先是從棧頂彈出一個值,此時棧頂?shù)闹稻褪莄all指令下一條指令的地址,此時esp+4,然后直接跳轉到call指令下一條指令的地址處,繼續(xù)往下執(zhí)行
···
// 主函數(shù)
int main() {// 0. 構造主函數(shù)棧幀(配合堆棧結構圖片看更容易理解)
004517B0 push ebp //把ebp寄存器的值入棧,此時的ebp中存放的是invoke_main?;?004517B1 mov ebp,esp // 將當前棧頂指針esp賦值給?;芳拇嫫鱡bp,即現(xiàn)在為mian函數(shù)棧幀
004517B3 sub esp,0D8h// 棧頂指針esp下移(棧由高向低生長),即由esp和ebp共同維護這一段mian棧幀
004517B9 push ebx //將寄存器ebx的值壓棧,esp-4,這三個應該是main的三個形參變量
004517BA push esi //將寄存器esi的值壓棧,esp-4
004517BB push edi //將寄存器edi的值壓棧,esp-4
004517BC lea edi,[ebp-18h] //先把ebp-18h的地址,放在edi中
// 下三行將ebp指向的內存到值為ebx之間初始化成0CCCCCCCCh
004517BF mov ecx,6
004517C4 mov eax,0CCCCCCCCh
004517C9 rep stos dword ptr es:[edi]
// 下兩行為編譯器debug模式下調試用的cookie
004517CB mov ecx,offset _01833B24_TestCpp@cpp (045C008h)
004517D0 call @__CheckForDebuggerJustMyCode@4 (045130Ch)
// 函數(shù)功能實現(xiàn)
// 1. 為main函數(shù)棧幀中的局部變量賦值(系統(tǒng)啥時候把a和b初始化成main堆棧的?)
int a = 1;
004517D5 mov dword ptr [a],1 // a相當于一個指針,將1賦值到a指向的內存中,實際在mian函數(shù)的棧幀中
int b = 2;
004517DC mov dword ptr [b],2 // 同上
sum(a, b);
// 2. 將被調用函數(shù)的參數(shù)從右向左依次用通用寄存器保存
004517E3 mov eax,dword ptr [b]
004517E6 push eax
004517E7 mov ecx,dword ptr [a]
004517EA push ecx
// 3. call子函數(shù),分成兩步,1.將程序下一條指令地址壓入棧中2.轉移到調用的子函數(shù)(最后一行)
004517EB call sum (045116Dh)
004517F0 add esp,8
return 0;
004517F3 xor eax,eax
}
004517F5 pop edi
004517F6 pop esi
004517F7 pop ebx
004517F8 add esp,0D8h
004517FE cmp ebp,esp
00451800 call __RTC_CheckEsp (0451235h)
00451805 mov esp,ebp
00451807 pop ebp
00451808 ret
// 以下是函數(shù)表,只是一個中轉作用
···
// 4. 執(zhí)行跳轉指令到子函數(shù)的執(zhí)行(第一行)
0045116D jmp sum (0451740h)
···
5. 64位下反匯編,代碼沒問題,但是我的注釋可能有問題,有的地方?jīng)]理解,等我神功大成
#include// 主函數(shù)調用的子函數(shù)
int sum(int a, int b) {// 4. 將子函數(shù)的參數(shù)自右向左依次壓入棧中
00007FF7F5631740 mov dword ptr [rsp+10h],edx // 棧由高地址向低地址生長
00007FF7F5631744 mov dword ptr [rsp+8],ecx // 這是低地址
// 5. 壓入主調函數(shù)的?;罚瓷弦粋€棧幀的開始地址
00007FF7F5631748 push rbp
// 6.將主調函數(shù)的函數(shù)調用點的下一條指針地址壓入棧中
00007FF7F5631749 push rdi
00007FF7F563174A sub rsp,108h
00007FF7F5631751 lea rbp,[rsp+20h]
00007FF7F5631756 lea rcx,[__01833B24_TestCpp@cpp (07FF7F5641008h)]
00007FF7F563175D call __CheckForDebuggerJustMyCode (07FF7F5631343h)
// 7. 執(zhí)行函數(shù)體內的功能語句
int c = a + b;
00007FF7F5631762 mov eax,dword ptr [b]
00007FF7F5631768 mov ecx,dword ptr [a]
00007FF7F563176E add ecx,eax
00007FF7F5631770 mov eax,ecx
00007FF7F5631772 mov dword ptr [c],eax
return c;
00007FF7F5631775 mov eax,dword ptr [c] // 將返回值賦值到eax中
}
00007FF7F5631778 lea rsp,[rbp+0E8h]
// 8. 注意此時棧頂依次為rdi rbp。所以逆序pop到相應的寄存器中
00007FF7F563177F pop rdi
00007FF7F5631780 pop rbp
// 9. ret是子函數(shù)返回指令,與call搭配使用,修改pc(存儲下一條將要執(zhí)行的指令地址),并恢復主函數(shù)堆棧
00007FF7F5631781 ret
// main函數(shù)
int main() {// 0. debug模式下的插入的cookie?
00007FF7F56317A0 push rbp
00007FF7F56317A2 push rdi
00007FF7F56317A3 sub rsp,128h
00007FF7F56317AA lea rbp,[rsp+20h]
00007FF7F56317AF lea rcx,[__01833B24_TestCpp@cpp (07FF7F5641008h)]
00007FF7F56317B6 call __CheckForDebuggerJustMyCode (07FF7F5631343h)
// 1. 分別開辟4個字節(jié)大小的雙字內存并將值移入
int a = 1;
00007FF7F56317BB mov dword ptr [a],1
int b = 2;
00007FF7F56317C2 mov dword ptr [b],2
// 2. 將參數(shù)壓入寄存器中,調用子函數(shù)
sum(a, b);
00007FF7F56317C9 mov edx,dword ptr [b] // 將內存地址為a的雙字類型的數(shù)據(jù)賦值給edx
00007FF7F56317CC mov ecx,dword ptr [a]
00007FF7F56317CF call sum (07FF7F56313A2h) // 調用子函數(shù)(最后一行)
return 0;
00007FF7F56317D4 xor eax,eax
}
00007FF7F56317D6 lea rsp,[rbp+108h]
00007FF7F56317DD pop rdi
00007FF7F56317DE pop rbp
00007FF7F56317DF ret
// 從函數(shù)表中截取的子函數(shù)跳轉指令
// 3. 跳轉到子函數(shù)
00007FF7F56313A2 jmp sum (07FF7F5631740h) // 第一行
···
棧溢出實驗🚩點此跳轉到首行??
參考博客你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧