iT邦幫忙

2025 iThome 鐵人賽

DAY 3
0
Security

現在是pwn的天下!系列 第 3

【Day-3】詳細說明stack frame運作原理,學buffer overflow前必須知道的事

  • 分享至 

  • xImage
  •  

前言

以下知識全部取自張元,如果需要,最下面reference有給影片連結
當我們在寫C/C++程式時,每當一個函式被呼叫,作業系統會分配一個暫時的記憶體區塊來儲存該函式的區域變數、回傳位址等等,這個區塊就稱為 stack frame,那由於我們之後也要探討buffer overflow的漏洞利用,所以也需要先了解stack frame的內容才可以清楚理解buf的細節

基礎概念

stack frame重要的兩個步驟

  • function prologue
    • 設定新的stack frame,也就是建立這次函式執行所需要的空間
  • function epilogue
    • 清理stack frame,恢復呼叫者的狀態,準備return

一個function的stack frame差不多就像下面那樣子,以下是我參考張元做的stack frame表:
https://ithelp.ithome.com.tw/upload/images/20250807/20172088r17FXgGyAB.png

  • 夾在rsp跟rbp中間是local variable
  • rbp指的那8個byte存的是call了這個function的rbp
  • 再+8的地方是return address
  • arguments就是如果超過6個參數,剩下的參數就會被放置在這
  • 越上面記憶體的地址就會越來越小,越下面記憶體的地址就會越來越高
  • stack在push的時候,會往上面長,因為stack在長的方向是往低的地址長

那我們現在來看function prologue跟function epilogue的流程

rip現在是停在call function的部分,被指到代表即將執行這一行,但是還沒執行這一行

func:
    push rbp
    mov rbp, rsp
    sub rsp, 0x70
	...
    mov eax, 0x1
    leave
    ret
    
main:
    push rbp
    mov rbp, rsp
    mov rdi, 1234
    mov rsi, 666
    call func          <== rip
    mov eax, 0 // address = 0x40071A
    leave
    ret

https://ithelp.ithome.com.tw/upload/images/20250807/20172088Rd5nNPIv5b.png
call function這個instruction可以看做是

push next-rip
jmp func

他會把next-rip push到stack
https://ithelp.ithome.com.tw/upload/images/20250807/20172088oL4zl6rR7m.png
這樣就可以jump到func

func:
    push rbp          <== rip
    mov rbp, rsp
    sub rsp, 0x70
	...
    mov eax, 0x1
    leave
    ret
    
main:
    push rbp
    mov rbp, rsp
    mov rdi, 1234
    mov rsi, 666
    call func          
    mov eax, 0 // address = 0x40071A
    leave
    ret

其實可以發現,compiler在前面三行都是,這個其實就是function prologue的部分

push rbp      ; 保存舊的 base pointer
mov rbp, rsp  ; 設定新的 base pointer
sub rsp, N    ; 預留區域變數空間(N 是需要的大小,指區域變數)
  • 假如說這個function宣告一個local variable,例如int a;,那就會發現它會sub rsp, 0x10
  • 這是因為stack會以16個bytes做對齊,所以它會直接往上補到16個byte的倍數
  • 正因為stack的地址都是16byte的倍數,所以一個stack的address結尾通常都為0

接下來就會執行compiler幫我們加的function prologue的instruction

func:
    push rbp
    mov rbp, rsp          <== rip
    sub rsp, 0x70
	...
    mov eax, 0x1
    leave
    ret
    
main:
    push rbp
    mov rbp, rsp
    mov rdi, 1234
    mov rsi, 666
    call func          
    mov eax, 0 // address = 0x40071A
    leave
    ret

https://ithelp.ithome.com.tw/upload/images/20250807/20172088WPXdGYAauX.png
然後就會執行mov rbp, rsp,這樣就會讓rbp指向跟rsp一樣的位址
https://ithelp.ithome.com.tw/upload/images/20250807/20172088aY87KcOrAa.png
再來就會執行sub rsp, 0x70
0x70這個值是compiler在compile的時候就已經決定好了

func:
    push rbp
    mov rbp, rsp
    sub rsp, 0x70          <== rip
	...
    mov eax, 0x1
    leave
    ret
    
main:
    push rbp
    mov rbp, rsp
    mov rdi, 1234
    mov rsi, 666
    call func          
    mov eax, 0 // address = 0x40071A
    leave
    ret

那就會挪出0x70的位置給local variable
https://ithelp.ithome.com.tw/upload/images/20250807/20172088Q5yOXrWMCx.png
那我們可以發現我們的rsp跟rbp都已經指向function的stack frame上了
這樣我們就完成我們的function prologue了 uwu

然後我們離開後執行mov eax, 0x1把return value放rax暫存器後
我們就會執行leave ret,那這也是function epilogue的環節

func:
    push rbp
    mov rbp, rsp
    sub rsp, 0x70
	...
    mov eax, 0x1
    leave          <== rip
    ret
    
main:
    push rbp
    mov rbp, rsp
    mov rdi, 1234
    mov rsi, 666
    call func          
    mov eax, 0 // address = 0x40071A
    leave
    ret
  • leave return也可以分為兩個instruction
mov rsp, rbp
pop rbp

所以rsp也會指到rbp的地方
https://ithelp.ithome.com.tw/upload/images/20250807/20172088Xfkjtw37Qy.png

  • 再來執行pop rbp,那pop rbp就是把rsp現在指的地方的值塞到rbp
  • rsp現在指到的值就是原本rbp的值,所以把它塞回rbp的時候,rbp就會指回去
  • rsp往下8個byte
    https://ithelp.ithome.com.tw/upload/images/20250807/20172088uIF7Hwm6D8.png

接下來我們執行ret

func:
    push rbp
    mov rbp, rsp
    sub rsp, 0x70
	...
    mov eax, 0x1
    leave
    ret          <== rip
    
main:
    push rbp
    mov rbp, rsp
    mov rdi, 1234
    mov rsi, 666
    call func          
    mov eax, 0 // address = 0x40071A
    leave
    ret
  • ret我們可以看成pop rip
  • 把stack上面的rsp現在指到的地方的值塞到rip裡面

那這時候我們function epilogue就結束了

  • 這時候可以發現rsp跟rbp又回到原本main的stack frame了
  • rip又指到了call完function後我們即將執行下一行的instruction
    https://ithelp.ithome.com.tw/upload/images/20250807/20172088viDkOX5sfl.png
func:
    push rbp
    mov rbp, rsp
    sub rsp, 0x70
	...
    mov eax, 0x1
    leave
    ret
    
main:
    push rbp
    mov rbp, rsp
    mov rdi, 1234
    mov rsi, 666
    call func          
    mov eax, 0 // address = 0x40071A          <== rip
    leave
    ret

結論

以上就是stack frame的運作以及function prologue跟function epilogue的流程
那我們理解完這些知識後,明天就要接觸buffer overflow了w
今天寫這個筆記快累死,明天應該可以輕鬆一點
明天見~~

資料來源

https://www.youtube.com/watch?v=U8N6aE-Nq-Q&t=2303s


上一篇
【Day-2】暫存器 (register) 介紹
下一篇
【Day-4】buffer overflow攻擊手法以及Canary的保護機制
系列文
現在是pwn的天下!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言