這場比賽題目品質很好,種類多而且都不猜謎,難度也很恰當,打完後覺得學到不少東西的比賽最棒了!
Info
附加檔案是 ELF64 的執行檔
diary
。PIE 沒開,Partial RELRO
連線上去感受一下功能,選項很少的選單題:
總之三個功能:
- 寫日記
- 顯示某篇日記
- 刪掉某篇日記
Vulnerability
一打開 IDA 就看到
getnline
這個讀日記內容的函式有非常明顯的漏洞:buffer
a1
在傳進來之前是 malloc(len)
獲得的,read
時多讀了一個 byte(len + 1
)。因為
len
(日記長度)完全沒有限制,所以可以構造長度使得 overflow 壓到下一個 chunk 的 size
,就是個 heap exploit 常見題型了!正當覺得這是個再正常不過的 heap exploit,想說先寫個 PoC 讓他 crash 的時候卻發現
ltrace
一直都不會顯示 malloc
或 free
,這才注意到.. 他的 heap 居然是自己實作的 XDrz
因此花了點時間看它的 heap 實作方式。大致上其實跟 glibc 的 heap 幾乎一樣,但簡化了很多操作。
一樣的部分:
- chunk 結構一樣是
previous_size,size,fd,bk
- 同樣有
prev_in_use
bit
他的 heap 實作簡述:
- 只有一個 bin,而且他是 sorted bin,裡面的 free chunks 總是按照
size
由小到大排好序。 malloc
的size
是 8 byte 對齊 (glibc#heap 是 16 byte 對齊)- bin 裏一開始就有一個大 chunk (4096 byte)
malloc
時:- 從 bin 裏找第一個
size
足夠的 chunk - 如果找到的 chunk 比需求
size
多超過 8 byte,切掉多餘的部分放回 bin free
時:- 前後的 chunk 如果已經被
free
,就先從 bin 裏unlink
之後與當前 chunk 合併 - 把當前 chunk 放進 bin
- 所有
unlink
都沒有做 double-linked list 合法性的檢查
此外,自己實作的 heap 是另外
mmap
出來,並且權限為 rwxp,如果能控 rip
到上面就可以執行 shellcode。
因此需要做到兩件事:
- Information leak: 得知 heap base
- 控制
rip
使跳轉到 heap 上執行 shellcode
Leak heap base
如果是從 bin 裏被拿出來的 chunk,上面會殘留有
fd
跟 bk
。輸入日記內容時因為是用 read
讀的,只要內容很短且沒有換行就可以讓 chunk 上的 fd
被留著,之後 show diary 就可以把上面的 fd
印出來,即可 leak heap base。詳細可見最後附的 script。
Control rip
最一開始發現的 one-byte overflow 漏洞這時候就要派上用場了。
只要 overflow 把
prev_in_use
bit 改掉,就可以使在 free
時「誤會」前一個 chunk (prev_chunk
) 已經被 free
了,此時就會對 prev_chunk
進行 unlink
。而 prev_chunk
上的 data 是我們完全可控的,因此只要適當控好 prev_chunk
的 fd
跟 bk
,就可以改掉 GOT entry 的值。這也是 glibc 古老版本的 heap exploit 常用技巧。
unlink:
void unlink(Chunk *c) { c->bk->fd = c->fd; c->fd->bk = c->bk; }
因此讓
prev_chunk
上的 data 為fd: puts_got - 0x10 (0x602020 - 0x10)
bk: shellcode_addr
shellcode_addr
就根據自己哪篇日記的內容是 shellcode,用 heap base 算出來就可以了。這樣進行 unlink 時就會
void unlink(Chunk *c) { c->bk->fd = c->fd; // *( shellcode_addr + 8 ) = fd = puts_got - 0x10 c->fd->bk = c->bk; // *( (puts_got-0x10) + 0x10) = bk = shellcode_addr // *puts_got = shellcode_addr }
就成功把 puts_got 改成 shellcode 的位置,下次呼叫
一點要注意的是因為剛才
這樣就做到任意 shellcode 執行了,至此為止的 exploit script。
然後這題開始了。
binary 最一開始有利用 seccomp 做為 sandbox,簡單講就是通常用來阻擋
首先要先知道他究竟阻擋了哪些 syscall。雖然之前有看過這類 sandbox ,花了點時間在研究 seccomp 的註冊方式QQ。
seccomp 的原理與解讀方式可以參考這篇,年初 32C3 那場的 writeup 也有出現 seccomp。
簡單來說註冊禁止 syscall 的要寫 BPF(Berkeley Packet Filter),肉眼不容易看懂。裝了 libseccomp之後, tools 資料夾裡面有個
因此先把 BPF 的內容 dump 出來再餵進去就可以了。
可以看到他阻擋了哪些 syscall number,對應一下可以知道是這些:
但會發現
題目敘述當中其實有個 Hint 寫可以利用 "./bash"。因此 shellcode 改成
原因是
開啟
既然沒有
Flag:
puts
時就會跳到 shellcode 上了。一點要注意的是因為剛才
unlink
操作裡會改掉 shellcode_addr
後面 8 個 byte,因此 shellcode 開頭要先 jmp
來跳過被改寫的地方。這樣就做到任意 shellcode 執行了,至此為止的 exploit script。
然後這題開始了。
binary 最一開始有利用 seccomp 做為 sandbox,簡單講就是通常用來阻擋
execve
syscall 讓你能開 shell 做任意代碼執行。首先要先知道他究竟阻擋了哪些 syscall。雖然之前有看過這類 sandbox ,花了點時間在研究 seccomp 的註冊方式QQ。
seccomp 的原理與解讀方式可以參考這篇,年初 32C3 那場的 writeup 也有出現 seccomp。
簡單來說註冊禁止 syscall 的要寫 BPF(Berkeley Packet Filter),肉眼不容易看懂。裝了 libseccomp之後, tools 資料夾裡面有個
scmp_bpf_disasm
可以直接讀 BPF 的 code 進行 disassembly。因此先把 BPF 的內容 dump 出來再餵進去就可以了。
可以看到他阻擋了哪些 syscall number,對應一下可以知道是這些:
2 open 257 openat 56 clone 57 fork 58 vfork 59 execve 85 create 322 execveat
execve
被擋掉自然,但因為檔了 open
,因此也不能 open/read/write
來讀 flag 內容。不過黑名單編號總是會由遺漏,64bit 某些版本的 kernel 中會有些 syscall 在 5xx 號那附近,這裏可以看到列表,520 也是 execve
。因此只要把 shellcode 中 syscall number 改成 520 就好了。但會發現
execve("/bin/sh")
一樣會失敗!原因是 seccomp 的規則是會延續的,而 execve
的過程中就會用到 open
,就被擋下來了QQ。If execve(2) is permitted, then the seccomp mode is preserved across execve(2).
題目敘述當中其實有個 Hint 寫可以利用 "./bash"。因此 shellcode 改成
execve("./bash")
,攻擊遠端時竟然就成功了!原因是
./bash
是 32bit 的,syscall number 會不一樣,所以原本禁止的 syscall 在 32bit mode 底下不太有影響。開啟
bash
之後,會發現打什麼指令好像都沒反應,但是 echo 123
卻會成功。原因是 32bit 的 fork
剛好是 number 2,因此如果要執行指令就會因為使用 fork
而被 seccomp 擋下來。換句話說,我們就不能 ls
或 cat flag
,只能想辦法透過 builtin command 來知道 flag 內容。既然沒有
ls
,可以返璞歸真用 for
迴圈 + echo
當 ls
用,讀檔則可以利用 source
並把錯誤訊息(stderr
)導回來就會看到 flag 了~Flag:
TWCTF{bl4ckl157_53cc0mp_54ndb0x_15_d4ng3r0u5}
沒有留言:
張貼留言