增强Linux系统对缓冲区溢出攻击抵御能力的方法

2015-06-08 08:20吕洪艳
系统仿真技术 2015年2期
关键词:缓冲区内存代码

吕洪艳,李 荟

(东北石油大学计算机与信息技术学院,黑龙江大庆163318)

1 引 言

2 PaX功能简介

PaX是Linux系统核心的安全补丁包,它给系统带来了安全方面的新希望。PaX是Hardened Gentoo Linux系统的组成部分,它的部分特性也被OpenBSD操作系统采用。

由于操作系统并不知道一个应用程序的具体功能是什么,所以操作系统仅能从控制程序的执行环境出发来增强安全性。为了阻止有人利用现有操作系统和应用程序的弱点获取非正常的访问权利,操作系统必须阻止程序在执行的时候有以下情况发生:①引入和执行没有安全保证的代码;② 不按正常的顺序执行现有的代码;③按正常的顺序执行现有的代码但参数被修改。

PaX应用了两种方法来帮助Linux系统抵御大多数的缓冲区溢出攻击。一是为Linux提供了“缓冲区不可执行”特性,这会阻止攻击者执行插入到缓冲区中的攻击代码;二是“地址空间的随机布局”,如果一个应用程序满足PIE(Position Independent Executable)格式,则PaX会在各个段装入内存时,采用不规则的布局方案,以增加攻击者入侵的难度。

3 缓冲区不可执行

设置攻击代码所在的内存空间为“不可执行”,这样即使程序中的缓冲区被溢出也不能执行攻击代码,这种技术被称为不可执行缓冲区技术[3]。

在早期的Unix系统中,只允许程序在代码段中执行[4],但近来的Unix和Windows系统由于要实现更好的性能和功能,往往在数据段中动态地放入可执行代码,这也是缓冲区溢出问题的根源。由于绝大多数程序不会在栈段里放置可执行代码,所以通过禁止执行栈段内的代码可以提高系统的安全性。“不可执行的栈”就是如果检查发现返回地址在栈中,则禁止执行这些代码,报告错误并记录。设置“不可执行的栈”,仅能对付“把攻击代码植入栈中”这种最普通的攻击方式,但攻击者还有其他的方法,例如把攻击代码植入“堆空间”和“静态数据段”中。利用PaX则可以实现更为全面的保护。

PaX的“缓冲区不可执行”机制有三个特性:第一个特性是为内存页增加的“不可执行”属性(由于IA-32体系结构具有“分段管理”和“分页管理”两种方式,所以这部分由PAGEEXEC和SEGMEXEC两个模块组成);在操作系统核心可以实现“不可执行”的页之后,就可以获得第二个特性——将栈段、堆空间、内存的“匿名映射”所在的内存区标记为“不可执行”,并且在 ELF(Executable and Linkable Format,可执行可链接格式)文件映射到内存时将所有没有“可执行”标记的数据区都缺省标记为“不可执行”;第三个特性就是修改mmap()和 mpretect()功能(由MPROTECT模块实现),防止它们在执行时会破坏数据区的“不可执行”状态。PaX的这三个特性实质上是增强了系统内存访问控制,来阻止没有安全保证的代码被执行。下面详细介绍一下各个模块的实现原理。

由上述分析可知,在X = 20D处管道内的二次流已经基本消失,颗粒的体积分数分布基本恢复到直管段充分发展态,对比图6b)、图6c)和图7b)、图7c) X = 5D和X = 20D处各曲线可看出,在X = 5D处的体积分数曲线较X = 20D处仍有较明显的变形。由于在X = 5D处泥浆不再受到离心力的作用,颗粒分布的改变主要由残留的二次流涡流作用引起,故从颗粒体积分数分布的梯度可反映出残留二次流的强度。

PAGEEXEC:此模块为“分页管理”下的IA-32系列处理器实现了具有“不可执行”的页。从80386开始的IA-32系列处理器都支持存储器分页管理机制。段管理机制实现虚拟地址到线性地址的转换,分页管理机制实现线性地址到物理地址的转换。如果不启用分页管理机制,那么线性地址就是物理地址。

IA-32中页目录表和页表中的表项都采用如表1所示的格式。

表1 IA-32中页目录表和页表中的表项Tab.1 List items of contents page table and page table in IA-32

