哈喽啊

这里是二进制安全之堆溢出(系列)第二期“堆基础 & 结构”第二节!!

话不多说,直接上干货!

微观结构

函数执行流程

void *malloc (size_t bytes)
void *__libc_malloc (size_t bytes) //对于_int_malloc做简单封装__malloc_hook  //类似于虚函数,派生接口,指定一个malloc的方式
_int_malloc(mstate av, size_t bytes) //申请内存块的核心

main_arena

  • 集中管理bins链的结构体,使用含fd和bk的bin头一对一管理各个free的chunk
  • 分释放配堆块是基于main_arena来寻址的,首先找的是fastbin,其次再找bins
  • main_arena存储在libc上,用以管理所有bins的头和尾,每个bins链头的fd和尾的b与之连接
struct malloc_state
{/* Serialize access. */__libc_lock_define (, mutex);//定义了一个0x4字节的lock/* Flags (formerly in max_fast). */int flags;//0x4/* Set if the fastbin chunks contain recently inserted free blocks. *//* Note this is a bool but not all targets support atomics on booleans. */int have_fastchunks;//0x4/* Fastbins */mfastbinptr fastbinsY[NFASTBINS]; //fastbin链的管理头,总共10个, 每个0x10字节/* Base of the topmost chunk -- not otherwise kept in a bin */mchunkptr top;//0x4 到此为止总共0x96字节/* The remainder from the most recent split of a small request */mchunkptr last_remainder;        //切割后剩下的chunk链接到last_remainder/* Normal bins packed as described above */mchunkptr bins[NBINS * 2 - 2];   // 每个bin头有fd和bk两个指针/* Bitmap of bins */unsigned int binmap[BINMAPSIZE];   //位图,用32bit来分别表示当前bin哪个链上有chunk,通过按位与的方式/* Linked list */struct malloc_state *next;/* Linked list for free arenas. Access to this field is serializedby free_list_lock in arena.c. */struct malloc_state *next_free;/* Number of threads attached to this arena. 0 if the arena is onthe free list. Access to this field is serialized byfree_list_lock in arena.c. */INTERNAL_SIZE_T attached_threads;/* Memory allocated from the system in this arena. */INTERNAL_SIZE_T system_mem;INTERNAL_SIZE_T max_system_mem;
}

  • main_arena:用来管理整个bin链的结构体,总共128个bin,10个fastbin
  • 每个bin头可以简化为fd和bk两个前后项指针
  • glibc ---> main_arena ---> 对应的bins头的fd和bk ---> 遍历找到对应free的chunk
  • main_arena存放在libc中,其中存放的是每一个bin链的头尾

tips:如果我们能打印一个非fastbin链中的fd,bk,那我们就可以计算出libc的基地址libc.addr = libc_on - libc.sysbols["main_arena"] - 88

main_chunk

  • 在程序的执行过程中,我们称malloc申请的内存为chunk。这块内存在ptmalloc内部用malloc_chunk结构体来表示。
  • 当程序申请的chunk被free后,会被加入到相应的空闲管理列表中。
  • 无论一个chunk的大小如何,处于分配状态还是释放状态,它们都使用一个统一的结构。但根据是否被释放,结构会有所更改。
