堆?;A(一)

新手入門pwn之棧溢出系列,先學習堆棧的基礎,函數(shù)調(diào)用棧這些.

運行時棧

運行時棧(runtime stack)是有cpu內(nèi)部硬件直接支持的, 也是實現(xiàn)過程調(diào)用和過程返回機制的基本組成部分?!≡诖蠖鄶?shù)時我們稱運行時棧為: 堆棧

這里的堆棧和數(shù)據(jù)結(jié)構里的棧抽象數(shù)據(jù)類型是不同的,堆棧即運行時棧在系統(tǒng)層上(由硬件直接實現(xiàn)) 處理子過程調(diào)用; 堆棧抽象數(shù)據(jù)類型通常用于實現(xiàn)依賴后進先出操作的算法,一般使用高級語言如c++/java等編寫。

棧方向

https://www.zhihu.com/question/36103513
https://www.cnblogs.com/xkfz007/archive/2012/06/22/2558935.html

棧方向跟體系結(jié)構有關系,x86是向下增長,x86硬件直接支持的棧確實是“向下增長”的,由高地址向低地址增長:push指令導致sp自減一個slot,pop指令導致sp自增一個slot。其它硬件有其它硬件的情況。arm沒有固定,但一般操作系統(tǒng)會選擇向下增長

image.png

在內(nèi)存管理中,與棧對應是堆。對于堆來講,生長方向是向上的,也就是向著內(nèi)存地址增加的方向;對于棧來講,它的生長方式是向下的,是向著內(nèi)存地址減小的方向增長。在內(nèi)存中,“堆”和“棧”共用全部的自由空間,只不過各自的起始地址和增長方向不同,它們之間并沒有一個固定的界限,如果在運行時,“堆”和 “?!痹鲩L到發(fā)生了相互覆蓋時,稱為“棧堆沖突”,系統(tǒng)肯定垮臺。

三個寄存器

函數(shù)狀態(tài)主要涉及三個寄存器

rsp/esp/sp:棧指針寄存器,其內(nèi)存放著一個指針,該指針永遠指向系統(tǒng)棧最上面一個棧幀的棧頂

rbp/ebp/bp:基址指針寄存器,其內(nèi)存放著一個指針,該指針永遠指向系統(tǒng)棧最上面一個棧幀的底部函數(shù)棧幀

rip/eip/ip:指令寄存器, 其內(nèi)存放著一個指針,該指針永遠指向下一條待執(zhí)行的指令地址。
image.png

push / pop操作

運行時棧是有cpu直接管理的內(nèi)存數(shù)組, 它使用連個寄存器,ss和esp(32是esp,16位是sp,64位是rsp), ss寄存器存放的段地址,esp是堆棧指針寄存器,指向最后壓入到堆棧上的數(shù)據(jù)。我們很少直接操縱esp的值,而是由call,ret,push和pop指令間接修改的。

堆棧段的操作步驟

壓棧(入棧)push sth-> [esp]=sth,esp=esp-4
彈棧(出棧)pop sth-> sth=[esp],esp=esp+4

http://bestwing.me/2017/03/18/stack-overflow-one/

函數(shù)調(diào)用方式和棧幀

參考:https://www.zhihu.com/question/22444939

如下代碼:

#include<stdio.h>

void func_A(int arg_A1, int arg_A2);
void func_B(int arg_B1, int arg_B2,int arg_B3);
int main(int argc, char *argv[], char **envp){
    printf("main start");
    int arg_A1 = 1;
    int arg_A2 = 2;
    func_A(arg_A1, arg_A2);
}
void func_A(int arg_A1, int arg_A2){
    int arg_A3 = 3;
    func_B(arg_A1,arg_A2,arg_A3);
}
void func_B(int arg_B1, int arg_B2,int arg_B3){
    printf("%d,%d,%d\n",arg_B1,arg_B2,arg_B3);
}


函數(shù)調(diào)用大致包括以下幾個步驟:

參數(shù)入棧:將參數(shù)從右向左依次壓入系統(tǒng)棧中
返回地址入棧:將當前代碼區(qū)調(diào)用指令的下一條指令地址壓入棧中,供函數(shù)返回時繼續(xù)執(zhí)行
代碼區(qū)跳轉(zhuǎn):處理器從當前代碼區(qū)跳轉(zhuǎn)到被調(diào)用函數(shù)的入口處
棧幀調(diào)整:
    具體包括保存當前棧幀狀態(tài)值,已備后面恢復本棧幀時使用(EBP入棧)
    將當前棧幀切換到新棧幀。(將ESP值裝入EBP,更新棧幀底部)
    局部變量入棧,esp減小

恢復的過程就是現(xiàn)彈出棧幀(pop ebp),這樣就可以恢復出調(diào)用函數(shù)的棧幀了,此時棧頂會指向返回地址,再將返回地址彈出(pop eip),并保存到eip中,之后就會回到原來調(diào)用函數(shù)的下一條地址繼續(xù)運行了。

棧幀的調(diào)整過程:

在main函數(shù)調(diào)用func_A的時候,首先在自己的棧幀中壓入函數(shù)返回地址,

然后為func_A創(chuàng)建新棧幀并壓入系統(tǒng)棧,在func_A調(diào)用func_B的時候,同樣先在自己的棧幀中壓入函數(shù)返回地址,