IA-32不仅提供段级保护,也提供页级保护。在表1所示页目录表和页表的表项中保护属性位R/W和U/S就是用于对页进行保护。其含义如表2所示。

从安全性的角度来说,如果程序地址空间中的某些数据不需要被执行,那么它们就不应该具有被执行的能力,就应该把这些数据标记为“不可执行”。更进一步,如果一个程序不会在运行的时候动态产生程序代码,那么它也不应该具有这样的能力,所以,核心应该有能力阻止内存页的状态在“可写”和“可执行”之间转换或同时拥有“可写”和“可执行”属性。由于IA-32硬件结构的限制,不能直接实现“不可执行”属性,所以PaX利用PAGEEXEC模块为“分页管理”下的IA-32系列处理器实现了这一功能。

表2 IA-32中页目录表和页表中的表项中R/W和U/S的含义Tab.2 Meaning of R/W and U/S in the list items of contents page table and page table in IA-32

从Pentium系列开始,Intel的CPU都将原来的一个TLB(Translation Lookaside Buffer)分割成了两个,也就是说为代码和数据的虚实地址转换各建立了一个Cache[分别称为ITLB(Instruction-TLB,指令页表缓存)/DTLB(Data-TLB,数据页表缓存)]。使用了TLB,可以大大加快存储器的访问速度。作为一种用途特殊的Cache,TLB容量是有限的,当TLB装满数据的时候,CPU就会淘汰旧的数据让出空间装新的数据。在IA-32中这个过程由硬件自动实现,如果硬件在执行时发生错误,就会触发“页错误”异常,转由软件来解决,这里的软件(也就是异常处理程序)就是实现内存页“不可执行”属性的关键。如果有内存页需要设置为“不可执行”,则将此页页表中的U/S位设置为“0”,就是设置为需要“系统特权”才能访问。这样在用户态下进行这样的页面访问就会引发异常,在此异常处理程序中,会判断这是要“执行指令”还是正常的数据读写(通过比较当前指令的地址和产生错误的目标地址就可以做出判断)。如果是要“执行指令”,这就检测到了试图执行“不可执行”的数据;如果是正常的数据读写,就将页表中相应表项装入DTLB中并将U/S位设置为“1”,以便进行用户级访问。注意:由于只有DTLB中的U/S位设置为“1”而页表中的U/S位仍是“0”,所以在下次访问同一页时还会触发异常,所以不会给攻击者留下可乘之机。简而言之,PAGEEXEC是在不影响数据访问的情况下,“重载”了U/S位来标记内存页的“可执行/不可执行”属性。

SEGMEXEC:IA-32系列也可以不启用分页管理机制,这时利用此模块实现“不可执行”属性。SEGMEXEC的工作道理很简单:把3GB的用户内存空间分为两份,限制数据段描述符访问0~1.5 GB 这个区间,代码段描述符访问 1.5~3 GB这个区间。这样的划分,就可以将访问普通数据(不可执行)和读取指令(可执行)区分开,在0~1.5 GB范围内读取指令就会引发异常。

MPROTECT:此模块用来对 mmap()和mpretect()原有功能加上必要的访问控制,以便阻止在特定的地址空间中引入新的“可执行”数据。新的访问控制功能用来完成如下功能:

(1)阻止创建“可执行”的匿名映射;

(2)阻止创建“可执行”并且“可写”的文件映射;

(3)阻止将“可执行”但“只读”的文件映射转变为“可写”的文件映射,除非是对ET_DYN格式的ELF文件进行重定位;

(4)阻止将“不可执行”的文件映射转变为“可执行”的文件映射。

在Linux的核心有一个vm_flags域用来存放每个映射的访问属性。PaX设置了四个预定义值来说明每个映射区的状态:VM_WRITE,VM_EXEC,VM_MAYWRITE,VM_MAYEXEC。如果某个内存区具有VM_WRITE属性,那么它也一定具有VM_MAYWRITE属性;VM_EXEC和VM_MAYEXEC的关系也类似。

在正常的Linux的核心中,某个映射区可以同时拥有“可执行”和“可写”属性,而PaX就是要阻止这种情况出现,以便防止用户可以随意在“可执行”的内存区引入新的可执行代码。所以,映射区的所有合法状态只有四个:VM_MAYWRITE,VM_MAYEXEC,VM_WRITE|VM_MAYWRITE,VM_EXEC|VM_MAYEXEC。由于这样的设计,攻击者就只有一种方法可以在“可执行”的内存区引入新的可执行代码,那就是使用带有PROT_EXEC参数的mmap()来引入一个文件。这样一来,进攻的意图就过于明显了,很容易被其他方法阻止。