struct malloc_chunk {INTERNAL_SIZE_T      mchunk_prev_size;  // 如果前面一个堆块是空闲的则表示前一个堆块的大小,否则无意义INTERNAL_SIZE_T      mchunk_size;       //当前chunk的大小,由于对齐的原因所以低三位作为flag,意义如下:/*A:倒数第三位表示当前chunk属于主分配区(0)还是非主分配区(1)M:倒数第二位表示当前chunk是从mmap(1)[多线程]分配的,还是从brk(0)[子线程]分配的P:最低为表示前一块是否在使用中*//*1.真正的内存从这里开始分配2.malloc之后这些指针没有用,这时存放的是数据3.只有在free之后才有效。*/struct malloc_chunk* fd;       //当chunk空闲时才有意义,记录后一个空闲chunk的地址struct malloc_chunk* bk;   //同上,记录前一个空闲chunk的地址/* Only used for large blocks: pointer to next larger size. */struct malloc_chunk* fd_nextsize; //当前chunk为largebin时才有意义,指向比当前chunk大的第一个空闲chunkstruct malloc_chunk* bk_nextsize; //指向比当前chunk小的第一个空闲堆块
};

  • prev_size

    • malloc(0x18)会分配0x20的内存
    • malloc(0x19)分会配0x30的内存
    • 如果该chunk的物理相邻的前一地址chunk(两个指针的地址差值为前一个chunk大小)是空闲的话,那该字段记录的是前一个chunk的大小
    • 否则用来存储物理相邻的前一个chunk的数据,这里前一个chunk指的是较低地址的chunk。
    • prev_size位可以被共享,当前的chunk, 如果不够用就会占用下一块chunk的prev_size
  • size
    • chunk1的数据有效区域覆盖到chunk2的prev_size位,并且chunk2的size位的prev_inuse被覆盖为0。系统认为chunk2之前的chunk1已经未在使用了。
    • 当free(chunk2)的时候,系统会将chunk2与chunk2中prev_size大小的空间合并到bins。
    • 我们可以通过改变chunk2的prev_size的内容,操纵向前合并的大小。
    • 造成的问题:overlap(堆块重叠),chunk1被释放了,但是我们可以操纵修改它(堆利用的核心思想),从而修改bins链的内容,泄露其中的地址。
    • 形成的攻击:fastbin ---> fd ---> main_arena ---> 分配新的堆块,我们通过修改chunk1的fd内容,达到分配任意内存的目的,造成fastbin attack。
    • 记录前一个chunk是否被分配。
    • 一般来说,堆中第一个被分配的内存块的size字段的P位都会被设置为1,以便于防止访问前面的非法内存。
    • 当一个chunk的size位的P位为0时,我们能通过prev_size获取上一个chunk的大小及地址,方便进行空闲堆块的合并。
    • 对于fastbin的堆块,不管前面还有没有被分配的chunk,PREV_INUSE都为1。
    • 64位chunk的size必须是16字节对齐
    • 32位chunk的size必须是8 字节对齐
    • 64位 低4位没用 11110000
    • 32位 低3位没用 11111000
    • define chunksize(p) (chunk_nomask (p) & ~(SIZE_BITS))
    • define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
    • NON_MAIN_ARENA记录当前chunk是否是main_arena管理的堆块,1表示不属于,0表示属于
    • IS_MAPPED记录当前的chunk是否是由mmap分配的。
    • PREV_INUSE
    • 最小堆原则 : malloc(0)会分配0x20的空间,prev_size + size + 数据对齐的0x10字节
    • prev_inuse 漏洞利用
  • fd / bk

    • 释放到bins链有效
    • fd指向下一个(非物理相邻)空闲的chunk
    • bk指向上一个(非物理相邻)空闲的chunk
    • 通过fd和bk可以将空闲的chunk块加入到空闲的chunk链表进行统一管理。
  • fd_nextsize / bk_nextsize
    • 释放到bins链有效,不过其用于较大的chunk(large chunk)
    • fd_nextsize指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针
    • bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针
    • 一般空闲的largechunk在fd的遍历顺序中,按照从大到小的顺序排列,可以避免在寻找合适的chunk时挨个遍历。

__libc_malloc

