堆溢出学习

堆溢出学习

一月 05, 2020

文章内容将会持续更新

0x0 what’s heap?

在我们平时的编程中,可能会需要使用更多的内存,这个时候就需要堆上场了。当我们使用了malloc分配内存之后,我们需要去释放掉内存。

我们在讨论malloc之前,先来了解一下进程获得内存空间的方式吧~

主要是通过两种方式:mmapbrk的方式,这些都是系统调用,意味着我们直接去请求了内核。

image-20200105114640175

mmap请求内核给我们了一些新的虚拟地址空间,基本上请求的是一个新的内存段。

image-20200105114853953

brk可以用来改变已经使用内存段的大小,

如果进程有自己的内存段,它就不关心这些内存是怎么实现的。

那么将在什么地方存储他们呢?或者你没有足够的内存空间的时候,你将会发现一个swap文件,进程都不会在意这些东西是怎么实现的。它只是通过内核和硬件进行处理,并且将内存映射到进程中去。这就意味着进程可以很“透明的”访问这些内存地址并且使用。

这里的透明指的是进程不需要知道内核和硬件的处理过程,就想空气一样,看不到摸不着

我们可以使用strace ./heap1查看程序的系统调用情况,你将会看到进程是如何调用mmap去初始化内存区域的。

并且在最后,进程使用brk来设置堆空间

image-20200105115900200

image-20200105115924864

那么我们为什么不直接使用mmap或者brk为我们的进程获取更多的内存而还要使用malloc呢?

为什么我们在谈论到堆的时候,都会提到mallocfree呢?

malloc只是一个函数封装,用来隐蔽brk或者mmap的操作,让代码看起来更简洁并且使用更方便一点

我们可以去glibc/malloc/malloc.c中找到

image-20200105142321897

如果堆不存在或者太小,malloc将调用mmap或者brk去获取更多的内存。但最重要的是他会帮我们组织和管理内存。

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 <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>



struct internet {
int priority;
char *name;
};

void winner()
{
printf("and we have a winner @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
struct internet *i1, *i2, *i3;

i1 = malloc(sizeof(struct internet));
i1->priority = 1;
i1->name = malloc(8);

i2 = malloc(sizeof(struct internet));
i2->priority = 2;
i2->name = malloc(8);

strcpy(i1->name, argv[1]);
strcpy(i2->name, argv[2]);

printf("and that's a wrap folks!\n");
}

我们先来看这样一个代码,目标函数是winner(),我们需要重定向代码执行到这个函数,在最上面有一个结构体internet,里面有一个int类型的成员变量叫priority,还有一个成员变量是一个char类型的指针叫做name,这意味着name是一个指向某一个位置的字符串指针。

main()函数中定义了三个internet结构体类型的指针变量,但是只有*i1, *i2被使用了。在这里强调internet指针是因为这三个internet对象没有存储在堆栈上,只有地址,存储在*i1, *i2,*i3这几个指针中。

总之,它将开始在堆上为这些对象分配内存空间,下面首先调用了malloci1对象分配足够的内存空间,sizeof()将返回这个结构所需要的字节数。这里就是8个字节,因为int占了4个字节,char*也是四个字节。我们知道malloc返回堆上的地址,我们就可以去使用。所以i1指向内存中8个字节的开始处。现在i1->priority = 1;我们将priority设置为1,这将把1写入到我们分配的第一个4字节空间。 i1->name = malloc(8);然后分配另外的8个字节的内存空间,然后将这个内存空间的地址存储到名为namechar指针中。char指针的位置是在i1对象的偏移地址上+4

i2也是做了同样的操作。

我们看到下面有两次strcpy,我们知道strcpy总是很可疑,因为这里没有看到所要拷贝的字符串长度标识在哪里。那么我们写入的数据可能就会超过我们已经拥有的空间的大小,造成溢出。

internetname成员只有8字节的空间,因此如果真的可以写入超过8个字节的数据,它将会弄乱堆上的数据。总之,strcpy将拷贝argv[1]中的数据到i1对象中的name成员中。i1指向i1这个对象的起始位置,并且偏移4个字节后就是name的起始位置,这里面保存的地址就是为字符串分配的8字节空间的地址。所以strcpy将地址存储在那里,并且从argv[1]中拷贝字符串过去。同样i2argv[2]也是一样的步骤。他会将地址存储在i2+4的地址,并且尝试在那里复制我们的字符串。

之后使用printf打印并且退出。

0x1 编译带debug symbol的libc

  • 下载:https://ftp.gnu.org/gnu/glibc/glibc-2.19.tar.gz
  • 解压
  • mkdir build
  • cd build
  • CFLAHS="-g -g3 -ggdb -gdwarf-4 -Og" CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og" ../configure --prefix=/home/nick/glibc-2.19/64
  • make -j8

0x2 Glibc Heap Overview

  • 是libc中负责动态维护内存的一个结构
  • libc中比较常用到的就是malloc,free,realloc,在C++中常用到的就是new,delete

0x2.1 Heap相关的漏洞利用

  • Use after free
  • Double free
  • Heap overflow

0x2.2 Glibc Heap

  • https://code.woboq.org/userspace/glibc/malloc/malloc.c.html
  • 要做到内存的管理,我们需要知道:
    • 有哪些位置的内存是可以被分配的
    • 有哪些是因为free掉而可以回收的
    • 有哪些位置是使用中则不需要记录,使用它们的人应该记住这些标志

真的会去做malloc的是_int_malloc

image-20200121142823500

但是平时我们去call malloc__libc_malloc

image-20200121142923761

他里面也是call _int_malloc,下断点也可以下在这里

image-20200121143048543

free的话和malloc差不多

  • 整个Heap的东西记录在一个struct malloc_state中,它叫做main_arena

    image-20200121143623638

  • malloc分配的内存名为chunk,会比要求的大小要大一点,因为需要记录一些维护heap用的额外的信息

  • Arena和heap分配的内存是分开存放的,heap overflow没办法直接去覆盖掉他的内容

0x3 Heap在malloc的时候会发生什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>

void *Malloc(size_t sz){
void *p = malloc(sz);
printf("%p = malloc(%ld)\n",p,sz);
}

void Free(void *p){
printf("free(%p)\n",p);
}

int main(){
void *p,*q,*r,*s;
p = malloc(150);
q = malloc(150);
r = malloc(150);
s = malloc(150);
free(p);
free(r);
}

然后export LD_LIBRARY_PATH=/home/nick/glibc-2.19/64/lib
使用ldd去查看是否成功
使用gdb去调试

1
2
3
4
5
6
7
8
9
10
11
12
13
gdb-peda$ p main_arena
$1 = {
mutex = 0x0,
flags = 0x0,
fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
top = 0x0,
last_remainder = 0x0,
bins = {0x0 <repeats 254 times>},
binmap = {0x0, 0x0, 0x0, 0x0},
next = 0x7ffff7dd7620 <main_arena>,
next_free = 0x0,
system_mem = 0x0,
max_system_mem = 0x0

刚刚我们看的是malloc,那么free的时候呢,会发生什么?

  • 回收的chunklinked list记录,成为bin

  • main_arena中有很多个bin,每个bin里面存储的chunk size不同,目的是让malloc时可以更快找到最合适大小的chunk

  • 回收的chunk会按照size来决定应该放进哪个linked list(bin)

    1
    2
    3
    4
    5
    main_arena{
    bin[0] (size=16) -> chunk1 -> chunk5
    bin[0] (size=32) -> chunk2 -> chunk3 -> chunk4
    bin[0] (size=48)
    }