【CTF Writeups】Teaser Confidence CTF Quals 2019 - p4fmt
Kernel challs are always a bit painful. No internet access, no SSH, no file copying. You’re stuck with copy pasting base64’d (sometimes static) ELFs. But what if there was another solution? We’ve created a lightweight, simple binary format for your pwning pleasure. It’s time to prove your skills.
nc p4fmt.zajebistyc.tf 30002
分數 | 解題人數 |
---|---|
304 | 10 |
Writeup
題目檔案解壓縮後有三個檔案 bzImage
, initramfs.cpio.gz
, run.sh
bzImage
是壓縮過的 linux kernel
initramfs.cpio.gz
是臨時的檔案系統
run.sh
裡面用 qemu-system-x86_64
把 kernel 跑起來
不熟悉 linux kernel debug 可以參考 Debug Kernel
First Glance
run.sh
跑起來後就會跑 linux kernel 彈出一個 shell
ls
一下可以看到三個比較重要的檔案 init
, p4fmt.ko
, flag
直接嘗試 cat flag
會得到 Permission denied
因為我們拿到的使用者是 pwn
而 flag
只有 root
有權限讀
init
裡面有一行 insmod /p4fmt.ko
加載 p4fmt.ko
這個內核模塊
看來我們的目標就是利用 p4fmt.ko
裡面的漏洞提權拿 root 權限,就可以 cat flag
了
前置作業
解壓 initramfs.cpio.gz
可以先用 binwalk
把 initramfs.cpio.gz
的檔案系統拉出來
1 | x initramfs.cpio.gz |
修改 init
1 | setsid cttyhack su root |
修改 init
讓我們有 root 權限,這樣才看得到 p4fmt.ko
內核模塊載入後的位址,等等才方便下斷點
修改完重新打包 initramfs.cpio.gz
1 | find . -print0 | cpio --null --create --format=newc | gzip --best > ../initramfs.cpio.gz |
修改 run.sh
1 | -gdb tcp:127.0.0.1:6666 |
開了 gdb server 後,就可以用 gdb 連上去 debug 了
首先先取得 p4fmt
內核模塊的位址
可以用 lsmod
或 cat /proc/modules
( 必須有 root 權限 )
1 | (gdb) target remote :6666 |
1 | / # lsmod |
逆向
起手式一樣 IDA 打開 ( 好像很多人改用 ghidra 了 O_O )
但是這次的反編譯有點糟,大部分還是看組語配 gdb
這個內核模塊主要的功能就是註冊一個新的執行檔格式 ( binary format )
1 | int __init p4fmt_init (void) { |
p4format
是一個 linux_binfmt
的結構
1 | struct linux_binfmt { |
其中的 load_binary
這個指標就是指向負責建立環境把程式跑起來的函式
而在這裡就是指向 load_p4_binary
這個函式 ( 一般的 ELF 執行檔是 load_elf_binary
)
1 | int load_p4_binary (linux_binprm *bprm) { |
linux_binprm
會先讀檔案的前 128 bytes 放進 bprm->buf
因為有這個結構有 __randomize_layout
,所以結構成員的順序是隨機的
這題的 bprm->buf
從 0x48
開始 128 bytes,可見下圖
程式一開始會先檢查前兩個 bytes 是不是 P4
接著檢查第三個 byte 是不是 \x00
,不是的話會噴 Unknown version
接著第四個 byte 可以是 \x00
或 \x01
,\x00
的話會進簡單的路線,\x01
會進複雜的路線
接著四個 bytes 代表後面有幾個 mapping
接著八個 bytes 代表 mapping 的開頭在 buf 的 offset
接著八個 bytes 擺的是 entry point 的位址
其他的部分基本上跟 load_elf_binary
一樣
1 | vm_mmap(bprm->file, *(QWORD *)(bprm + 0x50), 0x1000, *(QWORD *)(bprm + 0x50) & 7, 2, 0); |
1 | struct p4_mapping { |
漏洞
mapping_count
改大可以 leak linux_binprm
其他欄位的值
_clear_user
沒有檢查,可以把 kernel 上任意位址的值清空
linux_binprm
有一個 cred
的結構,裡面存的就是 uid, gid
所以我們只要 leak 出這個 cred
的位址,然後用 _clear_user
清成 0,我們的程式就是 root 權限了 ( root 的 uid 是 0 )
嘗試
1 | #!/usr/bin/env python3 |
先寫個簡單的 p4 格式的執行檔測試一下我們的理解是不是對的
1 | echo UDQAAQEAAAAYAAAAAAAAADAAQAAAAAAABwBAAAAAAAAAEAAAAAAAAAAAAAAAAAAASLgBAQEBAQEBAVBIuHVkcnULAQEBSDEEJGoBWGoBX2oFWkiJ5g8FajxYDwU= | base64 -d > a ; chmod +x a ; ./a |
1 | [50353.170813] vm_mmap(load_addr=0x400000, length=0x1000, offset=0x0, prot=7) |
接下來要找 cred
的位址,因為 pwn
的 uid
是 1000 ( = 0x3e8 )
所以我們把使用者切換成 pwn
,切成 pwn
之後要在 /tmp
才可以寫檔
然後把 mapping_count
改大一點,比如 6
,在他印出的位址指向的值中找 0x3e8
1 | [50800.668734] vm_mmap(load_addr=0x400000, length=0x1000, offset=0x0, prot=7) |
找了一找發現在第六個 vm_mmap
的 0xffffa1c307595b40
這個位址是 cred
但是這個位址每次跑起來都不一樣,不過多跑幾次會發現,這個值會一直循環重複利用,所以只要多跑幾次就會對了
Final Exploit
1 | #!/usr/bin/env python3 |
【CTF Writeups】Teaser Confidence CTF Quals 2019 - p4fmt