对于大部分合法程序而言,不会在内存的“不可执行”区放置代码,但在Linux中也有两个“常见”的例外,这时的可执行代码必须放置在栈段中:①信号传递:Linux通过向进程栈填写代码然后引发中断来执行栈中的代码以便实现向进程发送 Unix信号。② GCC(GNU Compiler Collection,GNU编译器套件)的函数嵌套:GCC在栈段中放置了可执行代码以便支持函数的嵌套定义和引用。为了支持这两种情况,PaX使用EMUTRAMP模块来模拟这两个程序段的执行。无论何时,如果试图执行“不可执行”区中代码,PAGEEXEC或SEGMEXEC模块都会触发一个“页出错”异常,所以EMUTRAMP模块就放置在这个异常处理程序中,来把“不可执行”区中代码取出来执行。

4 地址空间的随机布局

随着开放源代码的出现和普及,虽然共享软件的稳定性在不断增加,但软件中的弱点也更容易寻找,例如可以很容易地从源代码中计算出栈的帧地址。很多著名的共享软件像bind,wu_ftp,apache和 sendmail,等等,都被找出过漏洞并被攻击。

ASLR机制的原理是通过在虚拟地址空间中的各个段的位置上引入随机性,来阻止攻击者利用那些必须预先知道内存地址的系统弱点。一般情况下,Linux系统的虚拟地址空间的布置方案如图1所示。

图1 Linux系统地址空间布置方案Fig.1 Layout scheme of system address space in Linux

一些攻击方法利用了某些程序在段中存在固定地址(例如当前栈指针或者动态库的地址)的弱点,修改这些地址指向已经注入的攻击代码来发动攻击。ASLR可以随机设置栈段、堆空间、动态库和可执行段的位置,令上述进攻方法失去作用。虽然攻击者可以对地址进行一些猜测,但猜对的可能性很低,而且猜错后攻击程序将试图访问“不可执行”区,就会被NOEXEC发现。由于程序每次执行时都会设置新的随机位置,所以上次猜测错误不会对下次猜测有任何帮助。

ASLR由四个部分组成:RANDUSTACK,RANDKSTACK,RANDMMAP,RANDEXEC。

RANDUSTACK:此模块负责产生用户栈的随机地址。程序在执行execve()系统调用的时候,Linux核心会建立程序的用户栈,这个过程分为两步:首先核心分配适量的物理内存页,然后把这些内存页映射到程序的虚拟内存空间中。在IA-32系列中,Linux核心会把栈段映射到地址空间的最高端(地址为0xbfffffff),并向下增长(图1)。在这两步实现的过程中,RANDUSTACK都会引入随机值来修正:物理内存地址的随机修正量为4KB(过大的物理内存地址的随机修正量可能会形成内存碎片),虚拟内存地址的修正量达256MB。

RANDKSTACK:此模块负责为程序核心栈引入随机性。每个程序都会拥有大小为两个内存页的核心栈,在发生系统调用、硬件中断和CPU异常的时候,系统会从用户态切换到核心态,这些内存页供核心在此时使用。在这些处理结束的时候,系统会从核心态切换回用户态,正常情况下,这时的核心栈是空的。这就使得在每次使用核心栈之前可以为它分配一个新的地址,而不是像用户栈那样在整个程序执行期间地址保持不变。因为系统调用是潜在的攻击目标,每次进行系统调用时,核心栈的地址都是不同的就成为一个重要的优点。RANDKSTACK为核心栈引入的随机修正量为128个字节[5]。

RANDMMAP:此模块为所有的文件映射和匿名映射建立随机性,包括系统调用brk()和mmap()、堆空间的管理、装入可执行文件或者库。这些内存映射由核心的do_mmap()接口来完成,所以RANDMMAP模块就“挂钩”在这个接口上。Linux核心在分配堆空间的时候,总是从未使用的物理空间的最低端开始寻找第一个足够大而又未使用的区域分配出去(即首次拟和算法)。RANDMMAP在这个过程中增加了两个随机量:搜寻物理空间的起点不再是从最低端开始,而是加上了4K的随机修正量;而虚拟内存地址也具有的256MB的修正量。在把文件映射到内存的时候仅为虚拟内存地址提供了256MB的修正量。只有ET_DYN格式的ELF文件的映射是由RANDMMAP管理的,而EF_EXEC格式的ELF文件由RANDEXEC来管理。

