棧幀空間的創(chuàng)建與銷毀(附手寫筆記))
目錄引言前言須知一寄存器二匯編指令1.基礎(chǔ)指令數(shù)據(jù)的搬運(yùn)2.控制指令程序的指揮3.棧操作與算數(shù)指令三棧幀的創(chuàng)建四進(jìn)入函數(shù)五棧幀的銷毀六銷毀解析七:延伸問題1.局部變量的創(chuàng)建2.返回值3.局部變量不初始化是隨機(jī)數(shù)4.函數(shù)傳參5.數(shù)據(jù)存儲八:手寫筆記引言“代碼的本質(zhì)不過是 CPU 執(zhí)行的一條條機(jī)器指令而程序的運(yùn)行不過是內(nèi)存地址的不斷偏移與覆蓋?!碑?dāng)我們在黑框終端里按下回車一個普通的函數(shù)調(diào)用在底層究竟發(fā)生了什么從參數(shù)壓棧、返回地址保存到棧幀的開辟、局部變量的分配再到最后的平棧與返回。這看似行云流水的幾毫秒背后是 push、pop、call、ret 等匯編指令的精確協(xié)作。棧幀Stack Frame不僅是高級語言函數(shù)執(zhí)行的基礎(chǔ)載體更是理解遞歸、緩沖區(qū)溢出漏洞Stack Overflow Exploit以及逆向工程的敲門磚。本文將從零開始剝絲抽繭用圖文筆記復(fù)原函數(shù)棧幀從創(chuàng)建到銷毀的全生命周期。前言須知1.函數(shù)棧幀的創(chuàng)建與銷毀在不同的編譯器下會略有差異2.越高級的編譯器在各種原因下封裝越復(fù)雜難以觀察3.該文章是基于VS 2013寫的4.在VS 2013中main函數(shù)也是被其他函數(shù)調(diào)用的調(diào)用邏輯如下一寄存器寄存器分為多種eax,ebx,ecx,edx,ebp,esp等其中最重要的是ebp,espeax,ebx,ecx,edxeax累加器。專管數(shù)學(xué)運(yùn)算比如加減乘除的核心參與者和結(jié)果存放地也是函數(shù)返回值的默認(rèn)載體ebx基址寄存器。最開始專門用來存放內(nèi)存地址的基準(zhǔn)值類似于 ebpecx計數(shù)器。專管循環(huán)次數(shù)比如匯編里的 loop 指令默認(rèn)使用 ecx 作為遞減計數(shù)器edx數(shù)據(jù)寄存器。通常用來配合 eax 做大數(shù)運(yùn)算比如乘法的高 32 位除法的余數(shù)ebp,espebp,esp中存放的是地址這兩個地址是用來維護(hù)函數(shù)棧幀的兩者聯(lián)合使用來維護(hù)兩個地址之間的函數(shù)棧幀從調(diào)用堆棧及反匯編中觀察在調(diào)用哪個函數(shù)ebpexp就去維護(hù)哪個函數(shù)esp棧頂指針。永遠(yuǎn)指向當(dāng)前棧的最頂端最低地址ebp棧底指針。在一個函數(shù)內(nèi)它通常保持不變用來作為尋找參數(shù)和局部變量的“基準(zhǔn)坐標(biāo)”eipeip指令指針。永遠(yuǎn)指向 CPU 下一條要執(zhí)行的指令地址二匯編指令1.基礎(chǔ)指令數(shù)據(jù)的搬運(yùn)mov把數(shù)據(jù)從一個地方搬到另一個地方寄存器到內(nèi)存內(nèi)存到寄存器寄存器到寄存器push將數(shù)據(jù)放入棧頂。底層動作是先將 esp 減 4因為棧向低地址生長然后將數(shù)據(jù)存入 esp 指向的新地址pop從棧頂取出數(shù)據(jù)。底層動作是將 esp 指向的數(shù)據(jù)讀出然后將 esp 加 4lea計算內(nèi)存地址并將其存入寄存器。它不讀取內(nèi)存里的值只算地址相當(dāng)于C語言的 取地址符2.控制指令程序的指揮call是進(jìn)入一個函數(shù)的號角。底層動作I.壓棧把下一條指令的地址返回地址即 eip 的值壓入棧中。II.跳轉(zhuǎn)修改 eip 的值讓 CPU 跳到被調(diào)用函數(shù)的入口地址去執(zhí)行ret與 call 相對應(yīng)結(jié)束函數(shù)調(diào)用。底層動作它從棧頂彈出一個值給 eip這就是 call 之前存進(jìn)去的“返回地址”CPU 就會順著這個地址回到主函數(shù)繼續(xù)執(zhí)行。jmp無條件跳轉(zhuǎn) / 根據(jù)標(biāo)志位跳轉(zhuǎn)如 je 相等跳轉(zhuǎn)jne 不等跳轉(zhuǎn)3.棧操作與算數(shù)指令sub/add減法 / 加法xor位運(yùn)算leave這是匯編器為了方便提供的一個復(fù)合指令。它等價于兩句指令I(lǐng). mov esp, ebp 把棧頂指針直接拉回棧底位置瞬間回收所有局部變量空間。II.pop ebp 彈出原來的 ebp恢復(fù)上一層函數(shù)的棧底。nop空指令什么都不做占用 1 個字節(jié)int 3(0xCC)斷點(diǎn)中斷。CPU 執(zhí)行到這里會暫停三棧幀的創(chuàng)建1.先在上個函數(shù)上壓棧2.在壓棧后執(zhí)行mov開辟棧區(qū)空間3.初始化棧幀空間在其中加入隨機(jī)值VS 2013上為C繼續(xù)用mov等開辟局部變量的空間四進(jìn)入函數(shù)1.壓入ebx,esi,edi2.壓入局部變量3.call指令下一條指令的地址4.記錄ebp—main5.開辟函數(shù)空間棧幀創(chuàng)建五棧幀的銷毀1.pop恢復(fù)調(diào)用前的值2.ret返回值3.mov把ebpesp移走六銷毀解析通過pop把壓棧的寄存器復(fù)原再由mov把espebp移走(回收)。實現(xiàn)銷毀main提前把棧頂與棧底備份了ebpesp能再次找到故未銷毀銷毀:直接允許后續(xù)操作直接覆蓋七:延伸問題1.局部變量的創(chuàng)建先建該函數(shù)調(diào)用的函數(shù)棧幀→初始化一部分→在棧幀空間中找到空間→把變量放進(jìn)去2.返回值現(xiàn)將返回值放入寄存器中→回到函數(shù)→把值放到變量中3.局部變量不初始化是隨機(jī)數(shù)隨機(jī)值是給main函數(shù)(或其他函數(shù))初始化時隨機(jī)放的(每個編譯器都有所不同)4.函數(shù)傳參調(diào)函數(shù)時先把參數(shù)以push從右到左開始壓棧壓入→進(jìn)入形參后通過指針偏移量找到壓棧的參數(shù)(函數(shù)形參不在該內(nèi)存棧幀內(nèi)而是調(diào)用該函數(shù)的棧幀的擴(kuò)展)5.數(shù)據(jù)存儲數(shù)據(jù)可存儲在三個地方(彼此相互獨(dú)立)硬盤內(nèi)存寄存器(寄存器是集成在CPU上的)八:手寫筆記