heap pwn 入门大全 - 2:glibc heap机制与源码阅读(下)

这篇具有很好参考价值的文章主要介绍了heap pwn 入门大全 - 2:glibc heap机制与源码阅读(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文对glibc堆管理器的各项主要内存操作,以及glibc 2.26后引入的tcache机制进行源码级分析,可作为查找使用。

glibc memory operations

  • 第一次malloc,会初始分配一个0x290的chunk,top chunk split返回给user后,剩余部分继续作为top chunk
  • 通常heap的第一个chunk,prev_inuse都为1,防止非法内存访问

unlink

  • 将双向链表中的一个chunk取出,也就是链表中的删除元素
  • 维护fd, bk链表,若为large bin,则同时维护nextsize链表
  • 可能的泄漏点:不会修改两个链表的指针,可能会造成chunk地址泄漏
    • libc地址(通过main_arena

      • Chunk 为head chunk,bk泄漏bins地址
      • chunk 为tail chunk,fd泄漏bins地址
    • 泄漏堆地址(中间的chunk都可)

static void
unlink_chunk (mstate av, mchunkptr p)
{
  // check chunk size in 2 positions
  if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");

  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;

  // check double-linked list
  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");

  // unlink
  fd->bk = bk;
  bk->fd = fd;
  if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    {		// 维护nextsize双向链表,仅有large bin使用
      if (p->fd_nextsize->bk_nextsize != p
	  || p->bk_nextsize->fd_nextsize != p)
	malloc_printerr ("corrupted double-linked list (not small)");

      if (fd->fd_nextsize == NULL)	
	{			// 下一个块不在nextsize链表中,即size与p相同
	  if (p->fd_nextsize == p) // nextsize链表仅有一个元素
	    fd->fd_nextsize = fd->bk_nextsize = fd;	// 重新构建,将下一个块作为nextsize链表
	  else
	    {		// nextsize链表不只一个元素,将fd插入nextsize链表(代替当前size)
	      fd->fd_nextsize = p->fd_nextsize;
	      fd->bk_nextsize = p->bk_nextsize;
	      p->fd_nextsize->bk_nextsize = fd;
	      p->bk_nextsize->fd_nextsize = fd;
	    }
	}
      else
	{ // 下一个块也在链表中,直接进行元素删除即可
	  p->fd_nextsize->bk_nextsize = p->bk_nextsize;
	  p->bk_nextsize->fd_nextsize = p->fd_nextsize;
	}
    }
}

malloc

handler entry: __libc_malloc, finally invokes _int_malloc

__libc_malloc
  1. 检查memory hook,若有,则直接执行
    • 初始化时,将memory_hook设为malloc_hook_ini,执行ptmalloc_init
  2. 若配置tcache,使用tache
  3. 若程序为单线程,则直接调用_int_malloc(main_arena, size)进行内存分配,并直接返回
  4. arena_get获取可用arena,inpage ref
  5. 调用_int_malloc进行实际malloc调用
_int_mallloc
  • 调整request size到nb(+SIZE_SZ,只需要一个,因为数据可以存储在下一个chunk的top SIZE_SZ bytes)

    • 如果没有可用的arena,fallback到sysmalloc,使用mmap来分配chunk
  • 如果size满足fast bin,则直接到fast bin进行分配,有结果就直接返回

  • 若在small bin 范围内,则尝试small bin,有结果就直接返回

  • 否则调用large bin,只计算index,不直接搜索结果

  • 在unsorted bin中遍历,从后(bk)往前 (av)

    • 对于每一个unsorted bin中的chunk,检查其next块的属性(size)等是否与其匹配,检查fd和bk双向链表是否损坏
    • 如果请求的是一个small bin,并且当前unsorted bin中只有一个last remainder chunk,就直接使用它。这将有助于提升在频繁malloc 小块时的效率。这是唯一没有使用best fit策略的地方,并且只有在没有合适的small chunk能够匹配当前请求时才会使用。
    • 将chunk移出 unsorted bin,即使没有被直接使用,也会被放回对应的bin中
    • 如果大小匹配,则直接使用该块,不放回
    • 否则将该块放回bin
      • 若为small bin,直接找到位置
      • 若为large bin,则还需要保持其有序,还需要另行处理nextsize双链表
  • 遍历结束,所有unsorted bin被清空,若没有刚好匹配上的chunk,则进行下一步

  • 若request为large bin

    • 若在当前bin 中找到第一个合适大小的chunk,将其移出队列(若有多个符合,取第二个,否则会破坏nextsize链表)
    • 剩余部分不足MIN_SIZE,则使用整块
    • 否则进行split,剩余部分组成一个remainder,加入unsorted bin头部,并设置chunk 内容
    • 否则基于bitmap寻找下一个非空bin
      • 注意bitmap延迟响应,偏否的蒙特卡洛,只有为0时才一定为空
      • 所以检验得到false alarm后,需要更新bit
  • 否则最后一步,使用 top chunk use_top

    • 若top chunk不足,则会进行fast bin碎片整理consolidate
    • 若没有fast bin,则调用sysmalloc进行堆扩容
    • 以上两种例外情况结束后,都将返回上面重新检查bin等
  • 通用

    • 当一个chunk被选中使用,会执行以下操作
      • 对应位置位(inuse,main_arena)
      • 操作tcache,优先fill tcache,若已满则返回给用户
      • check_malloced_chunk 为debug预留接口,默认不开启
      • chunk2mem将chunk地址转换为user data首地址(加上header偏移0x20
ptmalloc_init
  • 第一次运行malloc时,作为memory hook内容(malloc_hook_ini)被调用,所有线程和起来只用一次
  • 所有线程的main_arena相同,都指向主线程的那个
  • 初始化main_arenamalloc_init_state,主要初始化bins链表头,设置top为unsorted bin(假top)
arena_get

获取一个可用arena,并上锁。如果没有找到可用的arena,则新建一个arena_get2

/* arena_get() acquires an arena and locks the corresponding mutex.
   First, try the one last locked successfully by this thread.  (This
   is the common case and handled with a macro for speed.)  Then, loop
   once over the circularly linked list of arenas.  If no arena is
   readily available, create a new one.  In this latter case, `size'
   is just a hint as to how much memory will be required immediately
   in the new arena. */

#define arena_get(ptr, size) do { \
      ptr = thread_arena;						      \
      arena_lock (ptr, size);						      \
  } while (0)

#define arena_lock(ptr, size) do {					      \
      if (ptr)								      \
        __libc_lock_lock (ptr->mutex);					      \
      else								      \
        ptr = arena_get2 ((size), NULL);				      \
  } while (0)

Arena_get2:

  • 若当前存在空闲arena in free_list,则直接返回
  • 若当前无空闲,并且当前arena数量没有超过限制,则新建一个arena - _int_new_arena
  • 否则复用一个现有的arena

_int_new_arena

  • new_heap 调用mmap来分配arena空间,top_pad由multi thread配置,默认0x20000,用于指定top chunk的padding
    • 前部size包含heap info以及arena 结构体(mstate),经过align up后对其到页(后面需要进行mmap操作),共0x21000
    • mmap内存区间,映射size 为HEAP_MAX_SIZE,为DEFAULT_MMAP_THRESHOLD_MAX*2 = 64MB,初始不赋予权限
      • 需要对齐到HEAP_MAX_SIZE,所以一开始mmap 2倍空间,并使用中间经过对齐的一段(类似align up),如果一开始mmap的2倍空间已经对齐,在下次申请新arena时,会使用原来剩余的部分,减少heap对内存空间的碎片化
    • 对申请部分(包括align_top)进行mprotect,赋予读写权限
    • 初始化heap_info其他信息
  • 将arena放置在heap_info之后,并设置参数
  • 将top chunk 放置在arena之后,并设置参数
Offset Name Type Size
0x0 Heap info Heap_info 0x20
0x20 arena Malloc_state 0x898
0x8c0 (aligned) Top chunk malloc_chunk
sysmalloc
  1. 若request size过大(>= mp_.mmap_threshold (0x20000)),并且当前mmap块数量少于上限,则直接使用mmap为request分配,而不是扩展top
    • mmap分配的空间对齐到page size
    • 由于不能使用下一个chunk的prev_size位置,所以当前chunk存在size_sz的overhead
    • 同样将mmap内容作为chunk,需要初始化prev,以及size
  2. 正常拓展top
    • 首先记录初始top信息,并检查top情况
    • 若为main_arena
      • 为top留出足够空间(top_pad
      • 调用MORECORE来获取更多内存,系统中使用_sbrk
        • (若存在)调用hook
        • 若brk失败,则尝试mmap
          • 使用mmap后,sbrk region为非连续段
      • 若sbrk为拓展已有的top,简单拓展top即可
      • 否则对堆做出调整
        • 若为contiguous,且已有旧堆(比较奇怪),则需要增加旧堆的大小(增加到old_sz + size,即拓展新需求的空间),且记录新的brk位置到snd_brk
        • 若非contiguous,需要建立fencepost
        • 建立新的top chunk,并将原top chunk free掉
    • 若非 main_arena
      • 尝试使用mmap来拓展已有堆grow_heap,heap最大不超过HEAP_MAX_SIZE
      • 若失败,建立新堆 new_heap
      • 将旧有的top chunk free掉,设置fence post
      • 将heap的arena等属性设置好,将新开辟的空间初始化为top chunk
  3. 完成拓展,将top chunk split,并返回给user
其他封装函数
  • calloc返回n个元素和指定大小的内存,全部初始化为0

free

entry: __libc_free

libc_free
  1. 检查hook (通常没有)
  2. 将pointer转换为chunk ptr
  3. 若为mmap chunk (检查mmaped bit - chunk_is_mmaped)
    1. 调整mmap threshold,后续大小更小的(不超过最大阈值)的request不会触发mmap
    2. __munmap解除块映射
  4. 调用_int_free
_int_free
  1. 检查chunk各项参数
  2. 处理tcache
  3. 检查fast bin
    • 将chunk 插入bin头部,若非单线程,则需要使用原子操作
  4. 检查非mmapped chunk
    1. 尝试合并前面的chunk(如果!prev_inuse)
      • 若可以合并,则unlink前面的chunk
    2. 尝试合并后面的chunk
      • 若后面为top chunk,则直接修改当前chunk参数,将其合并,直接结束
        • 否则,若后面chunk正在使用,则将其prev_inuse清空,否则同样unlink
    3. 将可能合并后的chunk重新设置头,并将其插入unsorted bin头部(仅需要维护fd/bk链表即可,若为large chunk,还需要清空next_size位置)
  5. 如果目前free的size(合并后)大于FASTBIN_CONSOLIDATION_THRESHOLD,则进行consolidate
    • 首先调用malloc_consolidate进行整理
    • 若为main arena,则使用systrim缩小arena体积,缩小量为top pad
    • 否则使用heap_trim缩小mmap堆
malloc_consolidate
  • 对所有fast bin中的bin进行遍历
    • 对每一个bin中的chunk进行遍历
      • 合并前后chunk,与_int_free中第4步(检查非mmapped chunk)完全相同
systrim
  • 只在当前top chunk末尾与current_brk相同时使用,避免外部sbrk对外部数据的影响
  • 调用MORECORE(-extra)将多余部分裁剪
  • update top chunk and heap
heap_trim
  • 如果当前heap仅剩top chunk,说明整个heap都可以被删除
    • 清除上一个heap的fencepost,检查后将上一个heap的大小拓宽fencepost大小
    • 删除当前heap
    • consolidate previous chunk
    • 更新 top
  • 尝试收缩当前heap
    • shrink_heap将多余部分重新映射为不可访问区域,用于节约内存

Other Operations

realloc

重新分配该内存块,提供两个参数oldmem和size,分别制定原内存块指针地址和需要修改的大小

  1. 若oldmem为空,直接转malloc,若size为0并且oldmem不为空,直接转free
  2. 进行相关安全检查,如wrap around,size等
  3. 若为mmaped chunk
    • 尝试mremap (linux syscall),成功即返回mremap_chunk
    • 否则malloc,并进行memcpy,复制内存内容
  4. 否则
    • 若为单线程,则直接调用_int_realloc
    • 若为多线程,则尝试在该arena上进行_int_realloc,失败则在其他arena上尝试malloc

_int_realloc

  • check size
  • if new_size < old_size, 直接选择该块待用
  • 若下一个chunk是top chunk,则缩减top chunk 直接使用,直接return
  • 若下一个chunk 空闲且空间足够,直接使用下一个chunk,先unlink
  • 否则,allocate, copy, free
  • 若当前选择的chunk(该块,或与下一个合并块)较大,则需要split

tcache

tcache是glibc 2.26后引入技术,用于提升堆小块管理性能,但是缺乏检查,存在较多漏洞

structure

tcache引入两个新结构体tcache_entry, tcache_perthread_struct

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key;
} tcache_entry;

/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
  uint16_t counts[TCACHE_MAX_BINS];		// count for entries
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
  • tcache_entry用于指向下一个chunk的user data部分
  • tcache_entry放在chunk的user data部分,并且会复用该部分(tcache_entry结构体)

工作原理

  • 第一次 malloc 时,会先 malloc 一块内存用来存放 tcache_perthread_struct - MAYBE_INIT_TCACHE
  • free 内存,且 size 小于 small bin maxsize 时
    • tcache 之前会放到 fastbin 或者 unsorted bin 中
    • 在tcache出现后:
      • 先放到对应的 tcache 中,直到 tcache 被填满(mp_.tcache_count, 默认是 7 个)
      • tcache 被填满之后,再次 free 的内存和之前一样被放到 fastbin 或者 unsorted bin 中
      • tcache 中的 chunk 不会合并(不取消 inuse bit,fastbin也不会
  • malloc 内存,且 size 在 tcache 范围内
  • 先从 tcache 取 chunk,直到 tcache 为空
  • tcache 为空后,从 bin 中找
  • tcache 为空时,如果 fastbin/smallbin/unsorted bin 中有 size 符合的 chunk,会先把 fastbin/smallbin/unsorted bin 中的 chunk 放到 tcache 中,直到填满。之后再从 tcache 中取;因此 chunk 在 bin 中和 tcache 中的顺序会反过来

Source Code

MAYBE_INIT_TCACHE

tcache_init()初始化tcache

  1. 获取一个可用arena,并malloc tcache_perthread_struct
  2. 设置tcache变量,并memset清空区域
free

in _int_free

  1. 在user data部分建立一个entry
  2. 检查tcache double free
  3. tcache_put 将chunk 放入tcache

tcache_put文章来源地址https://www.toymoban.com/news/detail-647455.html

  • 将chunk插入tcache单链表
  • 设置entry的key为tcache,以便检查double free
malloc
  • 若对应位置(tcidx)count不为0,则可以使用tcache (可在bins命令中查看)
  • 使用FIFO机制,同样从头部取一个块返还给user
  • 由于entry直接为user data头部指针,所以直接返还给用户空间

到了这里,关于heap pwn 入门大全 - 2:glibc heap机制与源码阅读(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • GDOU-CTF-2023新生赛Pwn题解与反思

    因为昨天学校那边要进行天梯模拟赛,所以被拉过去了。 16点30分结束,就跑回来宿舍开始写。 第一题和第二题一下子getshell,不用30分钟,可能我没想那么多,对比网上的WP,自己和他们有点不太一样,比较暴力。 大概17点10的时候,写第三题,可能自己第一次遇到随机数问

    2023年04月17日
    浏览(57)
  • PWN学习之格式化字符串及CTF常见利用手法

    格式化字符串漏洞是一种常见的安全漏洞类型。它利用了程序中对格式化字符串的处理不当,导致可以读取和修改内存中的任意数据。 格式化字符串漏洞通常发生在使用 C 或类似语言编写的程序中,其中  printf 、 sprintf 、 fprintf  等函数用于将数据格式化为字符串并进行输出

    2024年02月19日
    浏览(38)
  • PWN保护机制以及编译方法

    Ctf中的pwn题,在利用gcc编译的时候,保护是如何开启的,如何编译出来的,保护都有什么由于在ctf中,大部分都是linux pwn,Windows pwn很少见,所以我这里以linux pwn来举例。 在pwn里,保护一共是四种分别是RELRO、Stack、NX、PIE。 1.RELRO(ReLocation Read-Only):分为两种情况,第一种情

    2023年04月09日
    浏览(43)
  • 1、怎么阅读linux内核源码

    : 了解C语言和操作系统的基本概念对于理解内核代码至关重要。如果对C语言和操作系统的基础知识感到陌生,可以先学习相关的教材或课程。 Linux内核非常庞大,涵盖了多个子系统和功能。因此,选择您感兴趣的子系统或功能模块,有助于更有针对性地学习和研究相关的代

    2024年02月15日
    浏览(40)
  • linux常用命令大全(保姆及入门)

    命令格式: 命令 [-选项] [参数]    例:  ls -a /etc 说明:   1)个别命令使用不遵循此格式   2)当有多个选项时,可以写在一起   3)简化选项与完整选项     -a 等于 –all 命令名称:ls 命令英文原意:list 命令所在路径:/bin/ls 执行权限:所有用户 功能描述

    2024年02月02日
    浏览(50)
  • Linux常用命令大全(Linux初学者快速入门)

      本文旨在为Linux初学者提供一份Linux常用命令总结。我将介绍一系列常用的命令及其用法,包括文件和目录相关操作、系统管理、进程控制、网络操作等方面。通过学习这些命令,读者将能够更好地管理和操作Linux系统,提高工作效率。   在本文中,我将详细讲解每个命

    2024年02月11日
    浏览(46)
  • CentOS7.9中的Glibc2.17源码编译升级到Glibc2.31

    查看yum当前配置的仓库,如果yum配置的不是阿里云源,请配置阿里云源。 验证是否能ping通阿里云 备份官方的原yum源配置 下载Centos-7.repo文件 清除及生成缓存 ‍ ‍ 注意: 如果make编译完有错误,一定不要执行make install安装操作,有可能会把系统搞崩命令失效的情况。出现错

    2024年02月12日
    浏览(37)
  • MySQL安装:glibc版本与源码安装

    1、二进制软件包安装 第一种是yum安装或者rpm安装 第二种是glibc版本的安装 2、源码包编译安装 通用的Linux下都可以编译安装 1、什么是MySQL数据库? 数据库(database): 操作系统或存储上的 数据文件的集合 。mysql数据库中,数据库文件可以是*.frm、.MYD、.MYI、*.ibd等结尾的文件

    2024年02月03日
    浏览(35)
  • CTFshow-pwn入门-前置基础pwn32-pwn34

    FORTIFY_SOURCE(源码增强),这个其实有点类似与Windows中用新版Visual Studio进行开发的时候,当你用一些危险函数比如strcpy、sprintf、strcat,编译器会提示你用xx_s加强版函数。 FORTIFY_SOURCE本质上一种检查和替换机制,对GCC和glibc的一个安全补丁。 目前支持memcpy, memmove, memset, strcpy, s

    2024年02月09日
    浏览(33)
  • 【Linux从入门到精通】gdb调式技巧大全

         本篇文章会对开发工具:gdb调试器的使用进行讲解。希望本篇文章会对你有所帮助。 目录 一、gdb简单介绍  二、gdb 调试 2、1 加入调试信息 2、2 调试命令 2、2、1 gdb+可执行程序——进入调式 2、2、2 l+行号/函数名——显示代码 2、2、3 b+行号/函数名——打断点 2、2、4 

    2024年02月15日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包