UserLAnd是一款完全免费的Android应用程序,可以快速轻松地安装Linux发行版,而无需任何支持。研究人员最喜欢的发现软件漏洞的技术之一是将安全工具从一个研究领域重新定位或扩展到另一个研究领域。一个很好的例子是将流行的Linux内核模糊器syzkaller 移植到macOS上时,他们发现了macOS内核中的几个漏洞,其中包括两个他们能够利用其执行本地特权升级的漏洞。
最近研究人员一直在分析RHEL 7.7内核,为了帮助分析内核,研究人员修改了内核的几个部分以与通常用于Linux用户域漏洞研究的现有工具一起使用。与Linux内核相比,Linux用户态环境提供了广泛的工具来帮助进行漏洞研究。你可以点此文查看一些漏洞研究工具,以及它们研究漏洞的过程。
选择研究目标
尽管大多数内核过于混杂,无法移植到独立的Userland程序,但有些内核仅使用最少数量的内核API调用。因此,研究人员集中精力仅分析易于移植到用户领域的组件。将代码移植到用户环境的两个简单目标是Linux内核的ASN.1解析器和inet_diag字节码解释器。 Linux内核的ASN.1解析器用于解析X.509证书,PKCS7 crypt_grphic消息和RSA密钥。sock_diag子系统中使用inet_diag字节码解释器来确定从哪个套接字收集信息。用户通过netlink消息传入字节码程序,Linux内核验证字节码,然后在一个简单的解释器循环中运行它。GRIMM的GitHub 存储库包含每个移植的解析器的代码,但是本文只讨论X.509解析器。
将这两个组件移植到用户区相对简单,可以将ASN.1解析器和inet_diag字节码解释器使用的内核API交换为类似的userland API,即kmalloc变为malloc,pr_debug变为printf等。为简单起见,研究人员选择简单地删除多精度函数调用而不是移植内核多精度整数(MPI)库。 Linux内核的MPI库最初是从GnuPG提取的,作为OSS-Fuzz的一部分,该库已经接受了大量测试。移植了这些组件之后,研究人员创建了测试程序,该程序将读取文件并调用适当的API函数。
AFL
AFL是一个基于用户域覆盖率的模糊器,已被用来查找流行软件中的大量漏洞。 AFL对目标程序进行检测,以使其在进行模糊测试时能够接收基于覆盖率的反馈。该反馈显着改善了目标程序的功能覆盖范围,增加了找到易受攻击的路径的可能性。为了测试研究人员的移植库,研究人员将使用AFL。此外,研究人员将使用AFL的延迟启动和持续模式来大幅提高模糊测试的速度。 AFL的延迟启动模式允许测试程序在开始模糊测试之前启动并执行任何初始化。这允许测试程序仅在模糊之前执行一次初始化,而不是在测试每个输入之前分别执行一次。初始化完成后,通过在测试程序中调用__AFL_INIT来实现延迟启动,如下X.509证书测试程序中所示。 AFL的持久模式允许测试程序的单个实例化来测试多个输入而无需重新启动,从而节省了连续重新启动测试程序的成本。通过修改测试程序以在基于__AFL_LOOP宏的循环条件下连续读取和测试输入来实现持久模式,如下所示。
对源代码的修改以启用AFL持久性和延迟模式
一旦对测试程序进行了这些小的更改,下一步就是使用afl-clang-fast编译测试程序,并开始使用afl-fuzz进行模糊化,如下所示。
编译和运行测试程序AFL
不幸的是,经过几天的模糊处理,AFL没有在X.509解析器中发现任何崩溃。因此,我们决定继续使用KLEE来测试解析器。
KLEE
KLEE是建立在LLVM编译器基础结构之上的符号虚拟机,KLEE提供了一种象征性地执行研究人员的测试程序并查找任何内存损坏漏洞的方法。为了让KLEE知道内存中的哪些字节是符号字节,研究人员必须调用KLEE API并专门将内存标记为符号字节。在X.509测试程序中,这是通过将对read_file的调用替换为对klee_make_symbolic函数的调用来完成的。此外,研究人员的测试程序指定输入大小为符号,以便KLEE可以分析输入大小不同的测试用例。为了使输入空间易于处理,测试程序使用klee_assume函数来限制输入的大小。
对源代码的修改,以允许KLEE象征性地执行测试程序
指定了两个符号变量后,研究人员现在可以在KLEE中执行测试程序。为了简化设置,研究人员选择在Docker内部运行KLEE,具体过程,KLEE文档详细进行了介绍。因此,运行KLEE就像启动KLEE Docker容器,将X.509测试程序编译为LLVM字节码,并执行KLEE实用程序即可。
使用KLEE编译并象征性地执行测试程序
漏洞原因分析
一段时间后,KLEE产生漏洞,指示正在使用越界指针。研究人员可以查看KLEE测试用例ptr.err输出文件,以查看堆栈跟踪和漏洞说明。该文件表明look_up_OID函数正在取消引用不在有效变量内的指针。具体来说,它是fuzz_buffer变量的malloc分配之后的1024个字节,总共只有1024个字节。因此,它正在访问此缓冲区末尾之后的内存。
KLEE的符号执行生成的漏洞报告
使用ktest-tool python脚本,研究人员可以检索导致越界内存读取的输入。此外,当研究人员在x509_debug测试程序中运行输入时,如下所示,研究人员可以看到x509_note_OID函数在fuzz_buffer的末尾被赋予了一个指针,并被错误地告知有1个字节可用。
使用ktest-tool,研究人员可以生成通过KLEE发现的漏洞输入并对其进行分析
通过查看ASN.1解码器的代码,很快就会发现,仅在使用多字节ASN.1长度规范时才执行长度检查。当ASN.1规范中的项目长于127个字节时,长度将编码为以下N个字节,并且length字段用于指定包含输入长度的字节数。长度字段的最高位0x80被设置为指示使用多字节格式对长度进行编码。但是,当长度为127个字节或更少时,该长度可以直接编码为一个字节的长度字段。在下面显示的ASN.1解码器代码中,研究人员可以看到仅在使用多字节格式并且在length字段中设置了0x80位时才执行长度检查。因此,当测试输入的最后一个字节为06 01,即对象标识符(OID)的长度为1时,没有验证输入的长度是否包含足够的OID字节,并且x509_note_OID函数被赋予了一个越界指针和不正确的大小。
Linux内核的ASN.1解析器中的漏洞代码
影响分析
现在,研究人员已经确定了漏洞的根本原因,是时候确定漏洞的危害和影响了。此漏洞允许攻击者在分配结束后读取数据。在非常罕见的情况下,分配的数据位于页面的末尾,而没有映射相邻的页面,这可能会导致内核崩溃。实际上,这种情况不太可能发生。相反,可能会将敏感的内核内存泄漏给攻击者。但是,由于稍后在解析过程中会进行检查,所以不太可能在触发此漏洞的同时成功完成ASN.1对象的解析。因此,攻击者将无法检索已解析的项并直接获取泄漏的内存。
内存泄漏的另一种潜在途径是通过内核中的print语句,x509_note_OID函数将打印它无法识别的任何OID。这样,攻击者可能会通过dmesg实用程序读取内核日志来潜在地了解泄漏的内存。但是,默认配置中未启用x509_note_OID中的print语句,并且没有root权限也无法启用。下面显示了一个启用打印语句并触发内核中的漏洞以打印出边界字节的示例。
用于泄漏内核内存的几个字节的错误的示例输出
漏洞修复
在完成对漏洞的分析之后,研究人员决定确定有哪些Linux发行版也受到了影响。但是,研究人员很快发现该漏洞已在2017年的主线内核中被发现并修复了。因此,我的问题变成了,为什么开发人员不在其内核中修补此漏洞?截至撰写本文时,尽管已修补RHEL 8.1并且仍支持RHEL 7.7,但RHEL 7.7内核中的ASN.1解码器尚未修补此漏洞。一种可能的解释是,修复了此漏洞的原始修补程序并不认为该漏洞与安全性有关,因此开发人员未将其包含在RHEL 7更新中。鉴于漏洞的影响很小,这可能是一个合理的决定。
但是,这在Linux内核开发过程中并不少见,过去其他影响较大的信息披露漏洞也没有收到CVE。在这些情况下,开发人员可能无法识别补丁的安全相关性,因此,尽管RHEL 8.1(基于较新的主流内核)已进行了一些修复,但仍可以在RHEL 7.7内核中保留未修补的漏洞。
总结
这篇文章详细介绍了使用userland工具分析Linux内核的过程,将内核的相关部分移植到用户空间后,使用KLEE符号执行引擎检测到越界读取漏洞。通过分析此漏洞,研究人员确定该漏洞具的破坏性较低,并且它在2年前就已经在主线内核中得到了修复。然而,这个漏洞在RHEL 7.7中还没有被修复,尽管RHEL 8.1中提供了修复,这可能是由于最初的补丁开发人员不认为该漏洞有什么威胁。
本文翻译自:https://blog.grimm-co.com/post/analyzing-the-linux-kernel-in-userland-with-afl-and-klee/如若转载,请注明原文地址: