Linux内存取证:解析用户空间进程堆(中)

luochicun 技术 2019年1月29日发布
Favorite收藏

导语:本文将描述如何以及在何处将前一节中描述的结构存储在内存中,以用于运行中的Linux用户空间进程。

上文我们对解析用户空间进程堆的动机和历史,做了一个简要的概述。另外,我们Glibc堆的3层结构也做了一些概述,这些结构是解析用户空间进程堆的关键。至于每个结构所起的作用,请看本文的分析。

内存视图

本节描述如何以及在何处将前一节中描述的结构存储在内存中,以用于运行中的Linux用户空间进程。

内存中的块

块的结构名为malloc_chunk,包含以下字段:

· prev_size:如果上一个块已经被释放了,那该字段将包含上一个块的大小;

· size:从当前块的开头到下一个块的距离(以字节为单位);

· fd:正向链接(Forward link),指向下一个被释放的块;

· bk:反向链接(Backward Links),指向之前被释放的块;

· fd_nextsize:指向下一个更大的块;

· bk_nextsize:指向下一个更小的块;

此结构位于每个块的开头之处,但并非每个块都要用到所有字段。对于已分配的块,主要使用size字段来实现其预期目的。只有当前面的块是释放的块时,才使用prev_size字段。正如Ferguson已经解释的那样,size字段不仅包含了大小信息,还使用了其低层级的3位作为特殊标志。最低位(PREV_INUSE标志)表示前一个块是否是释放的块,第二个最低位(IS_MMAPPED标志)表示设置的MMAPPED块,第三个最低位(NON_MAIN_ARENA标志)表示代的块不属于main_arena。

malloc_chunk结构的所有其他字段都会被用于用户或进程数据,并且不包含任何元信息。如图2所示,其中显示了用户数据块分配的块(顶部)从fd构建到下一个块的 size字段的起始处,中间的所有字段都被填充为灰色(标记为数据),其中就包括第二个块的prev_size字段。

1-s2.0-S1742287617301895-gr2.jpg

内存中分配的块

每个块的大小和地址是一致的,这意味着它可以分别在x86和x64架构上分别被8和16整除。在MMAPPED块的示例下,大小和地址能够被页面大小均匀地整除。这是由于mmap API函数返回了请求块的内存空间,它不仅返回大小,还返回页面大小边界上的地址。此外,尽管每个MMAPPED块都是由单独的mmap调用产生的,但是多个MMAPPED块最终可能会出现在一个vm_area_struct结构描述的内存区域中,因为内核可以很容易地扩展一个区域。另一方面,如果释放了位于同一区域的两个或多个MMAPPED块之间的MMAPPED块(其相关页面被返回到操作系统),则连续内存区域可以被分割成两个独立的区域。与正常分配的块类似,用户或进程数据块在size字段之后立即被分配,但不包括下一个块的prev_size字段,因为它不能保证MMAPPED块后面紧跟着另一个块。

释放的块会以如上所述的方式链接在一起,最终形成一个bin。bin可以被视为一个容器,用于释放总是属于特定arena的块。不同类型的容器,其主要区别在于它们所包含的块的大小不同,比如fastbin块(通常在×86体系结构上大小不超过80字节的块),small bin(在×86架构上通常不大于512字节)和large bin(small bin以上的所有内容)。不同类型的bin,链接的方式也不同,会直接影响malloc_chunk结构使用的字段,从而决定覆盖多少字节的用户或进程数据。 Fastbin块仅使用fd字段,small bin和large bin使用的是fd和bk字段,用于构建循环链表,而large bin块也会使用fd_nextsize和bk_nextsize字段,以确保在每种情况下,相应的用户数据被指针覆盖。对于prev_size字段也是如此,当释放的块放入small bin或large bin时,该字段就会被设置。例如对于释放的large bin,未覆盖的用户数据从bk_nextsize字段之后一直延伸到下一个块的prev_size字段的起始处。关于prev_size字段的唯一例外是fastbin块,所有这些块的标志之间都不相互受影响,例如NON_MAIN_ARENA标志一直处于设置状态,但是下一个块的PREV_INUSE标志没有被设置为free,导致prev_size字段中没有覆盖用户或进程数据。

顶层块虽然不使用任何指针,但却非常重要,数据部分的起始处就像在fd构建之后分配的块一样,因为它是arena中的最后一个块并且大部分时间都在内存区域边界处结束,所以没有可以用于块的prev_size字段。

存在内存中的arena和堆信息结构

main heap是一个连续的内存区域,包含main_arena的所有块。虽然区域是连续的,但它可以分成多个连续的内存区域。它描述的malloc_state结构存储在映射的Glibc库的bss部分中,如前所述,没有heap_info结构用于main_arena。

