原文:PaX 早期设计文档概述 2003.11.29
作者:The PaX team
译者:wnereiz
1. 设计
PaX 项目的目标是研究各种针对软件开发缺陷的防御机制,这些缺陷可以让攻击者针对被攻击任务所在的地址空间行任意读/写。这类缺陷还包括其他各种形式的(基于栈或堆的)缓冲区溢出漏洞、用户提供的格式化字符串漏洞,等等。
我们要做的并不是发现和修复这些漏洞,而是要防范和抑制漏洞利用技术,认识到这点非常重要。基于此目的,这些技术可以从三个层次影响被攻击任务:
- (1) 添加/执行任意代码
- (2) 执行现有代码,但是打破了程序原本的执行顺序
- (3) 按照程序原本的顺序执行现有代码,但是加载任意的数据
例如广为人知的 shellcode 注入技术属于 (1),被称为 return-to-libc 类型的技术则属于 (2)。
将代码引入到任务的地址空间可以通过两种方式来实现——创建可执行映射或更改已有的可写/可执行映射。第一种方式可以通过控制映射到任务权限来防范,然而这已经超出了 PaX 项目的范畴,处理这种情况的最佳方法是使用存取控制系统。第二种方式可以通过完全拒绝创建可写/可执行映射来防范。然而这种解决方案会损坏确实需要执行此类映射的应用程序,除非重写这些程序以便其能够更加谨慎地处理这类映射。这是我们能做到的最优方法。这种方案的详细说明将在单独的文档 NOEXEC 中描述。
执行上述(由攻击者引入或已经存在于任务地址空间中的)代码,需要用已有的代码修改执行流程。这类修改发生在代码解引用函数指针的时候。如果指针存储在可写内存段中,攻击者便可以进行干预。虽然看上去不把指针放在可写内存段中的主意不错,但问题是这不太可能(例如,需要保存栈的过程调用的返回地址),所以要用不同的方法解决此问题。由于这类修改需要在用户态中进行,而 PaX 到目前为止是一个面向于 kernel 的项目,所以这些方法将会在未来实现,详细信息请参考单独的文档。
PaX 带来的下一类新特性是它引进了一种多样化的形式:地址空间布局随机化(ASLR)。其思想为,基于观察,在实际中大多数攻击行为都要获取被攻击任务的不同的地址。如果我们能够在每次创建任务的时候在这些地址中引入熵,那么这会迫使攻击者进行猜测或暴力查询,从而反过来使得攻击的尝试产生非常大的“噪音“,因为任何失败的尝试都可能会导致目标崩溃。这样便可以很容易地观察到这类事件的发生,并采取行动。这种解决方法的详细信息在单独的文档中有描述。
在分析上述技术之前,我们要注意一个经常被忽视或误解的组合防御机制的属性。有些人喜欢关注系统的独立片段,并依据它得出关于整体有效性的结论(或者更糟,解除某机制,原因是如果不引入另一个机制,它将变得无效。或者是相反的情况)。在我们的案例中,这种方法会导致错误的理解。我们考虑,如果有这样一个防御机制用来防御 (1) 和 (2),例如此机制利用了 NOEXEC 和 PaX 中对未来用户态的更改。若仅仅引入了 NOEXEC,有人会说这是没有意义的,因为方法 (2) 仍然可以被利用(在实践中这点经常会成为解除非可执行栈方式的理由,然而这种方式并不能与 NOEXEC 混为一谈)。如果仅仅作为 (2) 的防御措施,有人同样会说既然攻击者可以直接利用(1),那么我们为什么还要去关心它,从而得出结论说所有这些防御机制都是无效的。根据以上提示,这将得出错误的结论,部署两种防御机制将会同时保护(1)和(2)——当一个防线失效之后,另一个防线将会起到保护作用(例如 NOEXEC 仅可以使用 return-to-libc 类型的攻击进行突破,反之亦然)。
下面,我们将假设 NOEXEC (非可执行页特性和 mmap/mprotect 限制)和全 ASLR(使用 ET_DYN 可执行性)在系统中同时启用。此外,我们的系统中还需要有 PIC ELF 库,以及崩溃探测和反应系统,这样将可以在一定(低)数量的崩溃之后阻止攻击程序的执行。这样一个系统可能存在的攻击点描述如下:
- 如果攻击者不需要预先知道被攻击任务的地址,则攻击方式 (3) 具有 100% 的可靠性。
- 如果攻击者需要预先知道地址,而且可以通过被攻击任务的地址空间获取(例如,目标存在信息泄漏的 bug),则攻击方式 (2) 和 (3) 具有 100% 的可靠性。
- 如果攻击者需要预先知道地址,但不通过猜测或暴力搜索的手段便无法获取,则攻击方式 (2) 和 (3) 具有低可能性(“低“可以进一步进行量化,见 ASLR 文档)。
- 如果攻击者可以操纵被攻击任务创建、写入和映射一个文件,则攻击方式 (1) 是可行的。这反过来需要使用攻击方式 (2),所以对它的分析同样适用于此处(注意,虽然并非 PaX 的内容,但是建议在实际产品的系统中部署存取控制系统,这样可以防止此类攻击点)。
基于以上几点,毫无疑问 PaX 未来的开发方向将会是防范方式 (2) 或至少降低其有效性,并排除或减少方式 (3) 的可利用途径的数量。(当然这也有助于对其他的方式进行反击)
2. 实现
主线开发是在 IA-32 (i386) 架构的 Linux 2.4 上,然而大多数特性也已经提供了对 alpha, ia64, parisc, ppc, sparc, sparc64 和 x86_64 的支持 ,而且其他架构在可获得硬件的情况下也将得到支持(感谢 grsecurity 和 Hardened Gentoo 项目)。基于此原因,所有关于实现的文档都特指 i386 平台(通用的设计思想适用于所有架构)。
非可执行页的特性已支持 alpha, i386, ia64, parisc, ppc, sparc, sparc64 和 x86_64,而 ppc64 可以与 pcc 共享同样的实现。mips 和 mips64 架构总的来说没有希望得到支持,因为它们具有统一的 TLB (此型号有一个分支将被 PaX 所支持)。关于非可执行页以及相关特性的文档是 NOEXEC,两个针对 i386 的特定方法在 PAGEEXEC 和 SEGMEXEC 中有描述。
mmap/mprotect 的限制主要是独立于架构的,只有处理特殊情况时需要针对架构采用特定的代码(许多代码片段需要在可写但非可执行的内存中执行,例如一些架构上的栈或 PLT)。此内容的主文档是 MPROTECT,而 EMUTRAMP 和 EMUSIGRT 则描述了针对 i386 特定的模拟技术。
ASLR 同样是独立于架构的,只有一种特例,那就是不同地址的可随机化位在不同架构中是有区别的。其文档按照随机化区域拆分,所以 RANDKSTACK 和 RANDUSTACK 分别描述了 kernerl 和用户栈的特性。 RANDMMAP 和 RANDEXEC 的内容分别是关于随机化区域用于 ELF 库和可执行性的。实现 SEGMEXEC 和 RANDEXEC 的基础结构是 vma mirroring,在文档 VMMIRROR 中描述。
由于一些应用程序需要执行 PaX 所禁止的行为(实时生成代码)或需要做出 PaX 环境下不再正确的假设 (例如,在地址空间中固定了或至少预先描述出此地址),我们提供了一个工具叫做 “chpax”,它可以帮助终端用户针对各种 PaX 特性基于其可执性进行细致的控制。
Shawn注:目前(2016年3月)Hardenedlinux社区推荐使用paxctl-ng(而不是chpax和paxctl)来给二进制可执行程序打PaX flags,paxctl-ng是属于elfix项目的一个工具,在很长一段时间里都elfix都只有Gentoo才有人打包,而paxctl-ng支持XT_PAX的特性在安全运维中很多人都会使用到,目前Hardenedlinux社区有一名maintainer做elfix for Debian packaging的工作。