ctfwiki笔记--Linux堆基础

参考链接:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/heap_overview/#_5

1、Linux在内存分配与使用的过程中,核心思想是:只有当真正访问一个地址的时候,系统才会建立虚拟页面与物理页面的映射关系。所以虽然操作系统已经给程序分配了很大的内存空间,但是这块内存其实只是虚拟内存。只有当用户使用相应的内存时,系统才会真正分配物理页面给用户使用。

2、Linux标准发行版使用的堆分配器是glibc中的堆分配器:ptmalloc2.ptmalloc2主要是通过malloc/free函数来分配和释放内存。

3、堆管理器:(1)响应用户请求,向操作系统申请内存,然后将其返回给用户。
(2)管理用户释放的内存

4、malloc(size_t n)返回指向n大小的内存的指针。
当n=0时,返回当前系统允许堆的最小的内存块;
当n<0时,因为size_t是无符号整数,因此意味着分配很大的内存空间,这将导致失败。因为系统没有那么大空间进行分配。

5、free(void * p)释放p指向的内存块。
当p=null,程序不执行任何操作;
当p已经被释放过,则会出现乱七八糟的错误,就是double free;
除非被禁用时(mallopt),当释放很大的内存空间时,程序将内存块还给系统,以便减少系统所使用的内存空间。

6、我们动态申请或释放内存时,会使用malloc和free函数,但他们并不是真正与系统交互的函数。真的的函数是(s)brk、mmap、munmap函数。
如下图所示,我们主要考虑申请内存分配的操作:
在这里插入图片描述
(s)brk函数:OS提供了brk函数,GLIBC提供了sbrk函数。可通过增加brk的大小向OS申请内存。
初始时,堆的起始位置start_brk和末尾位置brk指向同一位置。根据ASLR是否开启可能有所不同,
(1)ASLR关闭,start_brk/brk指向data/bss段的末尾。
(2)ASLR开启,start_brk/brk指向data/bss段的末尾的随机偏移位置。

7、例子

/* sbrk and brk example */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
        void *curr_brk, *tmp_brk = NULL;

        printf("Welcome to sbrk example:%d\n", getpid());

        /* sbrk(0) gives current program break location */
        tmp_brk = curr_brk = sbrk(0);
        printf("Program Break Location1:%p\n", curr_brk);
        getchar();

        /* brk(addr) increments/decrements program break location */
        brk(curr_brk+4096);

        curr_brk = sbrk(0);
        printf("Program break Location2:%p\n", curr_brk);
        getchar();

        brk(tmp_brk);

        curr_brk = sbrk(0);
        printf("Program Break Location3:%p\n", curr_brk);
        getchar();

        return 0;
}

运行程序:
在第一次调用brk之前:
发现此时堆段的末尾就是0x0806c000。即sbrk(0)=0x0806c000
在这里插入图片描述
在这里插入图片描述

在调用brk之后:
start_brk = 0x806c000
brk = 0x806d000
在这里插入图片描述
内存映射和上图一样。

这和ctfwiki中不一样……
为啥?
brk()和sbrk()这两个函数又是干啥的?

8、malloc()利用mmap创建独立的匿名映射段。匿名映射的主要目的是创建用0填充的内存,并且这块内存仅被调用进程所使用。
例子:

/* Private anonymous mapping example using mmap syscall */
##include <stdio.h>
##include <sys/mman.h>
##include <sys/types.h>
##include <sys/stat.h>
##include <fcntl.h>
##include <unistd.h>
##include <stdlib.h>

void static inline errExit(const char* msg)
{
        printf("%s failed. Exiting the process\n", msg);
        exit(-1);
}

