# note bamboofox ctf 2019 bamboofox 出的跨年題目,一道經典的 heap 題,具體分析起來不難,但有點沒跟上現在 libc 的變化,導致遇到的坑有點多......,最後差臨門一腳解出來,不過算是學到一點東西 ## 分析 這是一道經典的選單題,程式不是很大,很簡單就可以分析完,不過找漏洞還是花了我一段時間,根據別人的說法這漏洞很明顯,那可能是我看的不夠多QQ ### 漏洞 copy() 會把字串用 snprintf 的方式放到 note[dst_idx].note 裡面,當時看到沒起疑(不過一般正常來說會用 strcpy 之類的?) ```c int copy(void) { uint src_idx; uint dst_idx; int iVar1; printf("Source idx: "); src_idx = read_int(); printf("Destination idx: "); dst_idx = read_int(); if ((((src_idx < 8) && (note[src_idx].use_flag != 0)) && (dst_idx < 8)) && (note[dst_idx].use_flag != 0)) { src_idx = snprintf(note[dst_idx].note,note[dst_idx].sz,"%s",note[src_idx].note); iVar1 = 0x202068; note[dst_idx].sz = (ulong)src_idx; } else { iVar1 = puts("Out of bound or note is not exist"); } return iVar1; } ``` 途中還因為找不到所以用 fuzz 結果 fuzz 不出個所以然 (因為太難觸發) 後來還是覺得最容易出問題的就是對 snprintf 的誤用,於是 google 了一下找到這篇[文章](http://blog.kankanan.com/article/4f605f8853ef80fd4e0d61c2-snprintf.html),發現他將 snprint 的 return value 放到 size 內,而當 source 的 strlen 比 dst 來的大的時候, snprintf 會回傳 source 的大小,所以 dst 的 size 就會被改大,於是就有了 heap overflow ## 利用 有了 heap overflow 我開始想著怎麼利用: ### leak 主要的難點在於他限定只能用 calloc 要 small chunk 大小的 chunk ,所以基本上 free 掉後不會用 libc address 在 heap 內,同時因為 calloc 所以 allocate 的 chunk 內容會被清空為 0 以下是簡略的做法: 1. 先布置好 heap 的 layout 2. 用 copy 改掉 note 的 size (heap overflow) 3. 利用 exploit 改掉下一個 chunk size 為不在 tcache 的大小 4. free 掉該 chunk 5. 要一個跟下下塊 chunk 切齊的 chunk (這樣從 unsorted chunk 切割的時候 libc address 會落在 allocated chunk 的 data pointer 位置 6. show(下下塊 chunk ) 這樣就可以 leak 出 libc address ## exploit ### 方案 1 透過 tcache 的利用,拿到 free_hook 附近的空間,寫 system 在 free_hook 上,然後 call delete 結果發現怎麼搞 libc 都不會從 tcache 上拿 chunk ,這大概浪費我半天的時間... ### 方案 2 看到某篇文章(現在找不到),意識到 calloc 某方面來說在 fastbin poisoning 會幫助到我,原因是這樣的: free 掉 chunk 會放進 tcache ,但因為 calloc 的原因放進去的 chunk 基本上不會拿出來用,所以一直 calloc 一直 free 很容易把 tcache 填滿且不會用到太多 note 空間 塞滿 note 空間,我開始想著要怎麼利用 fastbin poisoning 1. copy 創造一個 heap overflow 2. free 掉兩個 fastbin 大小的 chunk (前提是該大小的 tcache 被填滿) 3. 透過 overflow 改掉 fastbin 的 fd 指到某個存有 0x7f 的地址 A 上 4. 要兩次相同大小的 chunk 就會要到地址 A #### 方案 2-1 * 方法: 透過 unsorted bin attack 將 libc address 放到 free_hook 附近,再用 fastbin poisoning 將 system 寫在 free_hook 上 * 結果: 不知道為啥 calloc 下會去檢查一些有的沒的,最後導致 memory corruption ,死掉的在檢查 unsorted bin 連接的 size 部分,如果能繞過說不定能行 #### 方案 2-2 * 方法: 透過 fastbin poisoning 寫 system 到 malloc_hook 上 * 結果: 忘記有檢查 malloc 大小...... #### 方案 2-3 * 方法: 寫掉 stderr 的 vtable 改掉 IO_overflow 地址為 system ,再寫掉 flag 為 "sh\0" * 結果: 噴出奇怪的東西,但最後還是沒成功,且題目給的 libc 中 file structure 是不可寫的 #### 方案 2-4 * 方法: 寫掉 stdin 的 buf_end 和 buf_base ,將其改到 free_hook 附近 * 結果: 忘記他直接用 read() ,所以不會利用 file structure ,當然也就不會觸發 #### 方案 2-5 * 方法: 用 fastbin poisoning 要到 malloc_hook ,寫上 one_gadget 地址 * 結果: 三種條件都無法達成,最有可能達成的條件因為中間有 `movaps` 的關係需要 stack align 0x10 ,我有想過呼叫 gadget 或是其他 function 想辦法調整 stack 但是失敗了 #### 方案 2-6 * 方法: 跟 2-4 差不多,但在 vtable 上改成 one_gadget * 結果: 在自己編譯的 libc 上可用 (而且還不滿足條件,真奇怪),但 remote 版本的 vtable 上不可寫 #### 方案 2-7 這部分是看 writeup 才知道的,也是成功版本: * 方法: 跟 2-5 差不多,只是在 overwrite malloc_hook 同時順便 overwrite realloc_hook , realloc_hook 放 one_gadget 而 malloc_hook 放任何會呼叫 realloc 的 gadget : malloc_hook -> call realloc@plt -> one_gadget 特別注意到直接放上 realloc@plt 或是 realloc 地址不可行,一定要用 libc 中用 call realloc@plt 的指令,因為這樣 push 的結果才會剛好對齊 0x10 至於怎麼在茫茫 libc 中找到任何會 call realloc@plt 的指令呢?我用 ag 直接在 libc source code 中找符合 `realloc (` 的 pattern ,找到後記錄下該 function ,然後直接在 libc 內找並記錄 offset 後就可以了 ## 收穫 1. 對 libc heap 2.27 之後還是滿陌生的 2. calloc 不會採用 tcache 內的 chunk 3. 2.27 某些版本 vtable 不可寫 4. 利用 malloc_hook 呼叫 call realloc@plt 的程式且在 realloc_hook 上擺 one_gadget 修正 stack align 5. libc 2.27 後 exit 或是 abort 好像不會 flush() file structure ## 結語 寫的很累但最後跟解答滿接近的,這點來說不知道該笑還是該哭......,只好安慰自己有學到東西就好 發現自己對 heap 題滿排斥的XD ,鼓起勇氣開始寫的時候又發現自己對 file structure exploit 很排斥(主要是在 libc debug 很累吧,坑又有點多),有點羨慕高手都能輕鬆自在的跳進 file structure 海中