罗斌
摘要:缓冲区溢出是一种在各种操作系统、应用软件中广泛存在普遍且危险的漏洞,利用缓冲区溢出攻击可以导致程序运行失败、系统崩溃等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。本文分析了缓冲区溢出的基本原理及缓冲区漏洞利用的技术条件、利用途径,最后提出了Linux和Windows下缓冲区漏洞利用的防范策略。
关键词:缓冲区溢出;漏洞利用;漏洞防范
中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2018)33-0044-02
1 缓冲区溢出概述
缓冲区是一个较宽泛的概念,用于存放数据的内存空间都可以叫作缓冲区。缓冲区自身没有数据越界的防范机制,当放入缓冲区的数据超出了缓冲区的范围就叫缓冲区溢出。某些可被利用的缓冲区溢出是安全缺陷,严重威胁着计算机安全。
每个进程在内存中都有一段相对独立的存储空间,并且这段存储空间被分为了许多小分区,我们所关注的进程内存分区主要有六個:text、data、bss、堆、栈和Env段。其中,需要重点关注用于存储用户输入和变量的缓冲区——栈和堆。栈主要被用来保存局部变量和函数调用的轨迹,在大多数操作系统中,栈的增长方向刚好与内存地址的增长方向相反——栈从内存的高地址空间向低地址空间增长,正是由于这种增长方式,导致了栈缓冲区溢出的存在。堆则被用来存储动态分配的变量,它的增长方向是从内存的地址空间向高地址空间增长。
2 栈缓冲区溢出漏洞
2.1 原理
在讨论栈缓冲区溢出之前先厘清两个不同的概念[1]:栈缓冲区溢出和栈溢出,前者是文章要讨论的漏洞,是一种安全缺陷,后者则是由于内存资源不足引起,通常会导致程序崩溃,是一种编程错误。
当发生函数调用时,计算机按如下步骤操作:参数入栈,指令寄存器eip入栈(RET),栈基址寄存器ebp入栈,将栈指针esp赋值给栈基址寄存器ebp,本地变量入栈。
栈缓冲区溢出的本质是某个栈帧(Stack Frame)发生溢出,溢出的内容覆盖了保存在栈内的eip,即返回地址被改变,当函数返回时,被破坏的eip值从stack里弹出到eip寄存器中执行,从而产生三种结果:一是最直接也是最“好”的结果就是程序发生段错误而造成系统崩溃,二是eip寄存器可能被控制以执行用户级执行恶意代码,三是最坏结果eip被控制以执行系统级或root级的恶意代码。
2.2 漏洞利用
2.2.1 技术条件
一次完整的漏洞利用都需要用到以下组件:
一是NOP片段,是在有效净荷前用NOP填充缓冲区,这样就能有效地增大了目标区域的范围,便于溢出点定位[1]。尽管NOP片段大大提高了攻击成功率,但是在一定程度上它的成功还依赖于对栈偏移的估测,如果栈偏移估测错误将可能引起程序崩溃并触发报警。此外,NOP片段需要耗费大量的内存来存储NOP,这就有可能由于缓冲区太小而导致栈区不够用。由于该技术太流行,所以很多厂商提供了通过匹配NOP模式的入侵防范系统来检测shellcode。
二是Shellcode,指将达到黑客目的的机器代码,shellcode是实实在在的二进制机器代码,常以16进制的形式表示。
三是修改返回地址,在缓冲区漏洞利用中最关键的元素就是返回地址,攻击者必须能够成功地用想要返回的地址覆盖栈缓冲区里的eip值,才能够控制程序跳转到其所想要执行的代码执行。
2.2.2 漏洞利用途径
一是从命令行利用栈缓冲区漏洞,直接使用一些脚本语言编写攻击代码,以获得root权限。
二是通过通用的攻击代码利用缓冲区漏洞,通用的攻击代码在网上或其他参考资料里都能找到,可以在许多情形下起对许多利用有效。
三是利用小缓冲区,当有缺陷的缓冲区很小不足以存储相对较长的shellcode时,可以将shellcode注入环境变量中,利用main函数的argv参数从环境变量中将shellcode读入内存。这中途径之所以能成功是由于进程内存空间中的Env段是可写的。
2.2.3 实施过程
缓冲区溢出漏洞利用大致有以下步骤:控制eip、确定偏移量、确定攻击向量、构建一个攻击“三明治”(NOP片段—Shellcode—跳转地址)、测试、根据需要调试。
3 堆缓冲区溢出漏洞
堆缓冲区溢出是指发生在堆数据区域内的缓冲区溢出,也是可利用的,但利用方式与利用栈缓冲区溢出不一样[2]。
程序在运行过程中需要分配和释放内存时,函数库提供了malloc()、realloca()、和free()等函数。Malloc()会将brk()分配的大内存块分成小块,并将合适的块分给用户,同样,需要释放时,free()时会判断新释放的块是否可以与其他块合并,以减小碎片。为了提高运行效率,用分配元数据(malloc meta data)保存块的位置、大小、或相关的数据,这些分配元数据一般以两种方式存在:一种是保存在全局变量中,另一种是保存在分配给用户的存储块前或后。后一种方式就为利用堆溢出改变分配元数据从而改变某个程序的函数指针[3]。
4 缓冲区溢出漏洞防范
4.1 Linux下的缓冲区溢出漏洞防范
由于缓冲区溢出和堆溢出的出现,许多程序员设计了内存保护方案来防范此类攻击:
一是编译器改进:libsafe能有效防范栈缓冲区溢出攻击,但是对防范堆缓冲区溢出攻击却没有太大作用;Stackshield取代了gcc编译时的某些不安全选项;StackGuard在栈缓冲区和帧状态数据(frame state data)中加入了一个标识,一旦缓冲区溢出破坏了保存在栈里的eip,这个标识就会被毁坏并且被检测到;SSP在stackgaurd的基础上重新分布了栈变量,从而使攻击变得困难;基于gcc的不可运行栈,GCC从4.1版本开始已经实现了不可执行栈,这就意味着攻击者要在栈里运行shellcode是不可行的。
二是内核补丁和脚本:不可执行内存页,这种技术的主体思想是栈和堆应该不可执行,用户代码一旦被载入内存也应不可写。Page-eXec(PaX)补丁尝试通过改变内存分页的方法对栈和堆提供执行控制。PaX补丁实现了一组关于TLB缓存的状态表,该状态表记录着某个内存页是处于读/写模式还是执行模式。当进程请求将某个内存页从读/写模式转换为执行模式时,PaX补丁进行干预,在日志里记录并杀死发出这个请求的进程,包括SEGMEXEC方式和PAGEEXEC方式。
4.2 Windows下的缓冲区溢出漏洞防范
(1)基于栈的缓冲区越界检测(/GS),编译器选项/GS是微软实现的一個栈canary,一个隐藏标识被存储到以存储的ebp和以存储的RETN地址之上,一旦发生函数返回,就检测隐藏标识是否被改变。有很多方法可以绕过/GS:猜Cookie的值,这种方法仅适用于本地系统的攻击;覆盖调用函数的指针;用特定标记替代Cookie;覆盖SEH记录。
(2)安全结构化的异常处理(SafeSEH),SafeSEH的目的是在栈里使用SEH结构体存储以防止覆盖发生。SafeSEH保护机制对于异常处理相当有效,但是相对复杂。
(3)SEH覆盖保护(SEHOP),SEHOP是Windows Server 2008中新增的一种保护机制。SEHOP通过 RtlDispatchException程序实现,遍历异常处理链并且确保其能到达FinalExceptionHandler函数,如果攻击者覆盖了某个异常处理结构,RtlDispathcException将不能正常到达FinalExceptionHandler函数。
(4)堆保护:针对内存释放函数可能被利用伪造数据块头指针,微软实现了一系列的方法来保护堆避免受到此类攻击:安全释放和堆元数据标识,前者在释放前检测要删除块的前一个堆的后向堆块指针和后一个堆的前向堆块指针是否均指向当前的数据块,在实践中这个过程很难被破坏,但不幸的是,这个方法也受到某些先决条件的限制,后者则是通过在堆数据块头指针中保存一个标识,在删除链接之前对其进行检测。
(5)数据执行防范(DEP)是防止放入堆、栈或数据段中的代码运行。这一直是操作系统的一个目标,但是直到2004年,AMD在CPU里增加了NX位,首次实现DEP的硬件支持,通过NX位可识别内存页是否可执行,随后不久,Intel也在CPU里加入了XD位来完成同样的事情。但是由于兼容性问题,DEP并非一直处于使能状态。
(6)地址空间随机分布(ASLR),将随机性引入到进程所使用的内存地址中,由于内存地址的变化使得攻击更加困难。Windows Vista和随后的版本都引入了ASLR技术。绕过ASLR最简单的方法就是返回到没有与ASLR保护链接的模块。
5结束语
缓冲区溢出攻击会对程序和系统造成严重危害,因此需要采取有效措施来防范缓冲区溢出漏洞。在Linux和Windows操作系统中,通常可以通过漏洞检测、漏洞修复、运行时防护等技术来达到防范目的。
参考文献:
[1] Wikipedia. Stack buffer overflow. Retrieved from Wikipedia: http://en.wikipedia.org/wiki/Stack_buffer_overflow
[2] Wikipedia. Heap overflow. Retrieved from Wikipedia: http://en.wikipedia.org/wiki/Heap_overflow.
[3] Anley, C. The Shellcoder's Handbook: Discovering and Exploiting Security Holes. Wiley Publishing,Inc,2007.
【通联编辑:光文玲】