然而,线程arena的malloc_state结构与块一起存储在同一内存区域中。从图3中可以看出,它位于第一个heap_info结构之后和该arena的第一个块之前。通常,可以在属于线程arena的每个映射的内存区域的开头找到heap_info 结构。除此之外,多个heap_info stuct可能最终出现在同一个映射的内存区域中。这些heap_info结构虽不一定都属于同一个arena,但也可以与不同的arena相关。图3的内存区域可以说明了这一点,其中较低的heap_info结构属于另一个arena,而较高的heap_info结构属于所描绘的malloc_state结构。

1-s2.0-S1742287617301895-gr3.jpg

内存中的 malloc_state 和heap_info 结构

堆和栈通常各自在部分向上发展,并彼此靠拢,事实上,只要堆只包含没有任何线程arena或MMAPPED块的main_arena,就不会和栈靠拢。但是,只要其中任何一个被引入,这种严格的分离就会被打破。与MMAPPED块类似,属于线程arena的内存区域通常与包含特定线程堆栈框架的内存区域混合在一起。

插件实现

《Rekall内存取证医框架》一文提供了一组用于分析内存转储的插件。该方法最初是2013年从Volatility代码库中衍生出来的,后来经过重新编写和扩展,Rekall版本已经有了1.5.1和1.5.2.post1版本。在撰写本文时,这些版本在×86和×64架构上至少支持Glibc的2.20,2.21,2.22,2.23和2.24版本。Rekall的核心组件是Python类HeapAnalysis,它可以将之前描述的所有分析结果都呈现出来。

目前已经开发出的插件有4个:

· Heapinfo:该插件提供了一个关于arena数量、块和大小的简要概述;

· heapdump:该插件会将所有已分配和已释放的块转储到单独的文件中以供进一步分析;

· heapsearch:该插件会在所有块中搜索给定的字符串、正则表达式或指针;

· heaprefs:该插件会检测给定块的数据部分是否引用了其他块;

虽然这些插件最适合使用正在使用的Glibc版本的调试信息,但HeapAnalysis类提供了在没有调试符号可用时分析进程的功能。

main_arena的获取

HeapAnalysis类在初始化期间执行的首要任务之一是定位main_arena。除了保存arena大小和bin指针等重要信息外,它还会显示在映射的Glibc库中的位置,表明在搜索进程中检测的信息是否是正确的。

如果有调试信息,或者更准确地说,有main_arena符号的常量偏移量,则是获得main_arena的最可靠方法。否则,不能直接确定main_arena,而是通过使用两种不同的技术进行搜索。第一个假设有多个线程,因此也有多个arena。由于只有main_arena结构存储在映射的Glibc库中,所有其他结构通常位于内存区域的开头,紧跟在heap_info结构之后。因此,对于vm_area_struct结构描述的每个内存区域(除了映射文件区域或堆栈框架之类的例外情况),开头都是heap_info结构。如果其ar_ptr字段指向其自身并且其prev字段为null(第一个heap_info结构指向没有其他heap_info实例,并且如果它位于arena之前,它是第一个实例),则ar_ptr指向的arena将被暂时保存。如果最后可以识别至少一个arena,则就会测试出其下一个构建是否会出现在映射的Glibc库的内存区域中。如果是这种情况,指向Glibc的地址将被视为main_arena。

如果不能识别更多的arena,则意味着只有main heap和一些可能的MMAPPED块,这时就要使用第二种技术,即利用每个bin块包含的循环双链表。具体的进程就是顺着bin块的bk指针,找到main_arena。为了获得这样的bin块,我们必须要检测main heap上的每个块的PREV_INUSE位。如果此标志在任何块中都未被设置,则前一个块很可能是已释放的bin块。而后一个块则紧跟上一个块的bk字段,以便最终在main_arena中结束。如果在某个时刻,bk字段指向映射的Glibc库,则代表我们很可能找到了main_arena。

不过此时仍然存在一个问题,bk字段指向arena的中间而不是开头。虽然通过检测块的大小可以准确地确定该主要区域内的位置,但是这种方法并不总是完全可靠,因为不同的bin大小不同(容器大小在编译时是可以更改的)。所以找到main_arena开头的方法便是搜索顶部块指针,这么做有以下两个原因:

1. 对于一个特定的Glibc实例,malloc_state中的最大块指针的偏移量是固定的,因此是可靠的。因此,如果找到了该字段的虚拟地址,那么到arena起点的距离就可以很容易地计算出来。