void *__libc_malloc (size_t bytes)
{mstate ar_ptr;void *victim;void *(*hook) (size_t, const void *)= atomic_forced_read (__malloc_hook);if (__builtin_expect (hook != NULL, 0))return (*hook)(bytes, RETURN_ADDRESS (0));
#if USE_TCACHE/* int_free also calls request2size, be careful to not pad twice. */size_t tbytes;checked_request2size (bytes, tbytes);   //注意:用户申请的字节一旦进入申请内存函数被转化为了无符号整数size_t tc_idx = csize2tidx (tbytes);MAYBE_INIT_TCACHE ();DIAG_PUSH_NEEDS_COMMENT;if (tc_idx < mp_.tcache_bins/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */&& tcache&& tcache->entries[tc_idx] != NULL){return tcache_get (tc_idx);}DIAG_POP_NEEDS_COMMENT;
#endifif (SINGLE_THREAD_P){victim = _int_malloc (&main_arena, bytes);assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||&main_arena == arena_for_chunk (mem2chunk (victim)));return victim;}arena_get (ar_ptr, bytes);victim = _int_malloc (ar_ptr, bytes);/* Retry with another arena only if we were able to find a usable arenabefore. */if (!victim && ar_ptr != NULL){LIBC_PROBE (memory_malloc_retry, 1, bytes);ar_ptr = arena_get_retry (ar_ptr, bytes);victim = _int_malloc (ar_ptr, bytes);}if (ar_ptr != NULL)__libc_lock_unlock (ar_ptr->mutex);assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||ar_ptr == arena_for_chunk (mem2chunk (victim)));return victim;
}

  1. 该函数会首先检查是否有内存分配函数的钩子函数(__malloc_hook),这个主要用于用户自定义的堆分配函数。

这就造成了一个利用点:将__malloc_hook指针指向的内容改为one_gadget的地址,再次malloc的时候就会直接启动shell。__malloc_hook -> one_gadget(直接起shell的地址)这时不能将其修改为system的地址,因为system的参数为字符型,而malloc_hook的参数为无符号整数。

  1. 接着会寻找一个arena来试图分配内存,然后调用__int_malloc函数去申请对应的内存

如果分配失败的话,ptmalloc会试图再去寻找一个可用的arena,并分配内存如果申请到了arena,那么在退出之前还得解锁(__libc_lock_lock)

  1. 判断目前的状态是否满足以下条件
  • 要么没有申请到内存
  • 要么是mmap的内存
  • 要么申请的的内存必须在其所分配的arena中
  • assert

最后返回内存,进入__int_malloc

__int_malloc

__int_malloc是内存分配的核心函数,其核心思路为:

它根据用户申请的内存块大小以及相应大小chunk通常使用的频度,依次实现了不同的分配方法它由小大到大依次检查不同的bin中是否有相应的空闲块可以满足用户请求的内存当所有空闲的chunk都无法满足时,他会考虑top_chunk当top_chunk也无法满足时,堆分配器才会进行内存块申请

1. 定义变量

2. 判断有没有可用的arena

如果没有可用的arena,则返回系统调用mmap去申请一块内存

3. 判断是否在fastbin范围

如果申请的chunk的大小正好位于fastbin的范围,则从fastbin的头节点开始取chunk。需要注意的是,这里比较的是无符号整数调用remove_fb取出,并返回得到的fastbin的头

4. 判断是否在smallbin

如果获取的内存块的范围为smallbin的范围,执行以下流程找到其大小对应的下标,判断其链表是否为空,不为空则取最后一个

注意,为了防止一个堆块能够正常free且不前向后并,需要修改当前堆块的物理相邻的紧接着的2个堆块的inuse位为1。

5.调用consolidate

当fastbin,small bin中的chunk都不能满足要求时,就会考虑是不是largebin,在此之前先调用malloc_consolidate处理fastbin中的chunk

将有可能合并的chunk先进行合并后放到unsorted bin中,不能合并的就直接放到unsorted bin中,然后再进入大循环,以减少堆中的碎片。只有在分配一个size在largebin范围内的堆块,才能触发malloc_consolidate

6. 小总结

在fastbin范围内,先判断对应链表是否为空,不为空则取刚放入的chunk在smallbin范围内,先判断对应链表是否为空,不为空则取第一个放入的chunk这两者都无法匹配用户申请的chunk时,就会进入大循环

7. 进入大循环

a. 尝试从unsorted bin中分配用户需要的内存b. 尝试从large bin中分配用户需要的内存b. 尝试从top_chunk中分配用户需要的内存

8. 从unsorted bin中分配nb

如果申请的size小于unsorted bin中符合要求的chunk的size,会对其进行切割,剩下的进入last_remainder(由unsorted bin管理)如果unsorted bin中没有满足要求的chunk时,会先place in order整理,然后再去large bin中寻找

