前言

近来,出于兴趣、工作和课程的需要,我将许多精力放在了漏洞研究上,主要是对Linux内核漏洞的研究。除了阅读博客、论文资料,以及搭建环境复现一些漏洞和CTF题目外,同时我也在学习A Guide to Kernel Exploitation: Attacking the Core

今天下午,我在尝试理解CVE-2022-34918漏洞。在阅读了相关的博客文章之后,自然地从文章设置的超链接游走到被引用的多篇技术原理文章中。不觉望洋兴叹。

做的越多,学的越多,发现自己不知道的越多。一些曾经没有能力去实现的东西做出来了,一些曾经不懂的东西弄懂了,一些曾经模糊的确定了,却有了更大范围的未知和混乱。因此,我希望借助这篇文章梳理一下自己目前关于漏洞研究的一些想法,整理思维,从而向更深、更广处探索。受个人经历所限,下面的想法主要是关于Linux用户空间程序和Linux内核漏洞研究的。

首先,谈一谈我对于漏洞研究工作的认知。evilpan前辈曾写过一篇《漫谈漏洞挖掘》,我读了很多遍,十分受用。他对于漏洞研究工作的分类和关于不同类型漏洞研究的评论给我许多启发。evilpan将漏洞研究工作分为三类:漏洞分析,漏洞利用和漏洞挖掘。

结合个人工作经验,我想再加上一个分类:漏洞复现。从漏洞复现,到漏洞分析,到漏洞利用,最后到漏洞挖掘。由于目前为止我并未做过多少漏洞挖掘工作,所以本文不涉及这方面,等我后面有想法了再更新这篇文章。

漏洞复现

安全领域细分方向万万千千,许多同学的主要工作不是漏洞研究,日常可能并没有做过很多漏洞分析、漏洞利用和漏洞挖掘。即便如此,无论是出于兴趣还是出于工作需要,很多人应该都做过漏洞复现。客观上来说,漏洞复现不要求掌握原理,不限制复现场景。一种常见的漏洞复现工作是在渗透测试中使用漏洞进行攻击;另一种常见的场景则是入侵检测防护规则维护运营工作。

在前一种场景中,漏洞复现的环境通常不是自己搭建的,最终目标是通过漏洞复现(利用)来达到远程控制、权限提升等目的。由于一个完整的渗透流程中可能会涉及到不同类别的漏洞,因此渗透测试通常并不苛求攻击者能够掌握所用到的每一个漏洞的原理,就像一般情况下战士并不需要精通每一种武器的制造方法一样。当然了,精通原理更好,这里仅仅是对客观需求进行描述。

在后一种场景中,漏洞复现的环境通常是研究者自己搭建的。为了实现针对性的检测和防护,我们通常需要了解漏洞和ExP的原理,在此基础上能够在自建漏洞环境中使用ExP完成攻击,同时使用某种技术实现的探针捕获到攻击过程中产生的事件和日志,抽取攻击特征,编写检测和防护规则。

无论如何,“漏洞复现”的基础目的和要求就是在一个存在漏洞的环境中执行某个ExP或PoC去触发漏洞、实现特定的攻击效果。在有现成ExP的情况下,“漏洞复现”看起来简单,有时却并不是那么简单,特别是对于急功近利、心浮气躁的研究者来说,相对而言很多时候都不那么简单。我有过很多这样的经历。无论是想要复现一个最近曝出的新漏洞,还是一个老漏洞,我的惯性思维总是想要执行贪心算法:

  1. 针对该漏洞,快速在GitHub、Google上搜索已经写好的ExP,最好还是跨版本通杀的ExP。
  2. 在第1步实现的情况下,看一下漏洞公告中描述的漏洞影响范围,快速搭建一个受影响的漏洞环境,在环境中编译运行ExP,观察效果。
  3. 在第1步中只找到了针对特定版本的ExP的情况下,部署一个与ExP作者描述相同的Linux发行版,通过内核升降级的方式实现与ExP作者描述相同的环境,然后编译运行ExP,观察效果。
  4. 如果没有找到针对发行版的ExP,只有针对QEMU引导启动的自行编译特定版本内核的ExP,就部署与ExP作者相同的QEMU环境,编译运行ExP,观察效果。
  5. 无论前面的每一步成功与否,至此,都要开始研究漏洞和ExP的原理了。
  6. 现实中有很多漏洞是没有公开ExP的,这时候只能自己了解漏洞原理去编写ExP了,别无他路。

