热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MOSEC议题解读|ATaleofTwoMallocs

  议题概要dlmalloc和jemalloc是Android用户空间使用的两个内存管理器,议题详细分析了两种malloc的实现,深入分配和释放的算法,数据结构的相关细节,讲解中还附带提供了几个堆内存

  

议题概要

dlmalloc和jemalloc是Android用户空间使用的两个内存管理器,议题详细分析了两种malloc的实现,深入分配和释放的算法,数据结构的相关细节,讲解中还附带提供了几个堆内存可视化的调试器插件。最后会介绍如何利用堆分配器控制内存布局,并以堆缓冲区溢出为例讲解具体应用。

 

作者介绍

三叉戟(Pegasus)让以色列的NSO Group一战成名,Shmarya Rubenstein正是该组织成员之一。他研究的领域上至应用软件和固件的代码,下至芯片、PCB级别的硬件实现,精熟于嵌入式设备的安全分析。具有十二年专业领域的逆向分析经验。

 

议题解析


dlmalloc

经历了数十年的迭代更新,目前仍然广泛活跃在历史舞台的漏洞几乎都是堆内存中出现的漏洞(OOB,UAF)。想要在目标进程中利用这些漏洞时,不免都要和内存分配器打交道。

Android对libc的实现里(bionic)一开始采用了dlmalloc(诞生于1987,于2012停止更新),是一套非常成熟的解决方案。

dlmalloc通过segment和chunk管理内存,一块segment当中包含若干块chunk,当有比如malloc(0x300)的内存申请时,top chunk会划分出一块新内存:

不同大小的chunk可以连续排布,chunk中要包含metadata用于说明该chunk以及上一个chunk的大小,还有这两个chunk是否被使用:

当被free的chunk临近有已经被free的chunk时,两个chunk会合并。除了这些基本的管理方式,dlmalloc还使用bin来管理内存。相同大小且被free的chunk会以双链表形式存放在bin当中。bin中的内存遵循FIFO原则,下一次malloc时会优先从bin中取内存,选择大小不小于申请内存的一块返回。一共有32个small bins和32个tree bins,tree bins用于管理大内存,采用bitwise digital tree结构存储。dlmalloc的小内存分配原则总结如下:


  • 计算对象大小

  • 从small bin中找大小和目标相同的chunk返回

  • 最近一次被释放的内存块是否合适

  • 从small bin中找不小于目标的chunk返回

  • 从tree bin中找大小不小于目标的chunk返回

  • 如果仍然没有才从top chunk中划分新内存,或者创建新的segment

dlmalloc分配大内存时和上述流程相似,但要简单一些,直接从tree bin开始往下执行。当请求分配的内存大于64k时,malloc会调用mmap分配内存。

为了适应多线程,dlmalloc只是简单地在malloc开始和结束的位置加了一个lock,对多线程的应用性能影响还是比较大的。

jemalloc

Android目前已经开始转为使用jemalloc管理内存,相比dlmalloc,它的设计更利于多线程的运行环境。jemalloc在2014年五月,也就是Android 5.0开始引入,随后被设置为默认的分配选项。不过时至今日,Android 5和6的设备中仍能同时看到dlmalloc和jemalloc两者并存。

jemalloc管理内存时要复杂一些,最大的管理单元是arena,一共有两个,分别带有一个lock。不同线程尝试分配内存时,会平均分配至两个arena,只有在相同的arena中分配内存时才需要获取lock。arena中实际管理内存的是chunk和region,Android 7之前chunk为256k,之后32位系统改为512k,64位系统改为2MB。

每个chunk中会包含若干个run,run里面的region大小完全相同,而run的metadata会存放在chunk的header当中,这样region里只存放数据本身,不再有内存属性说明,malloc实际返回的是region的地址:

jemalloc也用bin来管理内存,共有39个bins。不同于dlmalloc的分配方法,jemalloc分配的内存完全来取自于bin。bin的metadata存放于arena的header中,39个bin还会存放当前正在使用的run。所有带有空闲region的run和闲置的chunk信息会被放置在红黑树结构当中,这样寻找空闲内存的复杂度可以控制在o(log(n)):

除此之外,为了优化多线程性能,jemalloc还采用了LIFO结构的tcache,存放近期被释放的region,每个线程的每个bin都对应一个tcache。存放在tcache中的内存并不会设置free标记位,并且由于tache附着于线程本身,使得大部分情况下从tcache分配内存时完全无需lock。

当tcache中存放的内存块用尽时会触发prefill,此时jemalloc会lock当前arena,并从当前run中取出一定数量的region存入tcache,使得它总有存量。

当tcache存满时(small bin是8,larger bin是20)会触发flush,tcache中存放的region才会被真正标记为释放。被释放的region才能被其他线程再次申请。

另外jemalloc本身也有GC,即有一个全局的计数器记录申请和释放,达到阈(读yu,四声)值时会触发一次特殊的释放,目标bin里tcaches中四分之三的region会被释放。下次GC时会轮到下一个bin。这是另一种真正释放region的方法。总结一下jemalloc的分配原则:


  • 计算申请内存大小

  • 从当前线程的tcache中找到合适的bin

  • 如果tcache为空,就从当前的run中prefill一些region进来

  • 如果当前run耗尽,就从低地址开始找到第一个非空run

  • 如果现有run里没有足够的内存就分配一个新run

  • 如果chunk里没有空间了就分配一个新chunk,同时分配新run并prefill一些region到tcache

对比两种内存管理方式如下:目前系统中大概30%使用dlmalloc,70%是jemalloc。

