這場比賽題目品質很好,種類多而且都不猜謎,難度也很恰當,打完後覺得學到不少東西的比賽最棒了!
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_usebit
他的 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}




沒有留言:
張貼留言