新手入門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)會選擇向下增長

在內(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í)行的指令地址。

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)用圖如下:

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

函數(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ù)過多時才入棧

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

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