前言


本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274


栈溢出告一段落。本文介绍下 uClibc 中的 mallocfree 实现。为堆溢出的利用准备基础。uClibcglibc 的一个精简版,主要用于嵌入式设备,比如路由器就基本使用的是 uClibc, 简单自然效率高。所以他和一般的x86的堆分配机制会有些不一样。

正文

uClibc 的 malloc 有三种实现,分别为:

其中 malloc-standard 是最近更新的。它就是把 glibcdlmalloc 移植到了 uClibc中。mallocuClibc最开始版本用的 malloc。本文分析的也是malloc目录下的uClibc自己最初实现的 malloc。 因为如果是 malloc-standard 我们可以直接按照 一般 linux 中的堆漏洞相关的利用技巧来利用它。

现在编译 uClibc 的话默认使用的是 malloc-standard ,我也不知道该怎么切换,所以就纯静态看看 malloc目录下的实现了。

malloc

malloc 的入口开始分析。 为了简单起见删掉了无关代码。


  1. //malloc 返回一个指定大小为 __size 的指针。
  2. /*
  3. 调用 malloc 申请空间时,先检查该链表中是否有满足条件的空闲区域节点
  4. 如果没有,则向内核申请内存空间,放入这个链表中,然后再重新在链表中
  5. 查找一次满足条件的空闲区域节点。
  6. 它实际上是调用 malloc_from_heap 从空闲区域中申请空间。
  7. */
  8. void *
  9. malloc (size_t size)
  10. {
  11. void *mem;
  12. //参数有效性检测。这里没有检测参数为负的情况
  13. if (unlikely (size == 0))
  14. goto oom;
  15. mem = malloc_from_heap (size, &__malloc_heap, &__malloc_heap_lock);
  16. return mem;
  17. }