然后為func_B創(chuàng)建新棧幀并壓入系統(tǒng)棧, 在func_B返回時,func_B的棧幀被彈出系統(tǒng)棧,func_A棧幀中的返回地址被“露”在棧頂,此時處理器按照這個返回地址重新跳到func_A代碼區(qū)中執(zhí)行在func_A返回時,func_A的棧幀被彈出系統(tǒng)棧,main函數(shù)棧幀中的返回地址被“露”在棧頂,此時處理器按照這個返回地址跳到main函數(shù)代碼區(qū)中執(zhí)行在實際運行中,

main函數(shù)并不是第一個被調(diào)用的函數(shù),程序被裝入內(nèi)存前還有一些其他操作,上圖只是棧在函數(shù)調(diào)用過程中所起作用的示意圖

函數(shù)調(diào)用圖如下:

image.png

棧幀調(diào)整圖如下:

image.png

函數(shù)調(diào)用時棧內(nèi)的數(shù)據(jù)從高地址到低地址分別是函數(shù)參數(shù)入棧(從右到左),返回地址入棧,ebp入棧,esp分配填充地址, 局部變量mov入棧.

函數(shù)調(diào)用的匯編代碼為:

main函數(shù)

push ebp    //保留舊棧幀
mov ebp,esp //分配新的棧幀               
sub esp,4C  //設置填充空間大小為4C                   
push ebx //寄存器入棧
push esi                       
push edi                         
lea edi,dword ptr ss:[ebp-4C] 將棧頂指針地址給edi   
mov ecx,13  //計數(shù)器設置為13                     
mov eax,CCCCCCCC //填充數(shù)據(jù)            
rep stosd dword ptr es:[edi],eax //重復填充13次0xccccccc到[ebp-4C]~[ebp]到這個空間
mov dword ptr ss:[ebp-4],pwn3.42201C  //pwn3.c:6, [ebp-4]:"test programmer", 42201C:"test programmer" //局部變量入棧(并不是push的)
mov dword ptr ss:[ebp-8],1   //pwn3.c:7 局部變量入棧
mov dword ptr ss:[ebp-C],2    //pwn3.c:8 局部變量入棧
mov eax,dword ptr ss:[ebp-C]  //pwn3.c:9
push eax   //右邊第一個參數(shù)2入棧                      
mov ecx,dword ptr ss:[ebp-8]     
push ecx  //右邊第二個參數(shù)1入棧                       
call <pwn3.ILT+5(_func_A)>  //函數(shù)調(diào)用   

參數(shù)入棧的方式:取決于調(diào)用約定,一般情況下:
X86 從右向左入棧,X64 優(yōu)先寄存器,參數(shù)過多時才入棧

image.png

main函數(shù)的棧幀:

ebp:0019ff40

我們看到函數(shù)調(diào)用后首先是函數(shù)參數(shù)入棧,函數(shù)調(diào)用后會發(fā)生什么呢? 我們來看一看

push ebp 保存main函數(shù)的棧幀ebp
mov ebp,esp  //設置funcA的棧幀ebp                    
sub esp,44  //給fucA棧幀分配填充字節(jié)空間44                     
push ebx //寄存器入棧                        
push esi                         
push edi                         
lea edi,dword ptr ss:[ebp-44]    
mov ecx,11                       
mov eax,CCCCCCCC                 
rep stosd dword ptr es:[edi],eax //填充同上
mov dword ptr ss:[ebp-4],3 //局部變量mov入棧
mov eax,dword ptr ss:[ebp-4] 
push eax //函數(shù)參數(shù)push入棧                        
mov ecx,dword ptr ss:[ebp+C]     
push ecx                         
mov edx,dword ptr ss:[ebp+8]     
push edx                          edx:&"ALLUSERSPROFILE=C:\\ProgramData"
call <pwn3.ILT+10(_func_B)>  //調(diào)用fucB    
add esp,C                        
pop edi                           pwn3.c:14
pop esi                          
pop ebx                          
add esp,44                       
cmp ebp,esp                      
call <pwn3._chkesp>              
mov esp,ebp                      
pop ebp                          
ret                              
image.png

func_A的棧幀: ebp=0019FED8

func_B的匯編代碼:

push ebp  
mov ebp,esp   
sub esp,40 
push ebx  
push esi  
push edi     
lea edi,dword ptr ss:[ebp-40] 
mov ecx,10 
mov eax,CCCCCCCC   
rep stosd dword ptr es:[edi],eax  
mov eax,dword ptr ss:[ebp+10]
push eax   //函數(shù)參數(shù)入棧  
mov ecx,dword ptr ss:[ebp+C] 
push ecx    
mov edx,dword ptr ss:[ebp+8]
push edx    
push pwn3.422030  
422030:"%d,%d,%d\n"
call <pwn3.printf> 
add esp,10   
pop edi  
pop esi  
pop ebx
add esp,40 
cmp ebp,esp  
call <pwn3._chkesp>  
mov esp,ebp  
ret 

將這段代碼的所有匯編一步一步跟蹤了解清楚了后,對堆棧算是大概了解了,下面就是入門棧溢出了,之后學到棧溢出再來更新.

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容