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字段。
内存中分配的块
每个块的大小和地址是一致的,这意味着它可以分别在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结构。
内存中的 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,它可以将之前描述的所有分析结果都呈现出来。
上文我们对解析用户空间进程堆的动机和历史,做了一个简要的概述。另外,我们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字段。
内存中分配的块
每个块的大小和地址是一致的,这意味着它可以分别在x86和x64架构上分别被8和16整除。在MMAPPED块的示例下,大小和地址能够被页面大小均匀地整除。这是由于mmap API函数返回了请求块的内存空间,它不仅返回大小,还返回页面大小边界上的地址。此外,尽管每个MMAPPED块都是由单独的mmap调用产生的,但是多个MMAPPED块最终可能会出现在一个vm_area_struct结构描述的内存区域中,因为内核可以很容易地扩展一个区域。另一方面,如果释放了位于同一区域的两个或多个MMAPPED块之间的MMAPPED块(其相关页面被返回到操作系统),则连续内存区域可以被分割成两个独立的区域。与正常分配的块类似,用户或进程数据块在size字段之后立即被分配,但不包括下一个块的prev_size字段,因为它不能保证MMAPPED块后面紧跟着另一个块。
www.wnhack.com
释放的块会以如上所述的方式链接在一起,最终形成一个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结构。
内存中的 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,它可以将之前描述的所有分析结果都呈现出来。
www.wnhack.com