--- title: swsec110_hw11_writeup date: 2022-01-13 tags: swsec --- # swsec110 hw11 writeup 學號: 309553032 網頁版: https://hackmd.io/@johnson08/rk7CuWont 本次作業主要分成以下部分: - [leak libc address](#leak-libc-address) - 減少`_IO_write_base`的方式利用 partial overwrite, libc自動補 1 byte `\x00` - [hijack vtable](#hijack-vtable) - 將合用的 one_gadget (offset 0xe6c7e) 塞入 fwrite 會使用到的、vtable entry指向的 `_IO_file_overflow` - 雖然理論上會先call到`_IO_file_xsputn`,應該也可以覆蓋這邊,但不知為何會造成segmentation fault - [remote buffer flush](#remote-buffer-flush) - local 的程式在 remote 無法直接 work 的原因是 buffer 沒有 flush 掉;填滿 buffer 或者換行可以flush, 這邊採用前者 - [catch the flag](#catch-the-flag) ## leak libc address ### libc 位址資訊位於 created file structure 由於此題不像 labs 有直接給 libc functions 位址,也不像 pwn2 可以 free unsorted bin 以 leak main arena, 而是一開始就空空如也,但基於 UAF 的精神還是翻了一下各個 chunks 翻著翻著就看到了某個很像屬於 libc 的東西 ![](https://i.imgur.com/gPJIWfe.png) 檢視其 file structure ![](https://i.imgur.com/LqCVL2l.png) 發現他是 stderr, 這呼應到上課所提到的,glibc會把所有 file structure 串起來,以便程式終止時 flush buffers 總之,想要 leak 的目標在這邊(雖然之後發現不是) 有個可控的 file structure, 又有 fwrite 可用之,看似滿足任意讀的架構,但仔細想了一下,heap的位址也沒有,即便可以任意讀,也無法指定到該 chunk ### hint: _IO_IS_APPENDING 由出題的hint谷歌此flag可以如何達到leak,找到以下關鍵: ![](https://i.imgur.com/0bPGVsZ.png) (src: https://n0va-scy.github.io/2019/09/21/IO_FILE/) trace code 以及理解的篇幅在此文章皆有提到。綜上,複寫 fp 須達到三件事: - fileno: 1 (stdout) - flags: 0x1800 - `_IO_write_base` 改小 至此,想法如下:由於不知道`_IO_write_base`位置,上述三件事無法透過一次的 overwrite 完成;須先進行一次 overwrite, 將較高位址的 fileno 覆蓋為 1, 再 call 一次 `fwrite(note_buf, 1, BUF_SIZE, fp);`還原看起來比較資訊有效的 io read/write pointers, 再透過 (hint) partial overwrite 將想要讀取的位址指定到想要讀的地方 ![](https://i.imgur.com/yUf7rP7.png) 至此,fileno成功地被覆蓋為1, 但再執行一次 fwrite() 後,發現本來想要的酷酷的 chain 當然也被覆蓋掉了,因為是在 fileno 前面啊... ![](https://i.imgur.com/BsxzKpv.png) 不過,就算 chain 可以被保留,fp 在遞增一個 buffer size 之後已經到遙遠的下方了,看看此時如果照原本想法 overwrite 可以得到什麼: ![](https://i.imgur.com/kc8YKu8.png) ![](https://i.imgur.com/GOawHI6.png) 雖然不知道`_IO_wfile_jumps`是什麼,但能夠幫助我得到 libc 就對了,就照原訂計畫去 overwrite 實驗幾次後,發現overwrite的數值後面會再被補 null byte, 那麼退而求其次,讓 null bytes 被補到 `_IO_write_base` 的 lowerbyte, 以此達到減小`_IO_write_base` 的目的 ![](https://i.imgur.com/TPGe7Pm.png) ![](https://i.imgur.com/akd7Kzj.png) ![](https://i.imgur.com/CvsMQyN.png) 看到`_IO_write_base`位址,減去其elf symbol offset 即可得到 libc 位址! ![](https://i.imgur.com/1nUJ9Eb.png) ![](https://i.imgur.com/cTQYXwK.png) ## hijack vtable 目標是將 `_IO_file_overflow` 改成 one_gadget ![](https://i.imgur.com/yYKOKKz.png) 位於 `_IO_file_jumps + 0x18` 在 lab2,3 中,我們透過 fread() 達到任意寫,而這邊是 fwrite 任意寫。 google fwrite 怎麼任意寫 ![](https://i.imgur.com/viwWSVW.png) (src: https://xz.aliyun.com/t/5853#toc-6) 好像突然想起 fwrite 是一件 trivial 的事情,只要控來源目的就可以達成啊。 是這樣嗎? ![](https://i.imgur.com/istsEWR.png) 覆蓋 `_IO_write_ptr`, `_IO_write_end` 的話,其前面的資料也會被覆蓋掉,write的來源就不明了。但這邊還是先遵照谷哥指示: ![](https://i.imgur.com/6dgMElu.png) 這邊卻發現好像是可控的區域!? 但是不知道這是哪部分 找一下 file structure, 也無法看出原因 ![](https://i.imgur.com/zG7nTCe.png) 實測幾次能改到的地方,發現是 payload 起頭的字串當作來源,於是修改 payload 如下: ![](https://i.imgur.com/bKl0nzN.png) ![](https://i.imgur.com/PwXvrg4.png) 成功塞成 one_gadget 了! 然而執行到這邊,會發現仍然無法過,看來是 one_gadget 用錯,條件不符合 ![](https://i.imgur.com/ONFsMgA.png) 事實上如果三個 gadget 都直接硬塞,仍然沒有一個能 work, 那麼來仔細檢視一下以上條件,在程式到 segmentation fault 時, regs 裡面有什麼是可控的 ![](https://i.imgur.com/88ZO0jX.png) 第一個 gadget 指定 r12, r15 址或值須為 null, 查看 regs, r15 已經有 null, r12 是一堆字串A, 這代表把先前用A覆蓋的方式改成用`\x00`覆蓋,基本上應該不影響前面exploit, 且可以滿足這裡的條件: ![](https://i.imgur.com/8qKu4Jm.png) 成功拿到 shell! ## remote buffer flush 如果直接把local測試好的程式跑在 remote 上,會直接卡住: ![](https://i.imgur.com/bFpftaO.png) 我的第一個做法是將 process.interactive()塞在程式中的故處,看到底是那裡卡了 ![](https://i.imgur.com/ytS8UAI.png) 重複測試,發現在要 leak libc 的部分時不會卡住,但也沒有照預期的把 libc leak 出來,而是直接 loop 下一次選項清單 ![](https://i.imgur.com/EELBhoZ.png) 說來難以相信,我沒有先想到 buffer flush 的問題,而是一邊想著到底是刪小一邊狂按3下去直到libc leak出來... ![](https://i.imgur.com/jGxuKw6.png) 後來想到上課提到,要將 buffer 內容輸出,1是掃到換行字元,2是buffer滿了會被flush, 既然我是重複 write() 直到 flush, 那就暴力的寫個回圈吧... ![](https://i.imgur.com/nlESw9h.png) ## catch the flag ![](https://i.imgur.com/oKUkYbJ.png)