前两篇文章(第一篇、第二篇),我们详细的对解析用户空间进程堆的方法进行了阐述,不过理论还需实践的检验,本篇文章,我们就接着将前面所讲的方法和理论进行评估,并将它们用于实际案例中就行检测。
结果评估
本节描述了HeapAnalysis类及其插件的评估。
结果验证
结果验证是对上述所讲的方法和技术可靠性的重要验证进程,所有测试均可以在以下环境中进行,且验证进程支持不同的Glibc版本:
1.Arch Linux 32位,×86,内核版本4.4.5-ARCH,Glibc版本:2.20,2.21,2.22,2.23和2.24;
2.Arch Linux 64位,×64,内核版本4.4.5-ARCH,Glibc版本:2.20,2.21,2.22,2.23和2.24;
HeapAnalysis类实现了多个函数,这些函数会将当前检测的数据与我们在处理内存时对每个块的期望进行比较:
1.测试正确的标志,例如,线程arena中的块的NON_MAIN_ARENA;
2.块的地址是否一致;
3.大小检测(例如是否一致);
4.分配状态测试:是否可能是任何bin或fastbin分配的一部分?
5.对于MMAPPED块,还要进行一些额外的测试(有关更多详细信息,请参见MMAPPED区域);
对于MMAPPED块,还有另一个涉及malloc_par结构的验证步骤。 glibc一方面使用此结构的构建来控制某些全局设置,另一方面用于计算MMAPPED块的总大小和数量。这是通过该结构的唯一实例(名为mp_)完成的,它位于映射的Glibc库中。如果提供了mp_的偏移量,则HeapAnalysis类将使用该结构来验证所有已识别的MMAPPED块的数量和大小。如果存在任何差异,它会尝试识别隐藏的MMAPPED块,如果识别不出来,则会发出警告。此外,malloc_par结构会将所有块和堆相关结构的大小分别与相应的vm_area结构的大小和所有arena的大小进行比较。
完整性验证
虽然这项工作目前还无法完成全部的Glibc堆实现,但这并不妨碍开始尝试用各种步骤来识别与内存取证相关的所有相关信息和方案。我们试着开发了一套测试程序,该程序涵盖了我们所知道的所有特殊情况,以提供最完整的检测,最终,我们将使用这些测试用例自动验证我们实现的输出的完整性。为简明起见,此文我们不会对这套测试程序进行详细讲解,不过你可以在完整技术报告中看到整个细节。
真实应用案例分析
本章节将演示我们的插件在真实世界示例中的应用,演示过程中,请注意两个方面,一方面注意插件是否是通过描述分析进程本身来实现,另一方面注意是使用的插件还是在整个堆中进行原始搜索的特点来实现的。在下面的小节中执行的分析是使用“黑盒测试方法”完成的,这意味着,如果没有另外指定,那么没有必要预先收集来自源代码的与进程相关的细节,比如结构定义。
zsh示例
之前描述的Rekall和Volatility的bash插件在整个堆空间中先会搜索以标签为前缀的时间戳字符串,然后再会搜索指向时间戳的历史结构,以便识别发出的命令字符串。插件的输出是命令条目列表,每个命令条目都是由发出的命令和相应的时间戳组成。我们的目标是为zsh识别相同的信息。本节中的相应分析已在结果验证部分中列出的相同环境中完成。
当使用history命令检测历史记录时,要分析的zsh进程在其历史记录中包含142个已执行的命令:第一个是ps aux,最后一个是#[email protected] &*()。分析进程的第一部分是以“黑盒测试方法”完成的,这意味着没有关于zsh如何存储命令或时间信息的内部信息是预先以任何方式收集的。这种方法的唯一信息是关于bash命令分析的现有信息,第一次检测尝试是查找以标签为前缀的时间戳字符串。然而,Zsh似乎没有以与bash相同的方式存储时间戳。于是下一步是使用heapsearch插件在块中的某处搜索已发布的命令。列表1显示了使用heapsearch搜索字符串#[email protected]%&*()的结果,其中显示了地址0x09BCF830处所包含的感兴趣的命令块。
列表1
使用heapsearch搜索包含已发布的zsh命令的块,虽然此命令有时可以在多个块中进行识别,但每个命令似乎至少会在分配的块中识别一次,该块仅包含命令和末尾保留的一些尾随字节。列表2显示了包含已发布的zsh命令块的hexdump。
列表2
包含已发布的zsh命令的块的Hexdump,由于这些块没有提供任何元信息(例如,在何时发出命令),所以下一步是再次使用heapsearch插件找到指向这些块的指针,但这次提供了包含发出命令的块的地址。你可以在单独分配的块中找到每个测试命令的一个指针,其大小为56字节(针对×86环境)。
在检测多个块(使用heapdump插件的转储内容)之后,可以得到以下信息:
1.字节5-8包含指向已发出命令的指针
2.字节25-32是2个时间戳,存储为4字节整数,其中前四个字节是发出命令的开始时间,后四个字节是命令结束的时间。
3.字节41-44包含命令计数器;
当使用heaprefs插件检测这些块以获取引用时(参见列表3,输出已经被删除和修改),它显示字节1-4,5-8(命令指针),13-16,17 -20和33-36指向其他块。这些块的起始地址列在“注释(Comment)”列中,而包含指针的字节在“数据(Data)”列中用方括号进行了标记。
列表3,使用heapref分析引用的块
通过将这些信息与zsh的源代码相结合,你就会在相关的历史条目结构histent中发现两个指针:fields down(向上的字段)(字节17-20)和fields up (向下的字段)(字节13-16)。由于这些字段会用来引用上一个或下一个histent条目,因此允许可靠地遍历histent实例。由于内容条目的链接列表是循环的,所以按着一个方向遍历,就足以获得所有histent条目,其他指针对于当前的检测并不重要。
现在,我们的最后一项任务是构建一个插件,以方便自动提取这些命令信息。为了能够遍历该histent列表,第一步是可靠地识别一个histent条目。由于命令可以包含在各种大小不同的块中,并且不提供任何可搜索的模式,所以这种方法是查找包含histent结构的块。对于×86而言,包含块的大小为56字节,对于×64架构,包含块的大小为96字节。因为还存在具有相同大小的非相关块,所以需要区分它们。由于每个histent条目都应该有一个指向包含命令的块的指针和指向下一个和上一个histent条目的指针,因此测试会包括检测这些指针是否引用已知的块。如果测试结果为正,最后一个检测则是遍历向上和向下指向下一个和上一个histent结构的指针,并测试它们的向下或向上构建是否指向当前块。如果是这种情况,则将当前块视为一个histent 结构,并使用向下构建遍历命令历史记录。
列表4显示了zsh插件的示例输出 (输出已经删除)。
列表4,zsh插件的示例输出
虽然因为时间戳字符串,可以使用原始搜索重建bash历史记录,但这种方法对zsh不起作用,因为时间戳不是以带有附加标签的字符串形式保存的,而是作为一个4字节整数保存的。此外,由于在分析进程中无法识别其他可搜索的模式,因此原始搜索很可能不适用于上述分析过程,此时使用堆分析插件的优势就显示出来了。
KeePassX示例
检测的第二个工具是密码管理器KeePassX(版本0.4.3),我们已在以下环境中进行了测试:
1.Ubuntu 15.10 32位,×86,内核版本4.2.0–16-generic,Glibc2.21版本;
2.Ubuntu 15.10 64位,×64,内核版本4.2.0-16-generic,Glibc2.21版本;
以下分析的设置由KeePassX数据库组成,该数据库包含多个密码条目,这些密码条目已被分为两个文件夹。每个密码条目都有标题、用户名、URL和注释的值。打开数据库进行分析时,只打开第一个文件夹,同时保持第二个文件夹完全不变。
我们的第一次尝试是在进程空间的某处找到未加密的主密码(master password )和条目密码,但找不到它们。但是,在5个带有不同密码管理器条目的测试中,我们已在3个已分配的块中成功观察到当前打开的密码条目的未隐藏密码。只要密码条目显示未隐藏的密码,这三个块就会一直存在。如果密码再次隐藏,三个块中的两个将被释放,保留其中一个进行分配。只有当输入窗口关闭时,包含密码的所有块才被释放。根据释放的块的大小,密码将在几毫秒(几分钟,甚至几个小时也有可能)内被重写。虽然释放的块(包含长度在1到40之间的密码)通常在几秒钟或几分钟内重新分配,但在某些情况下,包含同样大小密码的释放块会被合并到更大的释放块中。由于这些更大的块由于太大(例如在几百字节的范围内)不经常分配,密码可能在该块中可能保持几个小时,但也更难被找到(因为它被其他数据包围)。此外,在块数据部分的前18个字节中从未观察到实际密码,这意味着即使在释放的块被合并到大型bin中,密码也不会通过×86架构上的任何bin指针被覆盖。
在寻找到密码字段之后,下一步是寻找更多感兴趣的字段,这个分析过程中选择的字段是标题、用户名、URL和注释。 KeePassX在数据库被打开后,会立即将完整的字段内容存储在已分配的块中。这不仅适用于标题、URL和注释等字段,还适用于用户名字段,由于在KeePassX的示例中,该字段仅在概述中以星号显示,因此不应在堆内进行再加密。综上所述,如果密码数据库被打开而未被锁定,则可以从堆中提取所有文件夹中所有密码管理器条目的概述(密码字段除外)中的所有字段。为了分析和比较来自不同块(包含字段字符串)的数据,我们已使用heapdump插件将它们转储到单独的文件中。列表5显示了一个转储数据块的十六进制转储输出,该数据块包含一个用户名(在本例中是yyyyyyyy_yyy_user5_aaaaaaaab):
列表5,包含KeePassX用户名字段的块数据部分的十六进制转储
在比较包含来自相同类型的字符串(例如用户名)和来自其他类型的字符串的不同块之后,可以导出以下属性(对于未隐藏的密码也是如此):
1.字符串总是由16位的endian编码;
2.字符串并不是从块的数据部分的开头开始的,而是在18字节之后,这很可能是因为字符串是结构或对象的一部分;
3.字节5-8和9-12分别与字符串的大小相关,而字节9-12表示正确的大小(大小是由编码的字节序列表示的字符数,而不是字节数),这两者都可能是四字节无符号整数的实例。字节4-8中的值恰好比字节9-12中的值大1倍(请参见列表5:0x19与0x18);
4.字节13-16指向字符串的开头;
5.该字符串后会紧跟4个空字节;
6.根据字符串的大小,最后有一些额外的字节(某种填充字节),在大多数情况下从0到6个字节不等。然而,很少有这种情况,即这个额外的字节会达到14个字节(6加上直到下一个更大的块大小的字节数);
字节1-4没有改变,而字节13-18和字符串结束后的字节不但变化很大,它们也没有显示出任何与某个类型或密码管理器条目的可靠一致性。
于是,下一步我们就是使用heapsearch插件搜索任何指向字段字符串的指针。虽然搜索字符串的起始地址没有显示任何引用(包含在同一块中的引用除外),但搜索这个块的数据部分的开头处会显示出另一个块中至少有一个指针。使用heaprefs插件分析这个块时,它会显示指向其他块的12个指针,其中四个指针指向包含标题、用户名、URL和注释字符串的块。在我们分析了大量的密码条目之后,就可以合理假设出每个密码条目,其中存在着大小为96字节(在×86环境中)的块,这个块至少引用了这四个字段。这意味着,通过搜索大小相同的块并检测给定偏移量的指针,就可以收集到相同密码条目的标题、用户名、URL和注释字符串,这个信息被用来创建一个概念验证插件,具体方法就是使用HeapAnalysis类,它会自动为所有密码条目提取这四个字段。列表6显示了该插件的示例输出(输出已被删除)。
列表6,KeePassX插件的示例输出
此时,你应该注意,如果没有来自有关块上下文的起始地址的信息,查找字段字符串的引用将会更加困难,而在最坏的情况下,各种字符串关联到一个密码条目的可能性也会降低。
总结
本文重点介绍了如何在Linux进程的上下文中分析堆,其研究目的是支持研究者分析用户空间进程堆中包含的数据。首先,要对Glibc的堆实现有了深入的了解,这在Glibc分析部分中有详细的介绍。该分析侧重于从内存取证角度分析堆相关数据的存储方式和存储位置。其次,这些知识已被用于构建内存取证框架Rekall插件,该框架用于分析Linux用户空间进程的堆并提供对已识别块的访问。细节在插件实现部分中有详细说明,其中特别描述了一种识别隐藏MMAPPED块的算法。由于产生可靠的结果是计算机取证的关键要求,因此部分评估涵盖了能够验证收集到的结果的信息。为了说明研究的实用性,在真实的应用程序示例中中,我们使用了zsh和KeePassX的例子描述了用户空间应用程序的黑盒分析进程。
本文研究结果的一些局限性
由于交换空间在某些情况下是用户空间进程分析进程中的关键资源,到目前为止我们的研究中还未涉及到这一点。当本次研究中引入的插件用于包含堆相关数据的交换页面的进程时,它们很可能无法可靠地分析堆并提取所有块。
本次研究的目标之一是为存储在内存中的数据提供一个类似进程的视图,不过从上述研究的结果来看,虽然我们已经搞清楚了堆在进程中所包含的详细信息(特定信息的位置及其大小),但目前还是无法提取有关数据类型的信息,根本原因是堆不存储任何数据类型信息。在已经知道数据类型和数据本身大小的情况下,仍然有一种方法可以将特定块与特定类型关联起来,通过搜索特定大小的数据块,取证人员可能能够收集特定类型的数据。然而,这种方法需要对这些块进行进一步的测试(如zsh部分所示),因为可能会有更多大小相同的块包含不同的数据。
如上所述,对特定堆的解析要依赖特定的操作系统。在对不同的堆进行解析时,已经成功测试的方法和插件很可能都不适用。到目前为止,我们所探索的HeapAnalysis类和插件仅支持在上述架构和Glibc版本上分析Linux进程。但是,在详细的技术报告中,我们提供的信息可用于增加对更多体系结构或操作系统的支持。
虽然可以在不提供调试信息的情况下分析用户空间进程的堆,但这种方法并不可靠。比如当任何相关结构发生变化(malloc_chunk、malloc_state或heap_info)时,插件在分析堆时肯定会失败,如果缺少指向全局变量mp_的指针,结果可能仍然不完整。如果没有mp_,则无法确定是否已发现所有MMAPPED块,另外由于在本例中,我们没有启动对隐藏MMAPPED块的搜索,因此输出中可能至少缺少一个MMAPPED块。
除了隐藏的MMAPPED块之外,还有一种不确定性情况,就是HeapAnalysis类可能会丢失块,如果分析进程错误地将包含整个arena的vm_area_struct丢失了,虽然测试结果似乎仍然是正确的,但是却丢失了arena的块。以上所说的这种不确定性情况,可能发生的场景是这样的:解析过程中,没有提供关于main_arena位置的调试信息,并且main_arena搜索进程无法找到它,这也意味着此进程无法找到任何线程arena。由于在该场景中没有可用于任何arena的有效指针,因此可能存在未检测到的arena,这就是说有arena未被注意到。虽然这种情况在理论上是存在的,但是在我们的评估中,丢失arena的情况却未被检测到。
未来的研究方向
由于最重要的限制是缺少对交换空间的支持,为了全面分析进程的堆,必须确保对包含堆相关数据的所有页面的访问。因此,将交换空间集成到内存取证进程中,就是进一步分析用户空间进程的关键一步。
为了增加HeapAnalysis类的支持,我们计划更多系统架构上测试插件,并在需要时进行适当调整。此外,对诸如jemalloc之类的进一步堆实现的分析允许分析来自诸如Firefox之类的应用程序的进程的堆分配。此外,对诸如jemalloc等其他堆实现的分析,还允许来自Firefox等应用程序的进程的堆分配。
总而言之,本文中介绍的插件简化了分析进程,并使用模式匹配方法识别内存中无法轻易找到的信息。这些插件和内存中有关堆对象的详细信息,也可用于支持内存取证的进一步研究。此外,本文还演示了用户空间进程的分析,同时说明了在分析进程中如何获取堆的细节信息。