Exploitation

在一个漏洞利用的过程中,通常会基于这些前置的基础知识操纵堆内存。使得其按照我们预定的方式排布,如让特定的两个对象相邻,或者让一个对象重用另一个被释放的对象的内存,这些技巧统称为堆风水。

为了更好控制堆的状态,能够随时查看内存的分布情况是很有帮助的。下面三个工具非常好用,一个是去年INFILTRATE大会上Cencus的pyrsistence,另外一个是作者自己写的shade,最后就是NCC Group发布的libdlmalloc。以作者自己的工具为例,基于GDB的插件可以实时显示目标内存附近的区块状态。

不知道是不是开源的情况下反而更没有人去研究原理和可视化工具,Windows上反而很多年前就已经有各种堆内存可视化脚本了,几乎是每个调试器的标配功能。

Android可以说是目前主流系统中附加各类缓解措施最多的系统了,地址随机化,SELinux,进程沙盒等都让漏洞利用过程无比痛苦。下面以溢出为例,看看上述关于堆分配的知识能推导出哪些实际使用技巧。

堆溢出

在一个漏洞利用过程中,一般先要获取一些gadget,然后利用这些gadget扩大战果。gadget包括相对地址读/写、任意地址读/写,任意执行等。比如一个常见构造gadget的方法,让越界写的对象和一个带有数据指针+长度的对象相邻:

这样越界写后,临接对象就会成为一个读或写的gadget,这取决于临接对象能够提供哪些操作让我们使用。这一手应该早已经是脚本环境中漏洞利用的家常便饭了。实际在找这类gadget时可以直接在代码中找含有malloc,new,reallocs,std::vector,std::string的对象。如果能够访问到他们的方法,就可能是一个潜在的gadget。

jemalloc分配内存的情况下,临接对象的选择条件更为苛刻,必须和溢出的对象大小对齐后相同,这样才有可能位于同一个run当中。

另外一个技巧是placeholder,即提前分配大量和目标对象大小相同的占位对象,然后释放他们勇于填充漏洞对象和gadget,这样很大几率会出现临接的情况,确保溢出行为有效:

如果能提前分配足够多的对象将已有内存占满,后面placeholder将有更大几率分配在临接连续的内存当中。

在分配目标内存和gadget等过程中,很有可能引入噪音,即未预期的对象也因此分配并占了一精心排布的内存。对于这些噪音,一个很好的去除方法是预先分配足够的小内存块。每次引入噪音前先释放一些小内存块,确保噪音被这些内存块收纳。

由于dlmalloc内存chunk中有metadata,溢出时应该把这些字段的大小也纳入考虑范围,而jemalloc的metadata存放在region之外所以不用考虑。

另外,有可能本来用于临接的对象和溢出对象由不同的thread创建,对于dlmalloc来说这没有什么影响,但jemalloc就比较棘手,不同的tcache很难保证二者临接。遇到这种情况最好的办法是触发flush或者GC,让目标区块转移到同一个线程当中。

还有一个问题是padding,jemalloc分配的对象由于region大小固定,region很可能比对象实际要大,这样溢出时就要考虑把中间没有用到的内存也覆盖掉。

最后的一个可能导致问题的是两个对象所属arena不同,不过这个问题很好解决,可以先创建比如30个线程,相邻的线程应该位于刚好不同的arena当中。然后每隔一个线程释放掉自身。由于平衡的诉求,接下来创建的15个线程都会位于相同的arena当中。

 

总结

在漏洞利用的学习过程中,可能一个漏洞案例只能学到一手技巧,即便看了很多例子,方法却大同小异。Shmarya Rubenstein把这些技巧集中展现,虽然有些抽象,但抽出来的才最像。Android堆分配原理弄清楚了,无论以后遇到什么利用场景都能自行找到解决方法,而不是广撒网似的找其他利用案例去揣度。

这些内存分配方面的技巧相当可贵,近来越来越多人分享时只提及讲案例本身,而且专挑奇案特例,且关键步骤一笔带过,留给听众的最多只是特例的解决方法,回过神发现自己遇到的问题与此稍有不同就无从下手。与之相比,Shmarya Rubenstein的分享应该回让想要深耕漏洞利用的研究人员大呼过瘾。

 

温馨提示:安全客近期会陆续发布更多MOSEC干货议题解读,敬请关注~


推荐阅读
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • ScrollView嵌套Collectionview无痕衔接四向滚动,支持自定义TitleView
    本文介绍了如何实现ScrollView嵌套Collectionview无痕衔接四向滚动,并支持自定义TitleView。通过使用MainScrollView作为最底层,headView作为上部分,TitleView作为中间部分,Collectionview作为下面部分,实现了滚动效果。同时还介绍了使用runtime拦截_notifyDidScroll方法来实现滚动代理的方法。具体实现代码可以在github地址中找到。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
  • 本文由编程笔记#小编整理,主要介绍了关于数论相关的知识,包括数论的算法和百度百科的链接。文章还介绍了欧几里得算法、辗转相除法、gcd、lcm和扩展欧几里得算法的使用方法。此外,文章还提到了数论在求解不定方程、模线性方程和乘法逆元方面的应用。摘要长度:184字。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 如何在php文件中添加图片?
    本文详细解答了如何在php文件中添加图片的问题,包括插入图片的代码、使用PHPword在载入模板中插入图片的方法,以及使用gd库生成不同类型的图像文件的示例。同时还介绍了如何生成一个正方形文件的步骤。希望对大家有所帮助。 ... [详细]
author-avatar
LookUp77
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有