執行人: Welly0902
藉由回顧經典的 PWN 手法,理解 Linux 核心機制。本任務應在 Linux v5.15+ 驗證。
整理 Linux Kernel Exploitation 並重現相關實驗。
簡報: Kernel Exploitation
筆記: Linux Kernel pwn
台灣大學計算機安全 (2019)
至少要涵蓋以下:
在 Linux v5.15+ 驗證後,需要探討對應的原理,佐以 Linux 核心原始程式碼說明。
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 時去理解程式與作業系統互動的機制。
gdb-gef
seccomp-tools
ROPgadget
GHIDRA
QEMU
或許在去 exploit kernel 前,先了解 binary exploitation 會對之後 kernel exploitation 在做什麼會比較有概念。這裡先拿台灣的 CTF 網站 pwnable.tw 的第一個 challenge: Start 來去熟悉 PWN 的流程。
在 binary exploitation 中,顧名思義通常會先拿到一個 binary file
接下來 CTF PWN 的流程大概如下:
拿到 binary 後,首先我們可以先在 gdb 中用 checksec
去檢查此 binary 在 gcc 編譯時有加了哪些保護機制。
接著我們可以利用 objdump
來看我們的 assembly code:
也可以使用 GDB 來看:
觀察以上的 assembly code 可觀察到以下行為:
Let's start the CTF:
int 0x80
,也就是 interrupt 來利用 syscall,可查詢 32 bit 對應的 syscall 來知道分別是 sys_write
與 sys_read
name | eax | ebx | ecx | edx |
---|---|---|---|---|
sys_write | 0x04 | fd = 1 | char *buf = esp | count = 0x14 |
add esp,0x14
也就是 esp 加上20,因為 stack 愈接近 top 的地址的 address 值愈小,所以加上20作用類似於 function epilogue 回收 buffer 空間的動作使用 gef 來進行 debug,這邊想要觀察 syscall write 與 read 與 memory 之間的互動,所以把 breakpoint 設在 int 0x80
之前,並可以輸入 ni
觀察每執行一行 instruction 所帶來的變化
當 ni
至 eip 指向 int 0x80
也就是下一行要執行 sys_write 時,再次 ni
後可看到:
首先可以看到終端機輸出 Let's start the CTF:0x08048091 in _start ()
也就是如果正常執行程式輸入值之前會看到輸出的字串。這裡 sys_write
所做的事就是將 stack 中目前 esp 所指到的地址抓 20 個位元組輸出於標準輸出。
這裡可以實驗看看如果我們在 sys_write 參數中,讓原本 20 個位元組設成 28 個位元組,會不會印出 stack 中預期外的值:
可以看到輸出的字變成 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 來驗證:
此時我們可以看到上面顯示的 stack 範圍內 0xffffd474 (stack top 也是 esp 所指之處)
到 0xffffd490
一共 28 bytes 都被填入 a。不過剛總共輸入 70 bytes 的 a,再用 gef 檢查一下:
由上可以看出總共只寫入了 60 bytes 的 a。
如此一來,結合 sys_write
和 sys_read
將會留給我們寫入 shellcode 的空間。
從上述 gef 的觀察,可以知道有 BOF 的漏洞可以讓我們改寫 return address 進以操作。但此時並沒有辦法直接改寫 return address 到一個 function 來開 shell,故需將 shell code 寫入 memory 加以執行。
在這之前,因為 EIP 每次執行程式的地址都會不同,所以我們必須先 information leak 將 EIP address leaked 出來再利用。
這裡我們設計 payload 時,先用 20 bytes 的 a 將 stack 填滿,接下來的 32 bit 就可以將原先的 return address 給 overwrite。
當寫入完成後,此時要執行最後兩行:
當 add esp,0x14
後,ESP 往下 20 bytes(高地址),stack 變為如下:
此時 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,程式如下:
在知道了 ESP base address 後,因為程式剩下的 instruction 還剩下一次 sys_read,我們就可以利用再一次對 stack 的寫入去將 shellcode 也塞進去,並運用剛 leaked 出來的 ESP address,把 return address 指到 shellcode 所在的地址,示意圖如下:
這ㄧ次送進 sys_read
的 payload 為:
這次除了又從 ESP 開始填滿了 20 bytes 的 a 之外,剛 leaked 的 address 加上 20 bytes 剛好就會指到 shellcode 上
所以從上圖看 return 到 shellcode 所在位置,成功開 shell,就可以開啟 flag。
完整程式參考本篇:
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)
從 kernel pwn 選出至少 4 個能在 Linux v5.15+ 重現的案例,予以解說並彙整相關素材。