学逆向论坛

找回密码
立即注册

只需一步,快速开始

发新帖

2万

积分

41

好友

1157

主题

[Pwn] 堆概述

发表于 2020-9-2 09:45:41 | 查看: 4158| 回复: 0

相关题目:

堆的基本操作malloc
在glibc的malloc.c中:
/*
  malloc(size_t n)
  Returns a pointer to a newly allocated chunk of at least n bytes, or null
  if no space is available. Additionally, on failure, errno is
  set to ENOMEM on ANSI C systems.
  If n is zero, malloc returns a minumum-sized chunk. (The minimum
  size is 16 bytes on most 32bit systems, and 24 or 32 bytes on 64bit
  systems.)  On most systems, size_t is an unsigned type, so calls
  with negative arguments are interpreted as requests for huge amounts
  of space, which will often fail. The maximum supported value of n
  differs across systems, but is in all cases less than the maximum
  representable value of a size_t.
  */

malloc 函数返回对应大小字节的内存块的指针。
  • 当 n=0 时,返回当前系统允许的堆的最小内存块。
  • 当 n 为负数时,由于在大多数系统上,size_t 是无符号数(这一点非常重要),所以程序就会申请很大的内存空间,但通常来说都会失败,因为系统没有那么多的内存可以分配。
free
/*
  free(void* p)
  Releases the chunk of memory pointed to by p, that had been previously
  allocated using malloc or a related routine such as realloc.
  It has no effect if p is null. It can have arbitrary (i.e., bad!)
  effects if p has already been freed.
  Unless disabled (using mallopt), freeing very large spaces will
  when possible, automatically trigger operations that give
  back unused memory to the system, thus reducing program footprint.
  */

free 函数会释放由 p 所指向的内存块。这个内存块有可能是通过 malloc 函数或者realloc函数得到的
  • 当 p 为空指针时,函数不执行任何操作。
  • 当 p 已经被释放之后,再次释放会出现乱七八糟的效果,这其实就是 double free。
  • 除了被禁用 (mallopt) 的情况下,当释放很大的内存空间时,程序会将这些内存空间还给系统,以便于减小程序所使用的内存空间。
内存分配后的系统调用
无论是 malloc 函数还是 free 函数,背后的系统调用主要是 (s)brk 函数以及 mmap, munmap 函数。

堆概述

堆概述
(s)brk
对于堆的操作,操作系统提供了 brk 函数,glibc 库提供了 sbrk 函数
们可以通过增加 brk 的大小来向操作系统申请内存。
初始时,堆的起始地址 start_brk 以及堆的当前末尾 brk 指向同一地址。根据是否开启 ASLR,两者的具体位置会有所不同
  • 不开启 ASLR 保护时,start_brk 以及 brk 会指向 data/bss 段的结尾。
  • 开启 ASLR 保护时,start_brk 以及 brk 也会指向同一位置,只是这个位置是在 data/bss 段结尾后的随机偏移处。
具体效果如下图:

堆概述

堆概述
例子
/* sbrk and brk example */
  #include 
  #include 
  #include 
  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前

堆概述

堆概述

堆概述

堆概述
在第一次调用brk后

堆概述

堆概述

堆概述

堆概述
  • 0x080ed000 是相应堆的起始地址
  • rw-p 表明堆具有可读可写权限,并且属于隐私数据。
  • 00000000 表明文件偏移,由于这部分内容并不是从文件中映射得到的,所以为 0。
  • 00:00 是主从 (Major/mirror) 的设备号,这部分内容也不是从文件中映射得到的,所以也都为 0。
  • 0 表示着 Inode 号。由于这部分内容并不是从文件中映射得到的,所以为 0。
mmap
malloc 会使用 mmap 来创建独立的匿名映射段。匿名映射的目的主要是可以申请以 0 填充的内存,并且这块内存仅被调用进程所使用。
例子:
/* Private anonymous mapping example using mmap syscall */
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  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;
  }

执行mmap之前:

堆概述

堆概述
mmap后:

堆概述

堆概述
好像没区别????
正常来说,应该会多一块
munmap后:
还是没区别,正常来说是刚刚的多的一块又没了
多线程支持
在原来的 dlmalloc 实现中,当两个线程同时要申请内存时,只有一个线程可以进入临界区申请内存,而另外一个线程则必须等待直到临界区中不再有线程。这是因为所有的线程共享一个堆。在 glibc 的 ptmalloc 实现中,比较好的一点就是支持了多线程的快速访问。在新的实现中,所有的线程共享多个堆。
/* Per thread arena example. */
  #include 
  #include 
  #include   #include 
  #include 
  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;
  }

第一次申请之前

堆概述

堆概述
第一次申请之后:

堆概述

堆概述
堆段被建立了,并且它就紧邻着数据段,这说明 malloc 的背后是用 brk 函数来实现的。同时,需要注意的是,我们虽然只是申请了 1000 个字节,但是我们却得到了 0x00623000-0x00602000=0x21000 个字节的堆。这说明虽然程序可能只是向操作系统申请很小的内存,但是为了方便,操作系统会把很大的内存分配给程序。这样的话,就避免了多次内核态与用户态的切换,提高了程序的效率。
我们称这一块连续的内存区域为 arena。此外,我们称由主线程申请的内存为 main_arena。后续的申请的内存会一直从这个 arena 中获取,直到空间不足。当 arena 空间不足时,它可以通过增加 brk 的方式来增加堆的空间。类似地,arena 也可以通过减小 brk 来缩小自己的空间。
在主线程free后:

堆概述

堆概述
其对应的 arena 并没有进行回收,而是交由 glibc 来进行管理。当后面程序再次申请内存时,在 glibc 中管理的内存充足的情况下,glibc 就会根据堆分配的算法来给程序分配相应的内存。
在第一个线程malloc后:
线程 1 的堆段被建立了。而且它所在的位置为内存映射段区域,同样大小也是 132KB
同时,我们可以看出实际真的分配给程序的内存为 1M。而且,只有 132KB 的部分具有可读可写权限,这一块连续的区域成为 thread arena。
在第一个线程free后:
这样释放内存同样不会把内存重新给系统。


温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
论坛交流群:672619046

小黑屋|手机版|站务邮箱|学逆向论坛 ( 粤ICP备2021023307号 )|网站地图

GMT+8, 2024-4-26 06:00 , Processed in 0.102765 second(s), 42 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表