9. 从large bin中分配nb

注意,large bin中的堆块不会split,不满足的话就从top_chunk中切割

10. 大循环之对于unsorted bin的check

对于size的check:检查当前size是否满足对齐的 要求对于fd和bk的check:bck -> fd != victim对于double free的check:next->prev_inuse = 0

11. 大循环之切割unsorted bin

如果用户请求为small bin chunk,那么我们首先考虑last_remainder如果last_remainder分割后还够可以作为一个chunk,则使用set_head,set_foot设置标志位,将last_remainder放入原来unsorted bin的位置

12. 大循环之取出unsorted bin

首先将unsorted bin取出,如果其size和我们的nb(need bytes)一样则直接放回这个unsorted bin

13. 大循环之放入对应的bin

根据取出的size来判断应该放入哪个bin,放入small bin的时候则双向链表插入在else if中处理large bin的逻辑,包括大小排序以及fd_nextsize和bk_nextsize

14. 大循环总结

整个过程迭代了10000次

__int_malloc的大循环主要用来处理unsorted bin如果整个循环没有找到合适的bin,说明所有的unsorted bin的大小都不满足要求如果经过了10000次的循环,所有的unsorted bin中的bin都被放入了对应的bin中,即small bin放入对应的index中,large bin排好序后放入对应的index中

15. 大循环之large bin

如果请求的chunk在large bin范围内,就在对应的bin中从小到大依次扫描,直到找到第一个合适的,并不一定精确

切割后的remainder会被放入到unsorted bin中,同时设置标志位等信息

16. 寻找较大的chunk

如果走到了这里,说明对于用户所需的chunk,不能直接从其对应的合适的bin中获取chunk,需要扫描所有的bin,查找比当前bin更大的fast bin或small bin 以及large bin

17. 找到一个合适的map

18. 取出chunk

切割之后还是一样,放入到unsorted bin

19. 使用top_chunk

如果所有的bin中的chunk都没有办法直接满足要求(即不合并),或者没有空闲的chunk时,就只能使用top_chunk了

20. top_chunk不够用