2.需要有一个可靠的进程来将预期字段与当前值关联起来,顶部字段提供了这样一种关联,即它指向的块应该一直能扩展到内存区域的末尾(顶部块的偏移量加上它的大小)。

到达顶部字段的进程现在是从当前bin返回,将每个指针大小的值视为指针,并检测它是否指向main heap内以及满足顶部块的要求。一旦找到顶部字段,就通过从找到的顶部字段的内存地址中减去malloc_state结构中顶部字段的偏移量来计算出主arena的地址。

MMAPPED区域

虽然属于arena的内存区域的标识在大多数情况下非常可靠,但是包含在MMAPPED块的区域的标识则不是这样的,因为:

1.其中缺乏独特的结构或可靠的指针;

2.任何未命名的映射内存区域可能包含MMAPPED块,它们可以位于进程空间中的任何位置;

似乎指向这些块的指针最有可能保存在处理这些块的函数的堆栈框架中,而不是保存在任何相关的簿记结构中。因此,一种识别它们的方法就是将堆栈段(来自所有线程)中的指针大小的所有字节解释为指针,跟踪它们并验证驻留在该位置的信息。然而,这种方法却隐含着两个问题。

1.它非常容易出错,并且将任意数据解释为指针,并且,根据堆栈大小的不同,耗时多少也各不相同,因为必须读取、跟踪每个指针,然后,将它指向的数据作为块对象启动并测试其值。

2.它可能会遗漏一些MMAPPED块在,这种情况下,MMAPPED 块指针不再被使用,它的值可能会被更新的堆栈框架覆盖,但是如果块从未被释放,则它仍然存在于内存空间中,从而提供可能有价值的信息。

由于缺乏替代方案,当前确定某个内存区域是否包含MMAPPED块的方法就是执行可信检验。因为仅包含MMAPPED块的内存区域也以MMAPPED块开头,如果是这样的话,可信检验将出现以下特征:

1.prev_size字段的值必须为0;

2.块的大小(没有任何标志size字段的值)必须至少与页面大小一样大;

3.块的大小必须可以被页面大小整除;

4.块的偏移量加上其大小不得超过包含内存区域的边界;

5.块的位置必须可以被页面大小整除(主要用于后续和隐藏的MMAPPED块);

6.必须取消设置PREV_INUSE和NON_MAIN_AREA位并设置IS_MMAPPED位;

只有在所有这些属性都满足的情况下,相应的内存区域才被认为包含MMAPPED块。除此之外,同一内存区域的任何后续数据都将会被逐一检测。

虽然MMAPPED块通常位于独占映射的内存区域内,但可能会发生这些块位于映射内存区域的底部,包含不同的数据,如堆栈段(参见图4)。当开始在内存区域开头的堆栈场景中搜索MMAPPED块时可能会导致误报(将堆栈数据解释为块)。所以正确的方法是使用EBP方法,扩展基址指针寄存器(extended base pointer, EBP)其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部。基本进程就是根据所有保存的EBP指针,从pt_regs结构收集的基指针开始(用于在上下文更改时保存寄存器值)。当每个EBP值指向下一个保存的EBP时,只需跟踪这些指针就会导出第一个保存的EBP,它可能位于堆栈段的开头处,该进程如图4所示。

1-s2.0-S1742287617301895-gr4.jpg

隐藏的MMAPPED块——EBP展开

一旦第一个保存的EBP的偏移量被识别出来,下一步就是顺着向后搜索第一个MMAPPED块。由于MMAPPED块仅位于可被4096(最小页面大小)整除的地址,因此仅检测此类地址。在EBP值未指向堆栈段中保存的EBP的情况下(用于承载不同的数据),该方法保持基本相同,只不过它没有EBP展开,这也意味着它也不会在内存区域向后搜索。

如果丢失的MMAPPED块不在堆栈段之后,而是隐藏在其他地方,则必须扩展搜索范围,同时增加误报的风险。唯一可以排除的区域是已经检测过的堆栈和堆段以及那些包含映射文件内容的区域(vm_area_struct结构引用文件对象),因为他们不能确定是否包含其他数据(因为这些区域代表文件的内容)。此示例的搜索进程保持不变,但没有EBP展开。在内存区域中第一次命中之后(无论是在堆栈之后还是其他地方),第一个MMAPPED块都将被用于遍历潜在的后续块。

应该注意的是,为了防止误报,只有当关于MMAPPED块的当前信息看起来不正确时,HeapAnalysis类才会开始搜索隐藏的MMAPPED块,这将在下面的章节中进行解释。

本文翻译自:https://www.sciencedirect.com/science/article/pii/S1742287617301895如若转载,请注明原文地址: http://www.4hou.com/technology/15946.html
点赞 5
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论