RANDEXEC:在编译器链接EF_EXEC格式的ELF文件的时候,假设这种文件只会在固定的地址上执行,所以不需要过多的重定位信息,这就导致为这种文件的内存映射建立随机性是比较困难的。解决的办法是为EF_EXEC文件在内存中建立两个完全相同的映射区,第一个在它的原始地址上,另一个在一个随机地址上,而第一个映射区被标记为“不可执行”。如果试图执行第一个映射区中的代码,就会产生“页出错”异常,在异常处理程序中会把页面“重定向”到“随机区”去。自动的“重定向”功能会使随机性的引入失去意义,所以要在“重定向”之前进行各种检查;例如如果有人利用“重定向”功能发动攻击,就会在用户栈中留有相应的信息,这时RANDEXEC 就会终止“重定向”功能[6]。

5 PaX的防护效果和未来趋势

按攻击的目标分类,缓冲区溢出攻击方法共有 20 种[7-8],PaX 对各种攻击方法都有很好的保护。NOEXEC模块会阻止程序执行不安全的代码;ASLR模块可以阻止那些必须利用预先知道内存地址的弱点才能发动的攻击。有很多人研究过怎样突破PaX的防护,但很少有成功的。

在上面列出的三种必须阻止的程序异常行为中,NOEXEC模块会阻止情况①(见本文2节)的出现,ASLR模块仅能以一定的概率阻止情况②和情况③的出现。完全阻止后两种情况的出现是要付出很大的性能代价,所以PaX的未来研究目标就是怎样在安全性和性能之间做出合理的折中。

6 结 语

本文详细描述和分析了Linux系统缓冲区溢出的核心防卫方法。由于该攻击方式目前很常见,难以在短期之内彻底解决,并在未来的多年一直困扰着计算机安全,因而目前该问题的研究工作仍具有实际意义。

[1] John Wilander,Mariam Kamkar.A comparsion of publicly available tools for dynamic buffer overflow prevention[C]∥Network and Distributed System Security Symposium Conference Proceedings.California,USA:[s.n.],2003:23-24.

[2] Crispin Cowan,Perry Wagle Buffer.Overflows:attacks and defense for the vulnerability of the decade[C]∥DARPA Information Survivability Conference and Exposition.Hilton Head,USA:[s.n.],2000:119-129.

[3] 赖隽文,杨寿保.Linux系统中防止缓冲区溢出型攻击的新技术探讨[J].计算机应用研究,1999,11(11):11-13.LAI Junwen,YANG Shoubao,The new technology discussion of preventing buffer overflow attacks in Linux system[J].Application Research of Computers,1999,11(11):11-13.

[4] 孙文豪.缓冲区溢出的安全隐患[J].网络安全技术与应用,2010(5):9-10.SUN Wenhao.The safe hidden trouble of buffer overflow[J].Network Security Technology & Application,2010(5):9-10.

[5] 宁蒙.网络信息安全与防范技术[M].南京:东南大学出版社,2006.NING Meng.The network information security and prevention technology[M].Nanjing:Southeast University Press,2006.

[6] 卿斯汉,蒋建春.网络攻防技术原理与实践[M].北京:科学出版社,2004.QING Sihan,JIANG Jianchun.Network attack and defense technology principle and practice[M].Beijing:Science Press,2004.

[7] 陈莉君.Linux操作系统原理与应用[M].北京:清华大学出版社,2006.CHEN Lijun.Principle and application of Linux operating system[M].Beijing:Tsinghua University Press,2006.

[8] 杨明军.实战Linux编程精髓[M].北京:中国电力出版社,2005.YANG Mingjun.Actual programming essence of Linux[M].Beijing:China Electric Power Press,2005.

猜你喜欢
缓冲区内存代码
笔记本内存已经在涨价了,但幅度不大,升级扩容无须等待
“春夏秋冬”的内存
创世代码
创世代码
创世代码
创世代码
基于ARC的闪存数据库缓冲区算法①
一类装配支线缓冲区配置的两阶段求解方法研究
内存搭配DDR4、DDR3L还是DDR3?
初涉缓冲区