缓冲溢出(共7篇)
缓冲溢出 篇1
0 引言
很久以来,恶意的黑客和病毒开发者广泛使用漏洞和缓冲区溢出技术。Codered(红色代码)病毒就是最典型的例子,其传播所使用的技术可以充分体现网络时代网络安全与病毒的巧妙结合,将网络蠕虫、计算机病毒、木马程序合为一体,开创了网络病毒传播的新路,可称之为划时代的病毒。它可以完全取得所攻破计算机的所有权限为所欲为,可以盗走机密数据,严重威胁网络安全。Codered病毒采用了“缓存区溢出”的黑客技术,是通过微软公司IIS系统漏洞进行感染,它使IIS服务程序处理请求数据包时溢出,导致把此“数据包”当作代码运行,病毒驻留后再次通过此漏洞感染其它服务器。Codered不同于以往的文件型病毒和引导型病毒,并不将病毒信息写入被攻击服务器的硬盘。它只存在于内存,传染时不通过文件这一常规载体,而是借助这个服务器的网络连接攻击其它的服务器,直接从一台电脑内存传到另一台电脑内存。当本地IIS服务程序收到某个来自“红色代码”发送的请求数据包时,由于存在漏洞,导致处理函数的堆栈溢出。当函数返回时,原返回地址已被病毒数据包覆盖,程序运行线跑到病毒数据包中,此时病毒被激活,并运行在IIS服务程序的堆栈中。所以很多专门的反病毒公司对Codered束手无策。之后又出现了Nimda(尼母达)和Badtrans(坏透了)病毒,破坏也非常大。
本文就是针对缓冲区溢出的安全隐患进行分析。
1 缓冲区溢出的定义
缓冲区通常是用来存储数量事先确定的、有限数据的存储区域。当一个程序试图将比缓冲区容量大的数据存储进缓冲区的时候,就会发生缓冲区溢出。
为了便于理解,打个比方。缓冲区溢出好比是将十磅的糖放进一个只能装五磅的容器里。一旦该容器放满了,余下的部分就溢出在柜台和地板上,弄得一团糟。由于计算机程序的编写者写了一些编码,但是这些编码没有对目的区域或缓冲区——五磅的容器——做适当的检查,看它们是否够大,能否完全装入新的内容——十磅的糖,结果可能造成缓冲区溢出的产生。如果打算被放进新地方的数据不适合,溢得到处都是,该数据也会制造很多麻烦。但是,如果缓冲区仅仅溢出,这只是一个问题。到此时为止,它还没有破坏性。当糖溢出时,柜台被盖住。可以把糖擦掉或用吸尘器吸走,还柜台本来面貌。与之相对的是,当缓冲区溢出时,过剩的信息覆盖的是计算机内存中以前的内容。除非这些被覆盖的内容被保存或能够恢复,否则就会永远丢失。也就是说数据超出缓冲区的大小,多余的数据就会溢出到相邻的内存地址,破坏该位置原有的有效数据,并且有可能改变执行路径和指令。可以将任意的数据注入到执行路径中,允许远程计算机的系统级访问,不仅使恶意的黑客,也会使自我复制的恶意代码获得未经授权的访问。
2 缓冲区溢出的种类
2.1 第一种堆栈缓冲区溢出
一个有漏洞的函数
一个大小为256字节的缓冲区,程序试图用512字节的字符A填满这个缓冲区。由于程序溢出了这个小的256字节的缓冲区,EIP(指令指针)是如何改变的。
当数据超出缓冲区大小的时候,剩下的数据就会覆盖堆栈的邻近区域原有的数据,包括一些重要的值,比如用来定义执行路径的指令指针(EIP)。通过覆盖返回的EIP地址,攻击者可以任意修改程序下一步将要执行的内容。
攻击者使用自己的恶意代码填充缓冲区,而不是A,使用填满了恶意代码的缓冲区地址来覆盖EIP(指向程序下一步将要执行的代码),而不是随机的数据来重写它。这改变了程序的执行路径,转而执行被注入的恶意代码。
堆栈缓冲区溢出的原因是在复制数据到缓冲区时没有检测数据大小。这经常是由于使用了不限制从一个位置复制到另一个位置的数据的大小的函数造成。如,strcpy是C语言中用来将一个缓冲区中的字符串复制到另一个缓冲区的函数,这个函数不检测接收字符串的缓冲区是否够大,所以可能溢出。
2.2 单字节越界溢出
程序员经常犯计数错误,会出现因为多出一个字节而溢出的现象,即单字节越界溢出。程序员希望使用相对安全的函数,例如strncat函数,但这并不一定使他们的程序更加安全。其实strncat函数经常导致溢出,它的行为相对太直观,在检查缓冲区大小的时候发生错误,经常会导致单字节越界溢出,因为程序员没有注意到数组的下标是从“0”开始。这种溢出只溢出一个字节,程序不能够重写EIP。系统字节序是little-endian,程序可以覆盖EBP中的低位字节。根据代码的不同,可以用多种方式利用伪造的EBP。紧接着下一条指令是mov esp,ebp,这使堆栈指针指向缓冲区的位置。伪造堆栈中的本地变量部分是经过处理的,程序返回后继续执行伪造的EIP处的指令,实际上执行的是堆栈中被注入的代码。程序员有一个普遍的误解,通过动态分配内存,避免使用堆栈,减少缓冲区溢出的可能。堆是动态分配的内存,这部分内存与分配给堆栈的是分离的,堆的建立和删除是动态的。事先不知道程序所需的内存大小时,或所需内存比堆栈大时,经常使用堆。堆溢出并不影响EIP,溢出的堆覆盖数据或者修改指向数据和函数指针,也可导致拒绝服务攻击,使应用程序崩溃。函数指针(function pointer)溢出发生在使用回调函数(callback function)时,缓冲区后面是函数指针,不检查缓冲边界,就有可能覆盖函数指针。
2.3 格式化字符串漏洞
格式化字符串漏洞是因为程序员粗心大意的编码造成的,C语言的函数可以将字符串打印到文件、缓冲区、屏幕上。程序员利用函数的格式化控制输出格式。百分号(%)是转义符,后面字符表示输出的格式,如%d表示用十进制格式显示数值,%X表示用十六进制格式显示,并用大写字母。这就是格式说明符。一个使用错误格式化语法的伪程序:
这段程序想把“hello Beijing”显示在屏幕上,但函数的调用格式不正确,正确的应该是Printf(“%s”,buffer);这种编程方式容易被攻击者利用,控制堆栈并注入任意的可执行代码。
安全研究人员检测到大多数的应用程序都含有这种漏洞,开发者依然不重视,还继续错误使用格式输出函数,造成现在很多应用程序存在漏洞,易受攻击。
3 结束语
缓冲区溢出的安全隐患经常被忽视,它只是黑客和病毒攻击的一种手段,破坏力可大可小,随着现代技术的不断更新,网络攻击和防御也平衡发展。保护计算机的最好办法就是经常安装安全补丁修补系统,基于主机以及基于网络的漏洞分析工具能够帮助找到内部网络中过期的有漏洞的系统,及时安装补丁。另外,漏洞分析工具还可以识别出该卸载的不需要不安全的服务。企业版及个人版的防火墙软件也有利于防范外来的和内部的攻击,需要在工作站、服务器、网关上安装防范机制,网络安全最重要。
参考文献
[1]Halvar Flake,personal communication.2004.
[2]Peter szor and Frederic perriot.2003.
缓冲溢出 篇2
毕竟我也是觉得惶恐的.没有什么任何听不懂的地方,只要你能坚持看完本文,
则会初步的理解缓冲区溢出的概念.
关于缓冲区溢出,自然要从”缓冲区“说起.则,何为”缓冲区“,
学习过C语言的人应该知道,当程序即将运行时,
会检查该程序将会使用到的变量,并且为该变量在内存中”划“出(或者用”登记“比较合适)
一段来,用于存贮该变量的数据.而被分出的内存区域大小则和程序代码的变量长度一样.
例如,定义某变量为8个字符,则该被分配内存大小也为8.
什么是变量,且听下面的例子(或许不是很准确):
例如,你要计算一个长方形的面积,可以根据数学公式:
长*宽=面积,
得出.
倘若一个长方形长为20,宽为10,用程序表示出来或许应该如下:
(下面的程序我想用任何编译器都编译不出,用脑袋或许可以)
行1 l=20
行2 h=10
行3 s=l*h
现在让我们解读一下该”程序“
其中,我们用”l“表示”长方形的长“,并且赋值为”20“,行1;
其中,用”h“代表”长方形的宽“,赋值为”10“,行2;
其中,用”s“表示”长方形的面积“,值为”l*h“,
白话则是用”l“乘”h“得出面积,行3,这也恰恰应用到了数学的公式.
问题也就随之出现,上面的程序显然是有很大的弊病的.因为--它仅仅可以计算一个长方形的面积,世界上那么多长方形,要如何计算?!难道要为每个长方形都写一个程序么?
然而事情看样子总是有转机的,变量也就随之出现了,当有了变量,我们就可以输入
(通过你的键盘)数据给程序,程序接受后,则会计算出相应的长方形了.
程序或许可以如下:
行1 +------输入”长“
行2 | 输入”宽“---------------------+
行3 +------长的变量 |
行4 宽的变量---------------------+
行5 搜集到了足够的信息,执行s=l*h
事情就这样被解决了?不,经过上面的温习,我们初步了解了一些程序的知识.
现在进入缓冲区溢出这个概念.
还要提起变量,我们在本问第二自然段提过:
”当程序即将运行时,会检查该程序将会使用到的变量,
并且为该变量在内存中“划”出
(或者用“登记”比较合适)一段来,用于存贮该变量的数据.
而被分出的内存区域大小则和程序代码的变量长度一样.
例如,定义某变量为8个字符,则该被分配内存大小也为8.“
让我们看这一段:
”被分出的内存区域大小则和程序代码的变量长度一样.
例如,定义某变量为8个字符,则该被分配内存大小也为8.“
问题出现了,为什么要给变量定义一个长度呢?我是这样理解的,
因为它需要在内存中分配出空间给该变量提供数据存贮空间,
倘若,没有事先定义好该变量的数据长度(或者谨慎的说”最长度“),
则,我们的计算机到底要为它分配多少空间呢?
1K?1M?1G?呵呵,显然为其定义一个长度是有必要的!
何为缓冲区溢出?有何危害?
震荡波你应该熟悉,它就是利用了ms04-011漏洞,
是一个缓冲区溢出的利用.至于危害,
你可以查阅以前的帖子或者有幸体会吧?
下面的内容或许很笼统,但确实是我所理解的.
还是举个例子:
试图我们要编写一个如下的程序,
该程序运行的时候会自动读取同目录下的1.txt中的内容
,并且写入到变量A中.代码如下(注:”//“后为注释)
行1 变量A[10] //设置了一个变量A,长度10
行2 读取本程序同目录下的1.txt中的内容
行3 将读取的内容写入到变量A中
-=程序完=-
看样子是一个很符合逻辑的程序,但是问题恰好出现了:
如果该1.txt内容如下:
123456789
则,不会出现问题
倘若,1.txt内容如下:
123456789123456789123456789123456789123456789
123456789123456789123456789123456789123456789
123456789123456789123456789123456789123456789123456789
这样会如何呢?来分析一下,
这个时候程序会执行到第二行,
当执行到第三行的时候就会产生溢出了!问题出在程序的”行1“,
因为它定义的变量长度为10,而1.txt中的内容远远大于了10,换句话讲,该段内存被撑着了.
被撑着的内存会溢出,好比,一杯水,满了,还继续往里面倒,
必然会影响到桌子的卫生(我想这样的比喻更能诠释”溢出“二字)
缓冲区溢出漏洞攻击初探 篇3
1缓冲区溢出漏洞定义及原理
1996年, Aleph One在Phrack杂志发表的“Smashing the Stack for Fun and Profit”是缓冲区溢出的经典之作, 第一次详细地介绍了缓冲区溢出产生的原理和利用方法。缓冲区溢出漏洞攻击是指攻击者通过在缓冲区写入超过预定长度的数据造成所谓的溢出, 破坏了堆栈的原有数据结构, 使程序的返回地址发生变化, 使它指向溢出程序中恶意代码, 这样就达到了攻击者的目的。
常见的缓冲区溢出漏洞包括栈溢出和堆溢出漏洞, 本文主要结合实验简要分析栈溢出的攻击原理。要充分理解缓冲区溢出漏洞的基本原理, 就要从理解系统的内存结构开始。系统的内存结构主要包含有text、data、bss、heap和stack等区段。其中, 代码text区, 是由程序的代码段, 为只读数据, 任何对其写入的操作都会导致段错误 (segmentation violation) ;而bss和data都是可写的, 它们保存全局变量, data段包含已初始化的静态变量, 而bss包含未初始化的数据;heap是堆区, stack是栈区。
栈结构及溢出原理:
堆栈是操作系统中非常重要的一种结构, 系统或用户进程通过堆栈用来完成各种函数调用。对于系统中的每个进程来说, 都有其各自独立的堆栈空间, 堆栈空间随着进程的创建而被创建, 随着进程的结束而被销毁。与系统中其他内存空间的增长有所不同, 堆栈的增长方向是由内存高地址向低地址进行的, 正是由于堆栈的这个特点导致了一系列的安全问题。
当一个函数调用另一个函数的时候, 其栈帧结构如图2所示。在正常情况下, 被调函数执行完毕, 将被调函数的返回地址传给EIP寄存器, CPU从EIP寄存器取指令地址, 返回主函数继续执行。如果黑客特意构造数据, 使被调函数的局部变量越界, 这样就有可能破坏栈中的相邻变量的值, 甚至破坏栈帧中所保存的EIP、EBP值等重要数据。成功的缓冲区溢出攻击会用精心设计的数据覆盖返回地址, 使被调函数执行完后将修改后的返回地址传入EIP, 从而使CPU执行攻击代码 (shellcode) 。
2缓冲区溢出漏洞攻击的构造
通过前面的分析, 我们大致了解了构造缓冲区溢出漏洞需要考虑以下3个方面的问题。一是找出缓冲区溢出要覆盖或修改的返回地址EIP。如栈溢出中的返回地址在栈中的存储位置。二是将返回地址的值修改为何值?为了完成程序执行流程控制权的转移, 攻击者将影响程序执行流程的EIP值进行修改, 使其能够跳转至攻击者预期的代码进行执行。如何定位注入指令在目标程序中的位置, 以及在存在限制条件如何完成程序控制权的移交, 是一项重要的挑战。三是要构造合适的指令代码, 即Shellcode。在程序控制权交至攻击者注入的指令代码后, 那么这段代码具体完成什么功能是由攻击者开发的。这段代码有时被称为攻击者的payload, 由于这个payload通常为攻击者提供了一个远程shell访问, 因此也被称为Shellcode。由于受目标主机的缓冲区大小限制, Shellcode长度一般越小越好。Shellcode构造步骤一般如下:
(1) 先用C语言程序编写实现Shellcode的功能的代码。
(2) 将上面编写的代码通过编译器进行编译, 然后修改为带有Shellcode特点的汇编代码 (精通汇编语言的人可直接使用汇编语言编写程序) 。
(3) 根据汇编程序得到机器码形式的Shellcode。
3缓冲区溢出漏洞实验
在了解了缓冲区溢出攻击的原理之后, 下面给出一个远程栈溢出的例子。图1所示的程序是一个存在缓冲去溢出漏洞的服务器端程序。该程序主要功能是实现字符串接收与回显, 通过windows socket机制创建一个服务器示漏洞程序的远程渗透攻击代码示例端socket, 并在3764端口上进行监听;在接受客户端连接之后, 把客户端输入的字符串统计接收的字节数, 并进行回显。该程序的被调函数overflow在将接收字符串复制至本地局部变量s1缓冲区时, 因没有进行边界保护, 存在着栈溢出漏洞风险。
下面我们构造一个针对图1所示漏洞服务程序的远程渗透攻击程序。该攻击程序关键点在于构造一个用于溢出目标程序缓冲区的攻击数据, 这是整个攻击过程中最为关键的步骤。攻击数据缓冲区Buff类似一个“三明治”, “顶层”填充一段无效的nop指令, “夹心层”是将EIP值修改为“jmp ESP”指令的地址, 而这个指令地址在不同的目标程序所运行的操作系统平台上是不一样的, 由攻击者在不同的系统环境中调试获得。在本示例代码中, 实验的环境是windows xp环境, 并从ws2_32.dll中查找到的“jmp esp”指令地址。“底层”则填充攻击者构造的远程Shellcode。
图2所示的代码省略了580字节的shellcode二进制代码程序。下面给出Shellcode实现步骤, 具体的代码请读者根据实际需要自行编写。其大致过程如下:
(1) 创建一个服务器端socket, 并在指定的端口上监听;
(2) 通过accept () 接受客户端的网络连接;
(3) 创建子进程, 运行“cmd.exe”, 启动命令行;
(4) 创建两个管道, 命令管道将服务器端socket接收 (recv) 到的客户端通过网络输入的执行命令, 连接至cmd.exe的标准输入;然后输出管道将cmd.exe的标准输出连接到服务器端socket的发送 (send) , 通过网络将运行结果反馈给客户端。然后再将高级语言实现的shellcode代码编译, 生成汇编代码, 提取汇编代码所对应的Opcode二进制指令, 就可以创建图2所示的渗透攻击程序中所省略的shellcode指令数组。
4小结
本文详细描述和分析了缓冲区溢出攻击的原理, 然后就如何对缓冲区溢出攻击漏洞构造攻击程序进行了详细的分析。目前, 缓冲区溢出漏洞仍然是互联网和计算机系统中最普遍安全漏洞, 特别是能够实现本地提取和远程渗透攻击的缓冲区溢出漏洞威胁最大。虽然随着软件厂商不断加强了软件安全方面的建设, 但在短期之内很难彻底解决缓冲区溢出漏洞攻击的问题, 因而目前对该问题的研究工作仍具有实际意义。
参考文献
[1]韩万军.缓冲区溢出攻击代码检测与防御技术研究[D].郑州:解放军信息工程大学, 2012.
[2]付春雷.基于多阶段网络攻击模型的缓冲区溢出攻击技术研究与实践[D].重庆:重庆大学, 2006.
基于栈指纹检测缓冲区溢出的思路 篇4
当前主动防御等的概念逐渐进入人们视野,国外主流的杀毒软件都有栈溢出的检测模块,尽管相对传统的木马和病毒来说,缓冲区溢出仍占攻击的很小一部分,但是基于传统的“木桶理论”,安全是一个整体,威胁还是无处不在,
现有的栈溢出检测模式
整篇文章我都以Kaspersky Internet Security(KIS 6)作为例子,KIS7中这一部分并无大的改进。以下是测试用的Shellcode:
__asm
{
/* --------------------解码开始---------------------- */
jmp decode_end
decode_start:
pop edx // 得到解码开始位置 esp ->edx
dec edx
xor ecx,ecx
mov cx,0x13D // 要解码的长度
decode_loop:
xor byte ptr [edx+ecx], 0x99
loop decode_loop
jmp decode_ok
decode_end:
call decode_start
decode_ok:
/*--------------------解码结束---------------------- */
jmp end
start:
pop edx // 指令表起始地址存放在 esp ->edx
// ===== 从 PEB 中取得KERNEL32.DLL的起始地址 =====
//
// 输入:
// edx =>指令表起始地址 (不需要)
//
// 输出:
// eax =>kernel32.dll起始地址
// edx =>指令表起始地址
mov eax, fs:0x30 // PEB
mov eax, [eax + 0x0c] // PROCESS_MODULE_INFO
mov esi, [eax + 0x1c] // InInitOrder.flink
lodsd
mov eax,[eax+8]
// ========== 定位GetProcAddress的地址 ==========
//
// 输入:
// eax =>kernel32.dll起始地址
// edx =>指令表起始地址
//
// 输出:
// ebx =>kernel32.dll起始地址
// eax =>GetProcAddress地址
// edx =>指令表起始地址
mov ebx,eax // 取kernel32.dll的起始地址 DLL Base Address
mov esi,dword ptr [ebx+3Ch] // esi = PE header offset
mov esi,dword ptr [esi+ebx+78h]
add esi,ebx // esi = exports directory table
mov edi,dword ptr [esi+20h]
add edi,ebx // edi = name pointers table
mov ecx,dword ptr [esi+14h] // ecx = number of name pointers
xor ebp,ebp
push esi
search_GetProcAddress:
push edi
push ecx
mov edi,dword ptr [edi]
add edi,ebx // 把输出函数名表起始地址存人edi
mov esi,edx // 指令表起始地址存入esi
//mov ecx,0Eh // 函数getprocAddress长度为0Eh
push 0xE
pop ecx
repe cmps byte ptr [esi],byte ptr [edi]
je search_GetProcAddress_ok
pop ecx
pop edi
add edi,4
inc ebp
loop search_GetProcAddress
search_GetProcAddress_ok:
pop ecx
pop edi
pop esi
mov ecx,ebp
mov eax,dword ptr [esi+0x24]
add eax,ebx
shl ecx,1
add eax,ecx
xor ecx,ecx
mov cx,word ptr [eax]
mov eax,dword ptr [esi+0x1C]
add eax,ebx
shl ecx,2
add eax,ecx
mov eax,dword ptr [eax]
add eax,ebx
// ============ 调用函数解决api地址 ============
//
// 输入:
// ebx =>kernel32.dll起始地址
// eax =>GetProcAddress地址
// edx =>指令表起始地址
//
// 输出:
// edi =>函数地址base addr
// esi =>指令表当前位置
// edx =>GetProcAddress 地址
mov edi,edx
mov esi,edi
add esi,0xE // 0xE 跳过1个字符串“GetProcAddress”32177368
// ============ 解决kernel32.dll中的函数地址 ============
mov edx,eax // 把GetProcAddress 地址存放在edx
//mov ecx,0x5 // 需要解决的函数地址的个数
push 0x2
pop ecx
call locator_api_addr
// ============ 加载user32.dll ============
add esi,0xd
// 硬编码可以节省两个字节
push edx // edx是GetProcAddress 地址
push esi // 字符“urlmon”地址
//mov dword ptr fs:[4],0x0012FFFF
//mov dword ptr fs:[8],0x0012FFFF
call dword ptr [edi-4] // LoadLibraryA
// ============ 解决函数地址 ============
pop edx
mov ebx,eax // 将urlmon.dll起始地址存放在ebx
//mov ecx,1 // 函数个数
push 0x1
pop ecx // 函数个数 <-这种方式省两个字节
call locator_api_addr
// 取得一些空间存放系统路径
sub esp, 0x20
mov ebx, esp
//MessageBox的参数
mov dword ptr [ebx], 0x797a7967 // “yzyg”
mov dword ptr [ebx+0x4], 0x00000000 // “00”
push 0
push ebx
push ebx
push 0
call [edi-0x4] //MessageBoxA
// ExitProcess
push eax
call dword ptr [edi-0x0c] // ExitProcess
// ============ 解决api地址的函数 ============
//
// 输入参数:
// ecx 函数个数
// edx GetProcAddress 地址
// ebx 输出函数的dll起始地址
// esi 函数名表起始地址
// edi 保存函数地址的起始地址
locator_api_addr:
locator_space:
xor eax,eax
lodsb
test eax,eax // 寻找函数名之间的空格x00
关于缓冲区溢出攻击的防御研究 篇5
缓冲区溢出作为一种安全危害级别相当高的计算机安全问题而一直存在。在近几年的安全事件中与缓冲区溢出相关的安全事件超过了50%, 缓冲区溢出攻击已经成为安全威胁中最为严重的一类。
1 缓冲区溢出攻击的原理与过程
缓冲区溢出问题, 基本上来说就是CC++编程的问题, 该问题的核心在于:为了照顾程序的性能, 计算机通常并不具体区分用户数据指令和程序控制指令, 而将它们混合在一起放在内存中, 这就有可能因为未处理好用户数据而造成缓冲区溢出, 比如在一些编程语言中的函数调用中。为了搞清楚缓冲区溢出攻击的原理, 我们先来分析下函数调用时堆栈的变化情况。堆栈是一个先进后出 (First in, Last out) 的队列, 它的增长方向与内存的增长方向正好相反, 具体如图1所示。
处理器在函数调用时, 将函数的参数、返回地址 (即进行函数调用的那条指令的下一条指令的地址) 及基址寄存器EBP压入堆栈中, 然后把当前的栈指针ESP作为新的基地址。如果函数有局部变量, 则函数会把堆栈指针ESP减去某个值, 为需要的动态局部变量腾出所需的内存空间, 函数内使用的缓冲区就分配在腾出的这段内存空间上。函数返回时, 弹出EBP恢复堆栈到函数调用前的地址, 弹出返回地址到EIP以继续执行原程序。程序是从内存低端向内存高端按顺序执行, 由于堆栈的生长方向与内存的生长方向相反, 因此在堆栈中的数据超过预先给堆栈分配的容量时, 就会出现堆栈溢出。基于所述原理, 通过精心设计, 黑客就可以利用覆盖缓冲区数据的方式将病毒、木马程序或者入侵程序代码殖入被攻击的计算机, 并更改返回地址值以指向它们已植入的恶意代码, 对系统发起攻击, 获取系统的控制权。
2 防御措施
2.1 基于源代码的防范措施
从根源上说, 缓冲区溢出攻击的形成与程序开发人员的编程意识有很大的关系, 由于以前人们的安全编码意识不强或者急于完成任务而忽略了代码的安全性检查。提高编程人员的安全意识, 消除造成缓冲区溢出攻击的人为因素, 可以大大减少这类攻击的发生。安全编码首先尽量选用自带边界检查的高级语言来进行程序开发, 对于某些性能上有严格要求的应用程序来说, 可以采用折衷的方法:使用安全型高级语言来编写上层的界面, 这些界面用于处理一些相对危险的操作, 同时使用低级语言来编写核心代码。除了人为注意之外, 还可以借助一些现有的工具来帮助发现程序中可能导致缓冲区溢出的部分。如ITs4 (thesoftwarestuPidSoureeSeanner) 作为最早的基于Linux和UN*X的扫描工具, 可以扫描C和C++语言所编写的程序, 用于发现一些最普遍的安全性相关的漏洞。
2.2 基于编译器的方法
基于编译器的方法主要是通过扩展编译器的功能或通过对编译打补丁来实现保护堆栈或阻止溢出攻击成功的目的。Immunix使用的StackGuard方法, Microsoft的/GS选项方法等都是很有效的防御方法。Stack Guard是紧接返回地址存储一个canary随机值, 同时在通用寄存器中保留一份canary, 程序执行过程中函数返回时会检查canary的完整性, 如果发生过缓冲区溢出的攻击, 那么这种攻击很容易在函数返回前被检测到, 这样就阻止了溢出的发生。不过这种方法也有弱点, 那就是canary的值可能会被攻击者猜测到从而成功跳过检测。Poin Guard是StackGuard的一般化方法, 在函数指针、long jmp缓冲区之后增加canary保护函数指针, 防止被缓冲溢出操作所改写, 并与编译器整合优化。该方法结合StackGuard可有效地防止缓冲区溢出。Microsoft也是基于StackGuard的成果, 添加了一个编译器标记 (/GS) 用来探测返回指针是否被改。
2.3 基于非可执行栈的防御措施
防御缓冲区溢出的另一种方法就是使得堆栈上的代码变得不可执行, 即非可执行栈策略, 以此避免被攻击的发生。然而, 最常见的处理器x86处理器系列的内存保护机制无法容易地支持这点, 通常如果一个内存页是可读的, 它就是可执行的。一个名叫Solar Designer的开发人员想出了一种内核和处理器机制的聪明组合, 创建了“非执行的堆栈补丁”, 有了这个补丁, 堆栈上的程序就不再能够像通常的那样在x86上运行。使用这种策略的包括SolarDesigner的non-exec补丁 (由OpenWall所使用) 和exec shield (由Red Hat/Fedora所使用) 。Ruby B.Lee等人对处理器进行改进, 引入一个安全返回地址栈 (SRAS) , 在程序执行时保存其正确的返回地址。只要调用和返回指令可修改SRAS的值。处理器在执行返回指令时采用的是SRAS中的返回地址, 如果SRAS中的返回地址和内存中的返回地址不一致, 说明内存中的返回地址可能受到攻击。近几年, 基于硬件的研究方法主要是对处理器进行改造, 并已推出产品, 如AMD Athlon264芯片就可阻止缓冲区溢出攻击。自从AMD Athlon264芯片的诞生, 以及Intel的跟进研究, 使得基于硬件的方法成为目前及今后的研究热点。该方法的优势是无需重新编写和编译源代码, 对于软件开发者和软件使用者都适用。
2.4 基于操作系统的方法
操作系统掌握了更多关于运行程序的信息, 能够更有效地解决计算机安全问题。在系统级, 所要做的改进是为系统中所有可能引起缓冲区溢出的函数增加用于进行溢出检查的代码, 并且将其置于该函数的开始位置, 以尽可能早地发现和防止溢出。例如, 边界检查或边界完整性检验都可由内核通过存储边界值来完成。Shaozili提出一种基于操作系统内核的内存边界保护方法。基于操作系统的机制常常和基于硬件的机制结合使用, AMD Athlon264芯片和windows系统就是一个很好的例子。
3 结束语
缓冲区溢出攻击是当前网络安全的主要威胁之一, 本文分析了缓冲区溢出产生的原理, 并且分析了当前常见的针对缓冲区溢出攻击的防范措施。虽然现在已经有了许多防御办法, 但这些方法都不能完全防止缓冲区溢出攻击的发生, 必须综合利用这些方法来尽量减少缓冲区溢出所带来的危害。从目前的发展趋势来看, 防御的手段逐渐从软件倾向于硬件层, 从单一的防御机制向多种机制复合的方向发展。随着系统的逐渐完善和人们对溢出攻击原理的掌握, 相信未来系统遭受溢出攻击的几率会越来越小。
参考文献
[1]MARKW EICHIN, et al.An Analysis of the Internet Virus of November 1988[M].IEEE Computer Society Press, 1989.
[2]The CERT Coordination Center (CERT/CC) .http://www.cert.org.
[3]SPYRIT D.Win32 buffer overflows[J].Phrack Magazine 15 (55) , 1999.
[4]C.COWAN, C.PU, D.MAIER, H.HINTON, P.BAKKE, S.BEATTIE, AGRIER, P.WAGLE, AND Q.ZHANG.StackGuard:automati adaptivedetection and prevention of buffer-overflow attacks[C].In Proceed-ings of the 7th USENIX Security Conference, 1997.
[5]C.COWAN, S.BEATTIE, J.JOHANSEN, P.Wagle.Pointguard:protectingpointers from buffer-overflow vulnerabilities[C].The USENIX Securi-ty Symposium, August 2003.
[6]LEE R B, KARIG D K, MCGREGOR J P, et al.Enlisting hardwarearchitecture to hwart malicious code injection[J].Lecture Notes in Computer Science, 2004, 2802:237-252.
SSH缓冲区溢出漏洞与防范研究 篇6
SSH的英文全称是Secure Shell,中文意思就是“安全壳”。SSH传输层协议是底层的安全传输协议,通常都是运行于TCP/IP之上,以该协议为基础,其上可以建立起多种网络安全服务。SSH传输层协议提供高强度的数据通信加密处理、加密的主机身份认证、数据完整性校验以及数据压缩等多项安全服务。协商确定双方通信所需要的密钥交换方式、公钥密码算法、对称密钥密码算法、消息认证算法和哈希算法等。SSH传输层协议中的认证是基于主机的 (Host-based) ,并不涉及客户端用户的身份认证。该协议的设计目标是简洁、灵活,允许参数协商并力争来回握手次数最少 (争取在绝大多数情况下,只需要两个来回 (round trips) 就完成所有的密钥交换、服务器认证、服务请求及其应答等,最糟糕的情况也只需3个来回) 。
二、SSH漏洞及缓冲区溢出问题
2.1 SSH工作流程简述
协议版本协商:双方都发送“S S H-protocol version-software version”+CR (option) 十NL, 该版本信息会用于D-日密钥协商。
密钥协商: (使用SSH二进制包表示协议格式) 相互发送己方支持的算法列表 (可以直接进行猜测,从而发送与之对应的初始密钥交换包) ,服务器会从客户端支持的算法列表中依次去找,直到发现与自己支持的相吻合的某种算法。
密钥协商产生的结果包括:
共享秘密K
交换哈希值H
由此产生加密和认证用的密钥对。且还作为唯一的会话标识和签名数据的一部分。
数据包处理:数据包将按协商好的选项进行处理,如果需要压缩,则压缩完成之后才进行加密处理,计算包长度和消息认证代码。M A C计算先于加密处理。MAC=MAC (key, sequence number, unencrypted packet (除MAC外的所有内容) ) 。
服务请求:接下来客户可以向服务器方提出自己期望的服务,目前的保留服务有:用户认证 (SSH USERAUTH) 和连接服务 (SSH CONNECTION) 。
用户认证:
服务器响应认证服务请求,告诉客户使用何种认证方式 (注意处理认证超时和超次的情况) 。
随后客户端发出相应的认证起始消息,进行认证。
连接服务:认证成功结束之后,就可以提交连接服务请求,比如请求打开一个伪终端,执行一个登录shell或者命令,进行端口转发等。双方会根据不同的服务类型述方——具体和服务类型相关的逻辑信道 (Channel) 并进行数据通信。
S S H协议流程示意图如图1。
SSH协议有两种版本,即版本1和2,二者存在兼容性问题,比较如表1。
2.2 SSH存在的安全问题
尽管SSH有很多优点,如它是实现网络安全的非常轻型的工具,不占用客户及机服务器的大量资源;便于用户理解 (至少在简单应用水平上是这样) 和使用:灵活且功能强大等,但S S H仅是系统安全的一部分,也有其不足.。
主机欺骗危险:允许客户第一次连接一台主机 (服务器) 时可以直接接受其主机密钥而不检查该密钥是否真正属于该主机。可以增加主机认证.。
依赖于主机系统的安全:对主机系统的安全性没有任何提高,一旦主机系统被入侵或控制,S S H无法保证传输安全。
基于下层的可靠传输::SSH建立在假设可靠的传输之上 (如TCP) ,无法避免Wire Cuter式的Dos攻击。
没有消除隐含信道问题:因为在填充域、SHeMSGweIGNORE消息和其他一些地方都可以被利用来传递隐含消息,无法防止夹带非法的数据进出敏感区域。
用户认证协议对底层传输协议的支持要求:用户认证协议要求下面的SSH传输层提供数据加密和完整性校验保护,如果系统或者用户配置选项关闭了这些缺省的安全功能,那么其上进行的任何对用户身份的认证很可能没有达到预期的安全效果。用户认证前检验加密、完整性。令调试消息可能泄漏敏感信息,当设计调试消息的时候,要注意它们也许会泄漏大量的主机敏感信息。缺省时不显示调试消息,或屏蔽一些敏感信息。令远程执行安全:协议允许服务器向客户机、或者客户机向服务器请求远程执行一条命令或一个程序。今用户非完全透明:尽管SSH协议从应用程序的角度来说是应用无关 (只要是面向连接的应用) 的,但是对于用户来说却不是完全透明的,因为它要求使用者产生和分发自己的密钥、选择合适的客户端软件,修改相应的配置,事先知道自己要连接主机的IP地址和转发端口等。
密钥管理与分发操作复杂:协议中对用户认证用的密钥没有规定具体的产生、分发和废除等操作,大多数的实现也都是采取人为手工的方式来完成。这样做不够灵活和方便,安全性也不够。在规模加大时更是明显。
安全与管理的矛盾:端口转发可能使入侵者绕过边界安全防护例如防火墙。因为信道是加密的,不能看到其中的数据,因此很难做出反应。
三、缓冲区溢出攻击的防范措施
3.1缓冲区溢出的攻击方法
程序代码和数据的存储可以放在堆栈中,程序执行前的指令寄存器地址也暂时被放在堆栈中 (示意见图2) ,它们被依次压入堆栈。执行过程中调用参数和函数时,如果数据输入过长,就有可能把预先存储地址的代码区覆盖,在系统再次调用时也就存在错误调用的威胁,执行恶意代码,进而成为一种攻击手段。缓冲区溢出之所以成为远程攻击的主要手段,更因为它能够扰乱具有某些特权运行的程序,使攻击者取得该程序的控制权。如果该程序具有足够的权限,那么有可能控制整个主机,这对SSH而言尤其危险,因为SSH通信的安全首先取决于系统的安全。一般而言,攻击者攻击root程序,然后执行类似“exec (sh)”的执行代码来获得root的Shell。为此,攻击者必须要做的是:在程序的地址空间安排适当的代码:通过适当地初始化寄存器和存储器,让程序跳转到预定的地址空间执行。
在被攻击程序地址空间里设置攻击代码的方法有两种:一是攻击者向被攻击的程序输入个字符串,程序会把这个字符串放到缓冲区里。这个字符串包含的数据是可以在这个被攻击的硬件平台上运行的指令序列。在这里攻击者用被攻击程序的缓冲区来存放攻击代码。另一种是利用被攻击程序中已经存在的代码,攻击者所要做的只是对代码传递一些参数,然后使程序跳转到目标。比如,攻击代码要求执行“exe (“/bin/sh”) ”,而在libc库中的代码执行“exec (arg)”, 其中arg使一个指向一个字符串的指针参数,那么攻击者只要把传入的参数指针改向指向”/bin/sh”,然后调转到lib库中的相应的指令序列。
跳转到攻击代码最基本的方法,就是溢出一个没有边界检查或者其他弱点的缓冲区,犹乱程序的正常执行顺序,攻击者用近乎暴力的方法改写相邻的程序空间而直接跳过了系统的检查。
最简单和常见的缓冲区溢出攻击类型就是在一个字符串里综合了代码植入和激活记录。攻击者定位一个可供溢出的自动变量,然后向程序传递一个很大的字符串, 在引发缓冲区溢出改变激活记录的同时植入了代码。代码植入和缓冲区溢出不一定要在一次动作内完成。攻击者可以在一个缓冲区内放置代码,这是不能溢出缓冲区;然后。通过溢出另外一个缓冲区来转移程序的指针。这种方法一般用来解决可供溢出的缓冲区不够大(不能放下全部的代码)的情况。
如果攻击者试图使用己经常驻的代码而不是从外部植入代码,他们通常必须把代码作为参数。例如,在libc(几乎所有的C程序都要它来连接)中的部分代码段会执行“exec (something)”,其中something就是参数。攻击者使用缓冲区溢出改变程序的参数,然后利用另一个缓冲区溢出使程序指针指向libC中特定的代码段。
3.2缓冲区溢出的防范方法
3.2.l编写正确的代码
编写正确的代码是一件非常有意义但耗时的工作,可以使用一些工具和技术编写安全、正确的程序。最简单的方法就是用grep来搜索源代码中容易产生漏洞库的调用,比如对strcpy和sprintf的调用,这两个函数都没有检查输入参数的长度,因此用strncpy和snprintf等替代函数防止缓图1 SSH协议流程示意图表1 SSH协议版本1与2的比较冲区溢出的发生;利用一些高级的查错工具,如fault injection等,还有些静态分析工具用于侦测缓冲区溢出的存在。
3.2.2非执行的缓冲区
非执行的缓冲区技术通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入的被攻击程序输入缓冲区的代码。事实上,很多旧的U n i x系统都是这样设计的,但是近来的Unix和MS Windows系统为实现更好的性能和功能,往往在数据段中动态地放入可执行的代码。所以为了保持程序的兼容性不可能使得所有程序的数据段不可执行。
非执行堆栈的保护可以有效地对付把代码植入自动变量的缓冲区溢出攻击,而对于其他形式的攻击则没有效果。通过引用一个驻留的程序的指针,就可以跳过这种保护措施。其他的攻击可以采用把代码植入堆或者静态数据段中来跳过保护。
3.2.3数组边界检查
数组边界检查完全防止了缓冲区溢出的产生和攻击。只要数组不能被溢出,溢出攻击也就无从谈起。为了实现数组边界检查,所有对数组的读写操作都应当被检查以确保对数组的操作在正确的范围内。通常可以采用一些优化的技术来减少检查的次数。目前的检查方法包括:Compaq C编译器,Jones&Kelly C语言的数组边界存储器存取检查Purify以及安全语言等。
3.2.4程序指针完整性检查
程序指针完整性检查是在程序指针被引用之前检测它是否被改变。因此,即便一个攻击者成功地改变了程序的指针,由于系统事先检测到了指针的改变,因此这个指针将不会被使用。与数组边界检查相比,这种方法不能解决所有的缓冲区溢出问题,但是在性能上有很大的优势,而且兼容性很好。
最普通的缓冲区溢出形式是攻击活动记录然后在堆栈中植入代码。非执行堆栈可以防范所有把代码植入堆栈的攻击方法,堆栈保护可以防范所有改变活动记录的方法。这两种方法相互兼容,可以同时防范多种可能的攻击。
剩下的攻击基本上可以用指针保护的方法来防范,但是在某些特殊的场合需要用手工来实现指针保护。全自动的指针保护需要对每个变量加入附加字节,这样使得指针边界检查在某此情况下具有优势。
另外,利用一些工具检测特征代码等,及早发现:并针对不同的程序段应用动态连接检查缓冲区溢出漏洞,最大程度地防止溢出攻击的发生。
四、SSH协议的改进和实现
4.1在SSH协议中质询——响应认证方式嵌入
在SSH协议用户认证过程中,一般采用口令认证或公钥认证。口令认证比较简单,但存在传输过程中数据劫取的威胁,虽然利用SSH协议通信过程中建立起来的加密信道可以保护口令数据,但依然潜在被攻破的威胁;而公钥认证也存在两个弊端:一是公钥的生成要消耗和占用系统资源,二是公钥的管理和存储问题一直没能很好地解决。而质询—响应 (CHAP) 认证方式可以较好地解决上述问题。
所谓质询—响应认证方式,就是通过对等实体通过共享一个普通文本密钥来实现网络安全性。这个密钥不会在传输链路上发送,通过如下步骤执行:
链路建立后,主机 (认证者) 首先发送质询消息给客户 (被认证者) ,该质询消息一般包括标识符 (ID) 、随机数和本地设备的主机名和远程用户的用户名等。
客户接收到质询消息,使用单向哈希函数计算出一个质询响应值,密钥是单向哈希函数的输入。
客户发送该质询响应值,包括ID的加密版本、计算出的哈希值 (作为秘密口令) 、随机数及远程设备的主机名和用户名等。
主机接收到响应,查询响应中给定的名称并执行相同的加密操作,确认密钥是否一致,并把得到的哈希值与自己期望的哈希值比较。
如果密钥与哈希值都匹配,则认证成功,发送成功认证消息并建立连接;否则,认证失败在质询—响应认证方式中,远程和本地设备上的秘密口令必须相同,并以安全的方案得到认可、生成和交换。由于秘密口令从不进行传输,所以其他设备无法得到。通过使用一个增量变化的标识和可变质询值,质询—响应认证方式可以防止重放攻击。另外可以使用重复的质询,以限制任何一次攻击的暴露时间,认证者可以控制质询的频率和时间。
质询—响应认证方式中一般使用MD5作为单向哈希函数,而共享秘密口令通常以普通文本存储。质询响应认证方式的流程见图3。
4.2 Windows平台上SSH协议的实现
4.2.1 SSH协议应用存在的主要问题
现有S S H界面多是在U N X环境下运行,存在用户界面不友好、配置复杂等问题。而W i n d o w s是大多数用户比较熟悉的操作环境,在这一界面下完成协议的功能,将能进一步推动其获得更广泛的应用。其功能模块主要包括:
认证方式选择;
密钥产生(包括密钥选择,如D S AR S A) ;
密钥代理(选取密钥、修改、增删密钥):
SSH配置管理(协议版本(SSH 1/2)及认证方式选择,是否压缩等);
会话管理(己有连接会话的载入,新会话的建立)。
4.2.2系统设计目标及存在的问题
基于W i n d o w s平台实现S S H协议,要解决的主要问题涉及系统功能定义、终端字符支侍、用户界面设定等。关键的儿点内容包括:
系统功能包括远程连接、文件的远程传输(SCP及SFTP)、端口转发、Xll转发,以及相关的密钥生成、导入与删除管理等。
终端字符集的支持,包括终端类型、显示区域、字体与编码选择等。
用户界面的设定,主要包括功能选择、系统缺省设置,以及快捷键设置等。
4.2.3工作实施
分客户端和服务器两个实施阶段,利用已有的条件和资源,分别在客户端和服务器端实施,最后实现系统的整合。为保证接口的一致性,统一定义全局变量和局部变量,以顺利实现变量传递和保证接口的顺利衔接。考虑到为保证服务器端有较高的系统安全性,服务器端的操作系统可以使用Linux,这要在设计时考虑两种系统之间的互通。
协议环境设置完成后;进行SSH协议的具体认证过程,其流程参见图1,此处不再赘述。完成后在Windows环境下实现了基于SSH协议的安全通信,可以把命令、配置等以简捷的界面给用户提供安全通信。在实际中也证明,Windows环境下实现了SSH协议,既保证了网络的安全性,又实现了操作的简便性,达到了安全的目的。
五、结束语
根据当前网络技术推广和发展过程中遇到的安全问题,有针对性的选择应用层网络安全协议SSH作为研究对象,仔细分析了S S H协议的三个层次及其扩展协议,指出了协议本身和各种实现版本中存在的主要问题,并根据作者的研究和实践经验提出了相应的建议,嵌入了质询—响应认证方式,弥补了协议中的漏洞,消除了其脆弱性,在Windows环境下实现了SSH协议,并希望作为一种产品来应用。按照实际项目的需求,将SSH整合到目前的一些实际工程中应用,结果表明对应用后的速度影响不大,但显著增强了安全性,其效果令人满意。
摘要:安全Shell (SSH:Secure Shell) 是一种应用层的安全通信协议, 提供通信双方相互间身份的认证、通信数据的加解密处理、数据完整性校验等多种安全服务, 按照其实现的功能, 可归为一种应用层的虚拟专用网 (VPN) 协议。论文概要说明了SSH协议的基本概念, 然后对SSH会话从发起到结束的整个过程, 以及扩展协议进行了深入细致的研究和分析;归纳总结了SSH协议本身存在的若干缺陷和不足, 根据作者相关的研究和工作经验, 对这些问题进行了阐述, 并提出一系列实际操作过程中可以参考的建议;对部分安全漏洞, 特别是对缓冲区溢出漏洞, 进行了研究、分析, 修改了其脆弱性、弥补了漏洞, 并嵌入了质询-响应的认证方法。同时, 作者根据实际应用的需求, 基于Windows平台, 实现了SSH协议, 方便系统管理和提高SSH的可用性。论文最后总结与回顾全文, 介绍了SSH的应用情况, 并对未来的工作进行展望。
关键词:SSH, 网络安全,Windows,密码协议
参考文献
[1]圣安妮.卡利斯科著, 张蓬、匡巍、张建杰等译.SSH:UNIX Secure Shell工具.北京:机械工业出版社.2004
[2]戴关侠, 连一峰.正航, 系统安全与入侵检测.北京:清华大学出版社.2004
[3]李俊照, 王浩, 徐栋哲.基于SSH协议的集群构建与性能测试.计算机工程与应用.2005
[4]丁晓峰, 李周贤, 刘炳华, 顾巍, 吴楠宁.在SSH协议下的入侵检测[J].现代图书情报技术.2005
缓冲溢出 篇7
本文介绍了缓冲区溢出的基本原理及主要的防御方法,其中重点分析了地址空间随机化技术在Linux系统中的实现,并针对这一防御技术,研究如何绕开其防护的缓冲区溢出攻击方法,并作了相关的理论分析和实际测试。
1 缓冲区溢出
1.1 缓冲区溢出攻击原理
缓冲区溢出是指:写入缓冲区的数据量超过缓冲区的设计大小,溢出的数据覆盖了相邻存储单元中的数据。攻击者通过溢出缓冲区将恶意代码注入内存,使进程运行时跳转并执行恶意代码来进行攻击[3]。缓冲区溢出攻击主要分为以下几类:栈溢出、堆溢出、格式化字符串溢出和整型变量溢出等,其中以栈溢出最为常见。
在Intel 80×86体系结构中栈位于高地址区域,且向低地址方向增长。栈帧在内存中的分布结构如图1所示,其中,调用方EBP(栈基地址指针)存放了函数调用方的栈底地址,函数返回值存放了函数结束后的返回地址。由于新创建的数据总是位于内存的低地址区域,当用户向低地址缓冲区写入过多数据时,就会造成数据溢出,从而篡改高地址内存中的关键数据。如果写入的数据是攻击者精心构造的恶意代码shellcode,返回地址等指针被覆盖成了指向shellcode的指针,当调用该指针时,就将跳转并执行shellcode,从而改变进程的行为,达到入侵的目的。
1.2 缓冲区溢出攻击防御
针对缓冲区溢出攻击的防御方法归纳起来有:编译保护、安全库函数、缓冲区不可执行和随机化方法等。
编译保护是通过扩展编译器以防止函数的返回地址被修改来实现栈保护。通常是在返回地址前放置一个标志字或建立一个影子栈,在函数返回前查看是否有溢出存在。目前已有不少的补丁可供使用,如Stack Guard和Stack Shield。但这种方法只局限于对栈的保护。
C标准库中的函数一般不进行参数验证,通过重写这些函数使其进行参数验证,增加一些安全检查机制可以有效阻止缓冲区溢出攻击。如Libsafe对常见的字符串操作函数进行封装[4],在调用这些函数时先进行边界检查,一旦目标缓冲区不能容纳源字符串则终止程序。针对Libsafe只保护栈的不足,Avi-jit等提出Libsafe Plus[5],通过抽取程序的调试信息,实现对全局变量、栈变量和堆变量的边界检查。
缓冲区不可执行包括栈不可执行和数据段不可执行。攻击者通常是将恶意代码植入溢出的缓冲区中,只要让这些可写的缓冲区不可执行,就能防止恶意代码控制程序。采用这种技术的典型例子有Linux内核补丁Pa X(Page Exec)[6]。它通过不可执行页实现了不可执行栈和不可执行堆。不可执行缓冲区对于需要注入可执行代码这类攻击的防范是高效的,但无法阻止像return-into-libc这种无须注入代码的攻击。
现有的用于防范针对内存攻击的技术或者系统只能防御某一特定方式的攻击,而且要将这些技术或者系统集成在一起很困难。操作系统的安全防护机制对缓冲区溢出漏洞利用有很大影响。如果能在操作系统层检测和阻止缓冲区溢出攻击,将大大提升系统的安全性。因此研究人员提出了随机化方法对系统进行保护,主要有指令随机化和地址空间随机化。
指令随机化是指编译时对可执行程序的机器码进行加密,在指令执行前,对每条加密的指令进行解码。这个过程使得攻击者植入的恶意代码不能完成预定的功能,通常会导致程序崩溃。但这种方法存在因使用虚拟机导致负荷过重等问题[7]。
地址空间随机化ASR(Address Space Randomization)技术是近几年发展起来的一项比较有效的防御缓冲区溢出攻击的技术,由于它的独到之处,Linux、Free BSD和Windows Vista等主流操作系统都已采用了该技术。
2 Linux地址空间随机化技术的实现
基于缓冲区溢出漏洞的攻击需要事先熟悉进程的地址空间,从而将程序的执行流程跳转到攻击代码的位置。ASR技术通过对操作系统内核或C库的修改,使进程加载到内存的地址随机化,使得攻击者无法将程序的执行流程跳转到预期位置,从而阻止进程执行攻击者预先设置好的攻击代码。攻击者要想控制系统,需要修改返回地址或指定地址为指定值。地址随机化后,这些指定地址或者指定值都将无法事先确定,攻击者只能猜测该地址,所以不能保证能正确地跳转到攻击代码的位置,从而有效地降低攻击者攻击成功的概率。
2.1 Linux可执行目标文件的加载过程
在Linux系统中可以通过execve系统调用启动加载器。加载器为新进程创建一组新的代码、数据、堆和栈段。新的堆和栈段被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小的组块(chunks),新的代码和数据段被初始化为可执行文件的内容。最后,加载器跳转到_start地址,它最终会调用应用的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到存储器的数据拷贝,直到CPU引用一个被映射的虚拟页时,操作系统才会利用页面调度机制将页面从磁盘传送到存储器。
当加载器运行时,它创建的存储器映像如图2所示。在Linux系统中,代码段总是从0x08048000处开始。数据段是在接下来的下一个4KB对齐的地址处。运行时堆在接下来的读/写段之后的第一个4KB对齐的地址处,并通过malloc调用向高地址增长。开始于地址0x40000000处的段为共享库保留。用户栈从地址0xbfffffff处开始,并向低地址增长。从栈的上部开始于地址0xc0000000处的段是为内核的代码和数据保留的。
2.2 Linux中的地址随机化
自从Linux 2.6.12版本后,地址随机化技术已经默认在Linux内核中启用。在Linux 2.6.27下的测试结果如图3所示。
从图3中可以看出,栈、堆和共享库的加载地址是随机的。通过比较多次运行的结果可以判断,对于32位系统,栈基地址的第4-23位共20位是随机的,堆基地址的第12-27共16位是随机的,共享库基地址的第12-27位共16位是随机的。
Pa X是较成熟的ASR技术,它通过为Linux内核打补丁,在进程加载时,对栈基地址的4-27位共24位进行随机化,对包括主程序映象、静态数据区、堆这一连续区域的基地址的12-27位共16位进行随机化,对共享库加载地址的12-27位共16位进行随机化。再加上可写页不可执行技术,Pa X能大大降低攻击成功的概率。TRR通过修改进程加载器来实现ASR,而无需修改系统内核。与Pa X相比,TRR增加了对全局偏移表GOT的随机化,在进程加载时将GOT移动到新的位置,并用二进制代码修改工具修改代码段中的过程链接表PLT,以阻止修改GOT函数指针指向恶意代码的攻击。这一类ASR实现方案对栈、堆、主程序代码段以及共享库等在进程空间的基地址进行了随机化,粒度较粗、实现简单、额外开销较小,目前得到了广泛的应用。它们的缺陷主要在于只随机化了基地址以及使用的随机空间不大,可以被暴力破解和实施相对地址攻击。
2.3 栈随机化的实现
加载ELF可执行文件的函数是load_elf_binary()[8],它把page指针数组指向的内核页面以及可执行文件、解释器的部分区段映射到用户空间,并设置用户空间栈上的argc、argv、envp变量和解释器将用到的辅助向量,它调用的setup_arg_pages()函数形式为setup_arg_pages(bprm,randomize_stack_top(STACK_TOP),executable_stack);其中,STACK_TOP为用户空间的顶端,一般等于0xc0000000。randomize_stack_top()的主要代码实现如下:
首先判断内核是否开启了ASR保护,如果开启则调用get_random_int()获得一个随机数,并和STACK_RND_MASK(0x7ff)相与后左移PAGE_SHIFT(12)得到random_variable,最后将stack_top按页边界对齐后减去random_variable,得到最终的stack_top值。由此可以推出stack_top可能的最小值为0xc0000000-0x7ff000=0xbf801000。
其中,MAX_ARG_PAGES和PAGE_SIZE的值分别为32和4096,即参数的总长度不得超过32个页面。通过将sp指针减去一个随机数除8192的余数后,末尾四位取0,再进行PAGE_ALIGN,得到最终的stack_base值。最后setup_arg_pages函数将page指针数组指向的内核页面映射到用户空间中stack_base开始的区域。
上述分析可以得出:1)栈地址的第4-23位是随机的,页内偏移(0-11位)也是随机的。2)当向程序传递相同的环境变量时,即使stack_base不是固定值,但环境变量字串在页内的偏移是一个固定值。3)因为stack_base的最小值不会小于0xbf7df000,所以环境变量字串的地址总是高于0xbf7df000。
3 针对ASR防护的缓冲区溢出攻击方法
3.1 暴力破解
由于程序加载的位置不再固定,因此使用缓冲区溢出攻击程序成功的概率将大大降低。但由于使用的随机空间不大,通过一定次数的暴力破解后仍有可能成功。此时栈溢出攻击成功的概率计算如下。其中,L(v)表示v的长度。
1)shellcode在栈内变量buf中
p=(L(buf)-L(shellcode))/(L(random_stack_range)-L(shellcode)),
若L(buf)=256,L(shellcode)=25,L(random_stack-range)=1M,则p=0.02%。
2)shellcode在环境变量中
环境变量地址头8位是固定值bf,末12位通过getenv()函数再微调后获得,中间12位的范围是7df~fff,这是一个不大的区间。
p=1/(L(random_env_range)-L(shellcode))),
若L(random_env_range)=432,L(shellcode)=25,则p=0.24%。
3.2 转向非随机化区域
Linux操作系统默认采用了ASR机制后,它的栈,堆,共享库的加载地址是随机的,但是仍有些区域是非随机的,如text段、data段和bss段。因此可以通过覆盖栈中的返回地址使程序跳转到这些地方。
text段存放着程序代码,它是只读的,任何向其写入的操作都会被禁止,因此无法将shellcode放在其中。但可以用指向text段的另一个合法地址的指针覆盖返回地址,从而控制程序流程。bss段中存放的是未初始化的全局变量和静态变量。data段存放的是已初始化的全局变量和静态变量。如果程序中存在strcpy(localbuf,input);及strcpy(globalbuf,localbuf)语句,那么输入字符串input会覆盖局部变量localbuf,并最终覆盖全局变量globalbuf,又因为globalbuf的地址可以通过gdb调试确定,所以通过构造如图4所示的输入串就会控制程序执行shellcode代码。
3.3 return-into-libc
当设定栈不可执行后,shellcode无论放在栈的什么地方都基本无用,因此这种防御方法可以阻止大部分的攻击。在Linux中,可以通过Pa X设定栈不可执行,而return-into-libc方法可以避开这样的防护对策。return-into-libc主要从两方面突破Pa X的防护:1)通过伪造栈帧和利用连续的系统调用和库函数调用来构造溢出环境,这是因为系统调用和库函数所在的内存区是可执行的,然后在进程的内存空间中寻找一个非数据段的可执行空间来存放shellcode,最后使进程跳转到这一地址执行shellcode。2)针对库函数的随机定位,通过使用动态连接器dl-resolve()的接口即动态地址解析函数rtld,在溢出执行的过程中动态地决定函数的真正地址。
3.4 相对地址攻击
现有的ASR技术一般只随机化了基地址,可以被实施相对地址攻击。Tyler Durden针对Pa X只修改了基地址而没有修改主程序代码中语句的相对地址的漏洞[9],提出了一种攻击方法。因为Pa X只对主程序代码地址中12-27位进行了随机修改,所以代码段中特定代码的页内地址与没有采用Pa X的系统中的页内地址相同,因此可以通过覆盖存在溢出漏洞的函数的返回地址的最后一个字节(页内地址的低8位),使其跳转到本页内call或printf之类的指令,再通过格式化字符串攻击的方法,逐步获取共享库加载的基地址,实现return-into-libc攻击。
利用栈内数据间的相对偏移量不变这一特点也可实施攻击。如果知道进程的栈底地址,利用相对偏移量就可以计算出栈内其它变量的地址。由于stat文件对每个用户都是可读的,因此栈底地址可以从/proc/
服务端代码server.c中存在可利用的漏洞:strcpy(writebuf,str);首次运行程序时,通过gdb获得writebuf的地址,并通过cat/proc/`pidof server`/stat|awk‘{print$28}’命令获得stack_bottom的地址,然后计算offset=&stack_bottom-&writebuf=1496,最后再次运行程序,结果如图6所示。在客户端向服务端发起连接后,攻击者就能获得服务端的root shell,实现权限由普通用户到root用户的提升。
4 小结
本文重点介绍了地址空间随机化技术在Linux系统中的实现,以及如何绕过该技术保护的缓冲区溢出攻击方法。地址空间随机化技术是一种试图阻止各种内存攻击的有效技术。尽管它能有效降低缓冲区溢出攻击成功的概率,但由于随机化空间不够大、随机化粒度不够细等原因,使其不能完全阻止攻击。而细粒度的地址空间随机化又需要对程序内部结构进行较大的修改,这将导致兼容性差而得不到广泛应用。找到易于实现的细粒度随机化方法可能是ASR技术未来的研究重点。同时把ASR技术同其它的安全防护机制有效结合,可以构建出一套完整的缓冲区溢出防御体系,提高系统的安全强度。
摘要:缓冲区溢出攻击是一种被广泛利用并危害严重的攻击方式,已经成为计算机系统安全亟待解决的重要问题。在分析缓冲区溢出攻击原理的基础上,给出了主要的防御方法,重点探讨了Linux系统下地址空间随机化防御方法的实现,研究了如何绕过其防护的缓冲区溢出攻击方法,并对其防护效果进行了分析。结果表明,地址空间随机化技术能有效降低缓冲区溢出攻击成功的概率,但不能完全阻止攻击。
关键词:缓冲区溢出,Linux,地址空间随机化,攻击,防御
参考文献
[1]SANS Institute.Top 20 security vulnerabilities[EB/OL].http://www.sans.org/top20/.
[2]阎雪.黑客就这么几招[M].北京:北京科海电子出版社,2002.
[3]Forst J.C.,Osipov V.,Bhalla N.,et al.Buffer overflow attacks:detect,exploit,prevent[M].Rockland:Syngress Press,2005.
[4]A.Baratloo,N.Singh,T.Tsai.Transparent run-time defense against stack smashing attacks[C].Proceedings of 2000 USENIX AnnualTechnical Conference(USENIX'00).California:Usenix Association,2000:251-262.
[5]Kumar Avijit,Prateek Gupta,Deepak Gupta.TIED,LibsafePlus:Tools for runtime buffer overflow protection[C].Proceedings of the13th Conference on USENIX Security Symposium.Berkeley:Usenix Association,2002:191-206.
[6]PaX[EB/OL].http://pageexec.virtualave.net.
[7]Gaurav S.K.,Angelos D.K.,Vassilis Prevelakis.Countering code-injection attacks with instruction-set randomization[C].Proceedingsof the 10th ACM Conference on Computer and Communications Security.New York:ACM Press,2003:272-280.
[8]Hackisle.突破ASLR保护和编译器栈保护[EB/OL].http://blog.chinaunix.net/u3/102108/showart_2025891.html.