高級靜態分析技能基礎:X86架構的堆棧結構描述

在代碼運行時須要臨時存放各類信息,例如函數調用時的輸入參數,局部變量等,這些信息存儲在一種叫作」棧「的數據結構上。它的特色是後進先出,也就是最後存儲到棧裏面的數據將會最早被取出來。X86體系自帶棧結構,寄存器ESP,EBP專門用於對棧進行操做。EBP指向棧所在的內存地址,ES[圖片上傳中…(截屏2020-09-30 下午3.56.59.png-a3f002-1601452956326-0)]
P指向數據進入或彈出堆棧所在的位置,同時對於棧操做的指令有push,pop,call,leave,enter,ret等。
棧內存的分配由高到低,也就是當數據壓入堆棧時,會被最早存儲在地址高的內存,以下圖所示:
堆棧在程序安全中發揮着很是重要做用,不少系統被破解就是從堆棧入手。同時使用堆棧最多的是函數調用,函數在執行時,在二進制層次,代碼的運行遵循所謂的cdecl原則。其過程爲,在任何函數執行前都會有一段固定的「開場白」,它的做用就是對堆棧進行一些預處理,也就是準備好操做堆棧用的各個寄存器。在函數的結尾處一樣有對應的「終場白」,其做用就是恢復堆棧信息和清空原來用於操做堆棧的寄存器。
咱們看在二進制層面,函數執行時進行的一系列操做。首先是一系列push指令,它們將函數參數壓入堆棧。接着,使用call指令後面跟着函數第一條指令在內存中的位置,call指令作好幾個動做,首先是將call指令下一條指令的地址壓入堆棧,由於函數調用完後須要返回當前位置繼續往下運行,而後它將EIP寄存器的數值設置爲函數第一條指令對應的內存地址,因而接下來就從被調函數第一條指令開始運行。
因爲函數編譯成二進制代碼時,編譯器會自動爲其開頭添加一段「開場白」,這段開場白會爲函數的運行專門分配堆棧,這樣函數就能存儲它本身的局部變量,同時把EBP指針數值存入堆棧,這樣函數結束後能快速將壓入堆棧的信息彈出來。當函數的代碼執行完後,編譯器加在函數後面的「終場白」執行與開場白相反操做,它把開場白壓入堆棧的信息彈出來,恢復原來存儲在堆棧上相關寄存器的值,這時寄存器EBP的值會被恢復,此時EBP指向調用函數(注意不是被調函數)的堆棧,這樣就能夠訪問調用函數的局部參數了,最後執行ret指令,該指令把執行被調函數前壓入堆棧的EIP寄存器的值恢復,因而CPU就能回到函數被調用的位置,並接着執行下一條語句。
爲了更加形象的理解上面描述,咱們看下圖:
上圖表示函數調用時堆棧數據的變化。從0012F050到0012F040對應的數據是傳遞給被調函數的參數,注意到根據cdelc原則,最後一個調用參數先入棧,因而參數N先被壓入一直到參數1,接着把EBP寄存器的值壓入堆棧,也就是把函數調用前父函數的堆棧入口存儲起來,而後將EBP的值指向當前ESP指針所在位置,當子函數運行時,對堆棧內存的訪問就從0012F038開始,上圖也能夠看到子函數在堆棧上存儲了N個本身的局部變量,每次要存儲局部變量時,ESP的值都會減4,例如此時若是執行 push EAX寄存器的值壓入堆棧,那麼ESP就會指向0012F028,而後把EAX寄存器的信息存貯在給定位置的4字節。
接下來若是執行pop ebx,那麼CPU就會將ESP所指向位置開始4字節的數據轉移到寄存器ebx,而後將ESP的值加4,使得它指向0012F02C。X86指令提供了幾條專門針對堆棧的操做指令,例如pusha,一條指令就能分別將寄存器AX,CX,DX,BX,SP,BP,SI,DI壓入堆棧,指令pushad就能分別將寄存器:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI壓入堆棧,寄存器在將高級語言編譯成機器指令時,不多使用這些操做指令,若是在反彙編中時常看到這些指令時,咱們就有理由懷疑二進制代碼被人動了手腳。

本文分享自微信公衆號 - Coding迪斯尼(gh_c9f933e7765d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。安全

相關文章
相關標籤/搜索