想要走最少的路,用最短的时间,实现最炫酷的效果,往往却不得不最终把最长的路走一遍,其中夹杂着些许重复工作和不懂原理复现失败时的消极情绪。几番下来,我学到的第一件事是保持耐心,保持一颗平静的心。

有时候,搭建漏洞复现环境并不是一件容易的事,尤其是在国内的网络环境下,这也是我开发并开源Metarget项目的原因所在。然而,就Linux内核漏洞而言,个人认为最理想的环境还是源码编译、QEMU启动的调试环境,至少在这种环境下,在ExP没有达到预期效果时我们能够确定是ExP的问题而不是环境的问题,甚至各种调试手段能够帮助我们去找到问题。

漏洞分析与利用

通常情况下,我们进行漏洞分析的目的少不了漏洞利用,漏洞利用在一定程度上也证明了漏洞分析的正确性。因此,姑且把漏洞分析与利用放在一起进行讨论。以下是我对自己的漏洞分析利用进行归纳总结后,提出的一些关键点。在一次漏洞分析与利用的过程中,尤其是在十分复杂的场景中,我认为把握住以下这些点能够帮助我们更好地厘清思路,避免迷失在代码密林中。

1. 如何对目标漏洞点施加控制

这一点想谈的实际上是交互问题。对于有经验的人来说,这可能不是一个大问题,但是对于初学者来说,却是个比较麻烦、常常搞不清楚的问题。

对于用户态的漏洞来说,常见的交互触发方式有两种:

  1. 针对提供网络服务(包括通过UNIX socket进行通信的情况)的程序来说,对目标漏洞点施加控制的方式是按照某协议规定的格式发送数据包。
  2. 针对泛文件解析类程序(常见的Adobe、Word和FFmpeg等都可归入此类)来说,对目标漏洞点施加控制的方式是让这类程序打开并解析一个恶意构造的文件。

对于内核态的漏洞来说,由于绝大多数情况下攻击者只能从用户态发起攻击,对目标漏洞点施加控制的方式是有限的,通常只能是正常情况下用户态程序与内核进行通信的方式,如通过系统调用对内核特定功能对象进行控制、通过对存在漏洞的驱动进行读写进行控制等。综合来看,作为内核与用户空间的interface,“执行系统调用”是对内核内目标漏洞点施加控制的最主要途径。

曾经的我常常对这种漏洞利用方式感到陌生和无所适从,这主要是由于自己对于系统调用背后的功能逻辑不甚熟悉。例如,我们经常使用mmap、open、read、write、execve等系统调用,也了解每个系统调用的结果在用户空间的“现实意义”:映射文件和分配内存、打开文件、从文件描述符读、对文件描述符写、执行特定程序等。然而,每一个系统调用是怎么实现上述功能的,内核在我们发出系统调用后又做了些什么,这些我们很少了解。不了解这些,一下子提到“借助系统调用对内核目标漏洞点进行控制”,当然会觉得陌生。

然而,即使了解了某个系统调用背后实际做了什么,有时候也会陷于一种惯性思维而感到不适应。让我尝试来描述一下这种情景:第一次仔细读Linux内核漏洞利用的代码时,我看到了许多熟悉的系统调用带着陌生的参数,或者是陌生的系统调用带着陌生的参数。而且,在这类漏洞利用代码中,系统调用出现的频率通常很高,高于我们自己开发的正常应用程序——对于后者,似乎很多时候运算逻辑才占主导。

对于这种情况,我们要意识到,如果把日常编写应用程序时写下的系统调用比作充满“正向感情”的调用的话,那么在进行漏洞利用时,系统调用在某种程度上纯粹变为一种实现非正常目的的工具——在攻击者眼中,它不再与它所描述的正常功能挂钩,而是一种用于改变内核内存布局的方式。