int main()
{
        int ret = -1;
        printf("Welcome to private anonymous mapping example::PID:%d\n", getpid());
        printf("Before mmap\n");
        getchar();
        char* addr = NULL;
        addr = mmap(NULL, (size_t)132*1024, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        if (addr == MAP_FAILED)
                errExit("mmap");
        printf("After mmap\n");
        getchar();

        /* Unmap mapped region. */
        ret = munmap(addr, (size_t)132*1024);
        if(ret == -1)
                errExit("munmap");
        printf("After munmap\n");
        getchar();
        return 0;
}

(1)在执行mmap之前:
.so文件的mmap段为:0xf7dfe000-0xf7dff000
在这里插入图片描述
(2)mmap之后:
申请的内存与存在的内存,共同构成0xf7ddd000-0xf7dff000
在这里插入图片描述
(3)munmmap之后:
申请的内存段又没有了,内存又恢复到原来的样子
0xf7dfe000-0xf7dff000
在这里插入图片描述
关键点:
问:mmap()是干啥的??
munmmap()又是干啥的??

答:
参考链接:https://blog.csdn.net/l534145398/article/details/53283808

9、多线程支持
在原来的dlmalloc实现中,当两个线程同时要申请内存时,只有一个线程可以进入临界区申请内存,而另一个线程必须等待直到临界区中不再有线程。这是因为所有的线程共享一个堆。
在glibc的ptmalloc实现中,比较好的一点就是支持了多线程的快速访问。这样所有的线程共享多个堆。

例子:

/* Per thread arena example. */
##include <stdio.h>
##include <stdlib.h>
##include <pthread.h>
##include <unistd.h>
##include <sys/types.h>

void* threadFunc(void* arg) {
        printf("Before malloc in thread 1\n");
        getchar();
        char* addr = (char*) malloc(1000);
        printf("After malloc and before free in thread 1\n");
        getchar();
        free(addr);
        printf("After free in thread 1\n");
        getchar();
}

int main() {
        pthread_t t1;
        void* s;
        int ret;
        char* addr;

        printf("Welcome to per thread arena example::%d\n",getpid());
        printf("Before malloc in main thread\n");
        getchar();
        addr = (char*) malloc(1000);
        printf("After malloc and before free in main thread\n");
        getchar();
        free(addr);
        printf("After free in main thread\n");
        getchar();
        ret = pthread_create(&t1, NULL, threadFunc, NULL);
        if(ret)
        {
                printf("Thread creation error\n");
                return -1;
        }
        ret = pthread_join(t1, &s);
        if(ret)
        {
                printf("Thread join error\n");
                return -1;
        }
        return 0;
}

1)第一次申请之前和之后:
(1)可以看出堆段紧跟着数据段,说明malloc后面是用brk函数实现的。
(2)同时,虽然我们申请了1000字节的内存,但是分配了0x0806c000-0x0804b000=0x21000字节的堆。
这说明虽然程序向OS申请了小的内存,但是OS会把大的内存分给程序。这样的话避免了多次内核态与用户态的切换,提高了效率。我们称这一连续的内存为arena。此外,我们称由主线程申请的为main_arena。后续的申请内存会从这个arena中获取,直到空间不足。当arena大小不足时,可通过增加brk的方式增加arena的空间。类似的,arena可通过减少brk缩小它的空间。
在这里插入图片描述
2)主线程释放之后:
内存分布同上。说明对应的arena并没有被回收,而是交由glibc来管理。当后面程序再次申请内存时,在glibc管理内存充足的情况下,glibc会根据堆分配算法来给程序分配相应的内存。[why???不懂?从哪儿看出交给glibc管理了???]

3)在第一个线程malloc之前:
我们发现内存映射变了。并没有出现与内存映射相关的堆,而是出现了内存映射相关的栈。
(为什么就说明是栈了?因为ctfwiki中有【stack:pid】字段,我这儿没有……)

4)在线程malloc之后:
发现线程1的堆栈建立了,而它所在位置是内存映射位置。大小为0xf7421000-0xf74000=0x21000(132kb)。这说明线程申请堆时背后的函数是mmap函数。同时,实际真的分配给程序的内存大小为0xf7500000-0xf7400000=0x1000000,但只有132kb具有可读可写可执行权限。这一连续的内存区域为thread_arena

注意:当用户请求的内存大于128kb,并且没有任何arena满足要求时,系统会调用mmap函数分配相应的内存空间。这与请求来自主近程还是线程无关。

在这里插入图片描述
5)线程释放内存后:

同样不会把内存还给系统。

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值