> 如果top_chunk不够用的时候并不是直接申请内存,而是先调用consolidate合并空闲的fastbin
>
> 然后等待下次循环再去判断是否够用,不够用才会调用sysmalloc申请内存
>
> ![](https://ws1.sinaimg.cn/large/006nFhrCly1g47vckz6b7j30gn0cmgnb.jpg)

_int_malloc总结

  • malloc寻找堆块的顺序
  1. 在fastbin中寻找有没有对应的chunk
  2. 请求大小为small bin范围,在small bin中寻找有没有对应的chunk
  3. 请求大小为large bin范围,仅调用malloc_consolidate合并fastbin
  4. 在unsorted bin中寻找有没有合适的chunk
  5. 在large bin中寻找有没有合适的chunk
  6. 寻找较大的bin链中有没有合适的chunk
  7. 寻找top_chunk
  8. top_chunk不够用,调用malloc_consolidate合并fastbin
  9. top_chunk不够用,系统调用再次申请内存

malloc 结构体_二进制安全之堆溢出(系列)——堆基础 amp; 结构(二)相关推荐

  1. malloc 结构体_算法与数据结构——结构体变量

    首先,要学习数据结构,一般要先了解结构体变量的使用,那么该如何定义结构体变量呢?随我一起回忆一下吧.(不一样的音乐,不一样的体验)(1)直接定义结构体变量.struct {int a;        ...

  2. java 链表放置结构体_结构体和它在链表中的使用

    一.结构体 由不同类型的数据组合成一个整体,以便引用,这些组合在一个整体中的数据是互相联系的. 1.1如何声明结构体呢? struct 结构体名  //结构体名字用作结构体类型的标志 {成员列表}; ...

  3. C 语言结构体_点运算符( . )和箭头运算符( - )的区别

    很多时候,在对结构体进行相应的编码时,时而发现是用点运算符( . ),时而是用箭头运算符( -> ) 那么这两者之间的使用有什么区别吗? 相同点 两者都是二元操作符,而且右边的操作数都是成员的名 ...

  4. 遍历结构体_三菱ST语言编程(3)——结构体变量

    上篇文章介绍了数组,是一组相同类型数据的列表,那么不同类型的数据能否组合到一起用一个标签表示呢?答案当然是可以的,而实现这个功能的就是结构体(struct). 建立结构体 在三菱结构化编程的界面中左侧 ...

  5. c语言 结构体_颖儿教你学C语言结构体,全面讲解,让程序小白玩转结构体编程...

    C语言结构体详细教学开始 前面的教程中我们讲解了数组(Array),它是一组具有相同类型的数据的集合.但在实际的编程过程中,我们往往还需要一组类型不同的数据,例如对于学生信息登记表,姓名为字符串,学号 ...

  6. java解析c的结构体_解析C语言中结构体struct的对齐问题

    首先看一下结构体对齐的三个概念值: 数据类型的默认对齐值(自身对齐): 1.基本数据类型:为指定平台上基本类型的长度.如在32位机器中,char对齐值为1,short为2,int,float为4,do ...

  7. c语言 电话簿 链表,C语言_链表_结构体_电话簿简单实现

    [目的] 1.编写手机电话薄管理程序,用结构体实现下列功能: (1) 手机电话薄含有姓名.宅电.手机3项内容,建立含有上述信息的电话簿. (2)输入姓名,查找此人的号码. (3)插入某人的号码. (4 ...

  8. linux网络设备驱动结构体,Linux网络设备驱动之设备驱动的注册与注销(二)

    网络设备驱动的注册与注销由 register_netdev( ) 和 unregister_netdev( ) 函数完成,这两个函数的原型为: int register_netdev(struct n ...

  9. c++如何定义二维数组结构体_原来数组还可以这么玩(宏把数组玩坏了)

    点击上方公众号名称关注,获得更多内容 ✎ 编 者 悟 语 想起青春追寻的昨天,你也是曾经追风的少年. 文 章 导 读 今天给小伙伴们介绍下用宏对数组值进行分类使用的方式,比较大的程序中的寄存器经常会这 ...

最新文章

  1. 教你如何查看 Git 提交中发生了什么变化
  2. copy构造函数使用深copy
  3. Pixhawk---超声波模块添加说明(I2C方式)
  4. 穹顶灯打不出阴暗面_Java 8星期五:Java 8的阴暗面
  5. 模板元实现顺序、分支和循环结构
  6. DIY RazorEngine 的程序集生成方式
  7. 问题三十八:C++中bad alloc问题(1)——分析问题
  8. C# - Poker Sort
  9. 创业1年半,烧光130万:我总结了哪些教训?
  10. Nicholas C. Zakas谈怎样才能成为优秀的前端工程师
  11. 计算机辅助翻译技术的好处,计算机辅助翻译原理与实践
  12. 《C++ Primer》读书笔记——第十三章_拷贝控制
  13. 计算机领域的nature,Nature:什么是量子互联网?
  14. 第一批财务自由的90后,都做对了什么?
  15. node.js 基础(含mongodb,express,express-art-template)
  16. 【华人学者风采】聂礼强 山东大学
  17. Hive sql 常用命令总结
  18. 再见2017,你好2018
  19. 东北大学计算机英语复试,东北大学计算机考研复试英语口语常见问题.doc
  20. data fastboot 擦除_Fastboot 常用命令

热门文章

  1. 单例在多线程中的使用
  2. SQL Server--通过存储过程生成表数据的脚本
  3. 文件内容替换 :SED 命令 和 Perl
  4. PHP CURL 使用代理访问服务器
  5. Solaris 11 安装图解(8)
  6. IT人不要一直做技术(转--我也不知道转了几圈了)
  7. 【计算机网络复习 数据链路层】3.1 数据链路层功能概述
  8. php分页技术的作用,分页原理技术细节剖析(php+mysql)实例
  9. python 笔记本_Python笔记本
  10. 安卓系统挂载NTFS格式硬盘_苹果电脑挥之不去的烦恼!怎样兼容NTFS格式的U盘移动硬盘?...