当然了,这并不是说不去关注该系统调用的正常功能和其背后原理,而是说我们要更关注某次系统调用对内存的作用效果。实际要完成一次漏洞分析与利用通常要把这两种视角结合起来,而且以后者为主。这正是下面我们要提到的“情景模型”与“逻辑模型”。这是我给出的两个名称,也许不是很正规,但是希望能够说明我想表达的问题。

2. 漏洞的情景模型与逻辑模型

抽象来看,其实每个漏洞都有一个专属于它自己的内存模型。这个模型描述了该漏洞所涉对象在触发逻辑下的内存布局变化过程。将该模型与同步的CPU(尤其是寄存器)变化过程进行匹配,我们便能够理解漏洞原理并设计出漏洞利用代码。对于漏洞的“内存模型”,我们其实需要从两种视角去认识它,即所谓的“情景模型”和“逻辑模型”。

其中,大家可能会对逻辑模型更熟悉一些,它实际上是漏洞利用原语组成的链。在这个模型中,我们不再关注具体是什么系统调用、什么内核对象、在什么条件下被触发了什么溢出(栈、堆、整数)或什么验证绕过、逻辑错误,而是关注这种非正常触发表现在内存上的本质是什么,如任意地址写或任意地址读。我们不再关注宏观上某个系统调用表现出的具体功能,而是关注微观上为它提供特定参数后它将造成的内核内存的变化情况。“逻辑模型”提供的视角才是我们读漏洞利用代码应该采用的视角,从这样的视角出发,才不会产生陌生和不适应感,逻辑模型是“冰冷而正确”的。

相对地,“情景模型”指的就是系统调用及内核对象原始设计意图及代码功能所呈现出的情景了。什么意思呢?这其实是说,在分析漏洞可利用性和编写利用代码之前,我们可以在一段时间内关注内核开发者是如何实现某个内核功能模块的逻辑的:他的情景是什么,他的用意是什么。这些是和系统调用、内核对象上下文紧密相关的问题。理解这些将帮助我们发现漏洞利用可能性,设计更好的漏洞利用路径。从“情景模型”到“逻辑模型”的转变通常是自然而然的。例如,假设我们能够通过系统调用“对目标漏洞点施加控制”,从而在某个内核结构体中偏移靠前的数组实现溢出,能够覆盖偏移靠后位置的一个长度变量(xx_len),那么结合该对象的上下文(“情景模型”),我们可以追踪到该长度变量的引用位置,发现篡改它的值可能导致out-of-bound read、out-of-bound write,进而导致内核基址泄露或任意地址写,进入与该对象上下文无关的逻辑模型范畴。

在路上

上面的文字是我在研究Linux内核漏洞利用望洋兴叹之余的总结归纳。其中可能会有很多考虑不成熟或错误的想法,我的表达可能也不甚清晰。未来随着认识和技能的提高,我会持续更新这篇文章。

最后,引用另一位前辈VERITAS501在《基于USMA的内核通用EXP编写思路在 CVE-2022-34918 上的实践》一文结尾处的话:

随着越来越多的软硬件缓释措施不断部署,我们可以发现传统的ROP,JOP利用技术越来越难以攻破现有系统。当几年前的我听到shadow stack,control-flow guard等防御时,我曾一度以为未来漏洞利用将变成一件几乎不可能的事情。

但恰恰相反的是,我幸运地见证了越来越多新型攻击技术的诞生。例如数年前对eBPF的攻击去构造内核任意地址读写,亦或是去年 Google 的 Jin Xingyu 学长提出的 ret2bpf技术,又如今年360在BlackHat Asia上提出的USMA技巧,由DirtyPipe启发而来的Pipe原语,美国西北大学即将公开的DirtyCred技术等等。这些新型攻击技术无不为我展示了漏洞利用无穷的可能性,也让我感觉到漏洞利用中的那种艺术的美感。

我想,这也是最终让我坚定地去热爱的理由。纵然望洋兴叹,纵然攻防技术浩如烟海,但这是一泉活水,活水有源源不断的未来,而我也有一步进一步的喜悦。功不唐捐。