malloc 实际使用的是 malloc_from_heap 来分配内存。

  1. static void *
  2. __malloc_from_heap (size_t size, struct heap_free_area **heap
  3. )
  4. {
  5. void *mem
  6. /* 一个 malloc 块的结构如下:
  7. +--------+---------+-------------------+
  8. | SIZE |(unused) | allocation ... |
  9. +--------+---------+-------------------+
  10. ^ BASE ^ ADDR
  11. ^ ADDR - MALLOC_ALIGN
  12. 申请成功后返回的地址是 ADDR
  13. SIZE 表示块的大小,包括前面的那部分,也就是 MALLOC_HEADER_SIZE
  14. */
  15. //实际要分配的大小,叫上 header的大小
  16. size += MALLOC_HEADER_SIZE;
  17. //加锁
  18. __heap_lock (heap_lock);
  19. /* First try to get memory that's already in our heap. */
  20. //首先尝试从heap分配内存.这函数见前面的分析
  21. mem = __heap_alloc (heap, &size);
  22. __heap_unlock (heap_lock);
  23. /*
  24. 后面是分配失败的流程,会调用系统调用从操作系统分配内存到 heap, 然后再调用__heap_alloc,进行分配,本文不在分析。
  25. */

计算需要分配内存块的真实大小后进入 __heap_alloc 分配。

heap中使用 heap_free_area 来管理空闲内存,它定义在 heap.h


  1. /*
  2. struct heap_free_area
  3. {
  4. size_t size; //空闲区的大小
  5. //用于构造循环链表
  6. struct heap_free_area *next, *prev;
  7. };
  8. size 表示该空闲区域的大小,这个空闲区域的实际地址并没有用指针详细地指明,
  9. 因为它就位于当前 heap_free_area 节点的前面,如下图所示:
  10. +-------------------------------+--------------------+
  11. | | heap_free_area |
  12. +-------------------------------+--------------------+
  13. \___________ 空闲空间 ___________/\___ 空闲空间信息 ___/
  14. 实际可用的空闲空间大小为 size – sizeof(struct heap_free_area)
  15. 指针 next, prev 分别指向下一个和上一个空间区域,
  16. 所有的空闲区域就是通过许许多多这样的节点链起来的,
  17. 很显然,这样组成的是一个双向链表。
  18. */

所以 free 块在内存中的存储方式和 glibc 中的存储方式是不一样的。它的元数据在块的末尾,而 glibc中元数据在 块的开头。

下面继续分析 __heap_alloc


  1. /*
  2. 堆heap中分配size字节的内存
  3. */
  4. void *
  5. __heap_alloc (struct heap_free_area **heap, size_t *size)
  6. {
  7. struct heap_free_area *fa;
  8. size_t _size = *size;
  9. void *mem = 0;
  10. /* 根据 HEAP_GRANULARITY 大小向上取整,在 heap.h 中定义 */
  11. _size = HEAP_ADJUST_SIZE (_size);
  12. //如果要分配的内存比FA结构还要小,那就调整它为FA大小
  13. if (_size < sizeof (struct heap_free_area))
  14. //根据HEAP_GRANULARITY 对齐 sizeof(double)
  15. _size = HEAP_ADJUST_SIZE (sizeof (struct heap_free_area));
  16. //遍历堆中的FA,找出有合适大小的空闲区,在空闲区域链表中查找大小大于等于 _SIZE 的节点
  17. for (fa = *heap; fa; fa = fa->next)
  18. if (fa->size >= _size)
  19. {
  20. /* Found one! */
  21. mem = HEAP_FREE_AREA_START (fa);
  22. //从该空间中分得内存。这函数前面已经分析过了
  23. *size = __heap_free_area_alloc (heap, fa, _size);
  24. break;
  25. }
  26. return mem;
  27. }

找到大小 >= 请求sizeheap_free_area,然后进入 __heap_free_area_alloc 分配

  1. /*
  2. 该函数从fa所表示的heap_free_area中,分配size大小的内存
  3. */
  4. static __inline__ size_t
  5. __heap_free_area_alloc (struct heap_free_area **heap,
  6. struct heap_free_area *fa, size_t size)
  7. {
  8. size_t fa_size = fa->size;
  9. //如果该空闲区剩余的内存太少。将它全部都分配出去
  10. if (fa_size < size + HEAP_MIN_FREE_AREA_SIZE)
  11. {
  12. ////将fa从heap中删除
  13. __heap_delete (heap, fa);
  14. /* Remember that we've alloced the whole area. */
  15. size = fa_size;
  16. }
  17. else
  18. /* 如果这个区域中还有空闲空间,就把 heap_free_area 节点中
  19. 的 size 减小 size就可以了:
  20. 分配前:
  21. __________ 空闲空间 __________ __ 空闲空间信息 __
  22. / \ / \
  23. +-------------------------------+--------------------+
  24. | | heap_free_area |
  25. +-------------------------------+--------------------+
  26. \__________ fa->size __________/
  27. 分配后:
  28. ___ 已分配 __ __ 空闲空间 __ __ 空闲空间信息 __
  29. / \ / \ / \
  30. +-------------------------------+--------------------+
  31. | | | heap_free_area |
  32. +-------------------------------+--------------------+
  33. \____ size ___/ \__ fa->size __/
  34. */
  35. fa->size = fa_size - size;
  36. return size;
  37. }

注释很清晰了。所以如果我们有一个堆溢出,我们就需要覆盖到下面空闲空间的 heap_free_area 中的 指针,才能实现 uClibc 中的 unlink 攻击(当然还要其他条件的配合),另外我们也知道了在 malloc 的时候,找到合适的 heap_free_area 后,只需要修改 heap_free_area 的 size位就可以实现了分配,所以在 malloc 中是无法 触发类似 unlink 的攻击的。

下面进入 free

Free

首先看 free 函数。

  1. void
  2. free (void *mem)
  3. {
  4. free_to_heap (mem, &__malloc_heap, &__malloc_heap_lock);
  5. }

直接调用了 free_to_heap 函数。


  1. static void
  2. __free_to_heap (void *mem, struct heap_free_area **heap)
  3. {
  4. size_t size;
  5. struct heap_free_area *fa;
  6. /* 检查 mem 是否合法 */
  7. if (unlikely (! mem))
  8. return;
  9. /* 获取 mem 指向的 malloc 块的的实际大小和起始地址 */
  10. size = MALLOC_SIZE (mem); //获取块的真实大小
  11. mem = MALLOC_BASE (mem); //获取块的基地址
  12. __heap_lock (heap_lock); //加锁
  13. /* 把 mem 指向的空间放到 heap 中 */
  14. fa = __heap_free (heap, mem, size);
  15. //如果FA中的空闲区超过 MALLOC_UNMAP_THRESHOLD。就要进行内存回收了,涉及 brk, 看不懂,就不说了,感觉和利用也没啥关系。

首先获得了 内存块的起始地址和大小,然后调用 __heap_free 把要 free 的内存放到 heap 中。


  1. /*
  2. 语义上的理解是释放掉从mem开始的size大小的内存。换句话说,就是把从从mem开始的,size大小的内存段,映射回heap。
  3. */
  4. struct heap_free_area *
  5. __heap_free (struct heap_free_area **heap, void *mem, size_t size)
  6. {
  7. struct heap_free_area *fa, *prev_fa;
  8. //拿到 mem的 结束地址
  9. void *end = (char *)mem + size;
  10. /* 空闲区域链表是按照地址从小到大排列的,这个循环是为了找到 mem 应该插入的位置 */
  11. for (prev_fa = 0, fa = *heap; fa; prev_fa = fa, fa = fa->next)
  12. if (unlikely (HEAP_FREE_AREA_END (fa) >= mem))
  13. break;
  14. if (fa && HEAP_FREE_AREA_START (fa) <= end)
  15. //这里是相邻的情况,不可能小于,所以进入这的就是 HEAP_FREE_AREA_START (fa) == end, 则 mem, 和 fa所表示的内存块相邻
  16. {
  17. /*
  18. 如果 fa 和 mem 是连续的,那么将 mem 空间并入 fa 节点(增加fa的大小即可)管理, 如图所示,地址从左至右依次增大
  19. +---------------+--------------+---------------+
  20. | |prev_fa| mem |fa_chunk| fa |
  21. +---------------+--------------+---------------+
  22. ^______________________________^
  23. prev_fa 与 fa 的链接关系不变,只要更改 fa 中的 size 就可以了
  24. */
  25. size_t fa_size = fa->size + size;
  26. if (HEAP_FREE_AREA_START (fa) == end)
  27. {
  28. if (prev_fa && mem == HEAP_FREE_AREA_END (prev_fa))
  29. {
  30. /* 如果 fa 前一个节点和 mem 是连续的,那么将 fa 前一个节点的空间
  31. 也并入 fa 节点管理
  32. +---------------+---------------+--------------+---------------+
  33. | |pre2_fa| |prev_fa| mem | | fa |
  34. +---------------+---------------+--------------+---------------+
  35. ^______________________________________________^
  36. 将 prev_fa 从链表中移出,同时修改 fa 中的 size
  37. */
  38. fa_size += prev_fa->size;
  39. __heap_link_free_area_after (heap, fa, prev_fa->prev);
  40. }
  41. }
  42. else
  43. {
  44. struct heap_free_area *next_fa = fa->next;
  45. /* 如果 mem 与 next_fa 是连续的,将 mem 并入 next_fa 节点管理
  46. +---------------+--------------+--------------+---------------+
  47. | |prev_fa| | fa | mem | |next_fa|
  48. +---------------+--------------+--------------+---------------+
  49. ^_____________________________________________^
  50. 将 fa 从链表中移出,同时修改 next_fa 中的 size
  51. */
  52. if (next_fa && end == HEAP_FREE_AREA_START (next_fa))
  53. {
  54. fa_size += next_fa->size;
  55. __heap_link_free_area_after (heap, next_fa, prev_fa);
  56. fa = next_fa;
  57. }
  58. else
  59. /* FA can't be merged; move the descriptor for it to the tail-end
  60. of the memory block. */
  61. /* 如果 mem 与 next_fa 不连续,将 fa 结点移到 mem 尾部
  62. +---------------+--------------+--------------+---------------+
  63. | |prev_fa| | fa | mem | unused | |next_fa|
  64. +---------------+--------------+--------------+---------------+
  65. ^___________________^^________________________^
  66. 需要重新链接 fa 与 prev_fa 和 next_fa 的关系
  67. */
  68. {
  69. /* The new descriptor is at the end of the extended block,
  70. SIZE bytes later than the old descriptor. */
  71. fa = (struct heap_free_area *)((char *)fa + size);
  72. /* Update links with the neighbors in the list. */
  73. __heap_link_free_area (heap, fa, prev_fa, next_fa);
  74. }
  75. }
  76. fa->size = fa_size;
  77. }
  78. else
  79. /* 如果fa和 mem之间有空隙或者 mem> HEAP_FREE_AREA_END (fa),那么可以简单地
  80. 把 mem 插入 prev_fa 和 fa之间 */
  81. fa = __heap_add_free_area (heap, mem, size, prev_fa, fa);
  82. return fa;
  83. }

__heap_link_free_area 就是简单的链表操作。没有什么用。

  1. static __inline__ void
  2. __heap_link_free_area (struct heap_free_area **heap, struct heap_free_area *fa,
  3. struct heap_free_area *prev,
  4. struct heap_free_area *next)
  5. {
  6. fa->next = next;
  7. fa->prev = prev;
  8. if (prev)
  9. prev->next = fa;
  10. else
  11. *heap = fa;
  12. if (next)
  13. next->prev = fa;
  14. }

感觉唯一可能的利用点在于,前后相邻的情况,需要先把 prev_fa 拆链表,我们如果可以伪造 prev_fa->prev,就可以得到一次内存写的机会,不过也只能写入 fa 的值

  1. fa_size += prev_fa->size;
  2. __heap_link_free_area_after (heap, fa, prev_fa->prev);
  1. static __inline__ void
  2. __heap_link_free_area_after (struct heap_free_area **heap,
  3. struct heap_free_area *fa,
  4. struct heap_free_area *prev)
  5. {
  6. if (prev)
  7. prev->next = fa;
  8. else
  9. *heap = fa;
  10. fa->prev = prev;
  11. }

总结

怎么感觉没有可利用的点,还是太菜了。以后如果遇到实例一定要补充进来。

tips:

  • 分析库源码时看不太懂可以先编译出来,然后配合这 ida 看,所以要编译成 x86 或者 arm 方便 f5 对照看。比如这次,我把 uClibc 编译成 arm 版后,使用 ida 一看,发现 uClibc 怎么使用的是 glibc 的那一套,一看源码目录发现,原来它已经切换到 glibc 这了。

  • 忽然想起来交叉编译环境感觉可以用 docker 部署,网上一搜发现一大把,瞬间爆炸。

参考链接:

http://blog.chinaunix.net/uid-20543183-id-1930765.html

http://hily.me/blog/2007/06/uclibc-malloc-free/

一步一步pwn路由器之uClibc中malloc&&free分析的更多相关文章

  1. 一步一步pwn路由器之rop技术实战

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这次程序也是 DVRF 里面的,他的路径是 pwnable/She ...

  2. 一步一步pwn路由器之wr940栈溢出漏洞分析与利用

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这个是最近爆出来的漏洞,漏洞编号:CVE-2017-13772 固 ...

  3. 一步一步pwn路由器之路由器环境修复&&rop技术分析

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 拿到路由器的固件后,第一时间肯定是去运行目标程序,一般是web服务 ...

  4. 一步一步pwn路由器之环境搭建

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 正式进入路由器的世界了.感觉路由器这块就是固件提取,运行环境修复比 ...

  5. 一步一步pwn路由器之radare2使用全解

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 radare2 最近越来越流行,已经进入 github 前 25了 ...

  6. 一步一步pwn路由器之radare2使用实战

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 前文讲了一些 radare2 的特性相关的操作方法.本文以一个 c ...

  7. 一步一步pwn路由器之栈溢出实战

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 本文以 DVRF 中的第一个漏洞程序 stack_bof_01 为 ...

  8. 简单实例一步一步帮你搞清楚MVC3中的路由以及区域

    我们都知道MVC 3 程序的所有请求都是先经过路由解析然后分配到特定的Controller 以及 Action 中的,为什么这些知识讲完了Controller Action Model 后再讲呢?这个 ...

  9. 【计算机网络】一步一步学习IP路由流程

    TCP/IP协议簇是目前互联网应用最广的协议栈,谈到TCP/IP协议栈就不能不讲一讲IP路由的问题,因为在我们使用的网络通信中几乎每时每刻都在发生着IP路由的事件…….当你在网络世界中还是一位新手的时 ...

随机推荐

  1. 一文详解python的类方法,普通方法和静态方法

    首先形式上的区别,实例方法隐含的参数为类实例self,而类方法隐含的参数为类本身cls. 静态方法无隐含参数,主要为了类实例也可以直接调用静态方法. 所以逻辑上,类方法被类调用,实例方法被实例调用,静 ...

  2. C#获取文件版本、文件大小等信息

    使用以下C#程序代码可以非常方便地获取Windows系统中任意一个文件(尤其是可执行文件)的文件版本.文件大小.版权.产品名称等信息.所获取到的信息类似于在Windows操作系统中右键点击该文件,然后 ...

  3. 转 ZFC公理系统

    http://blog.sina.com.cn/s/blog_5d045b5c0100spld.html 首先,ZFC集合论中的公理大致分为3组: 1.外延公理. 2.子集公理模式.无序对公理.并集公 ...

  4. (转) mysqldumpslow使用说明总结

    原文:http://blog.csdn.net/langkeziju/article/details/49301993 mysqldumpslow使用说明mysqldumpslow --helpUsa ...

  5. 【字符串】Reverse Words in a String(两个栈)

    题目: Given an input string, reverse the string word by word. For example,Given s = "the sky is b ...

  6. list转换为树结构--递归

    public static JSONArray treeMenuList(List<Map<String, Object>> menuList, Object parentId ...

  7. VMware workstation 11 的下载

    不多说,直接上干货! VMWare Workstation 11的下载详细: 谷歌FQ,进入. 具体怎么达到可以FQ,见 FQ软件lantern-installer-beta.exe(推荐) 成功! ...

  8. centos7-安装mysql5.6.36

    本地安装了mysql5.7, 但和springboot整合jpa时会出现 hibernateException, 不知道为什么, 换个mysql5.6版本的mysql,  源码安装, cmake一直过 ...

  9. Java对象的强、软、弱和虚引用+ReferenceQueue

    Java对象的强.软.弱和虚引用+ReferenceQueue 一.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足 ...

  10. bzoj 3600: 没有人的算术

    Description Solution 我们可以给每一个数钦定一个权值 , 这样就可以 \(O(1)\) 比较大小了. 考虑怎么确定权值: 用平衡树来维护 , 我们假设根节点管辖 \([1,2^{6 ...