缓冲区溢出
- 1. 什么是缓冲区溢出
- 2. 缓冲区溢出攻击的类型
- 3. 攻击者如何利用缓冲区溢出
- 4. 如何防止缓冲区溢出攻击
1. 什么是缓冲区溢出
(1)缓冲区
缓冲区是一块连续的计算机内存区域,用于在将数据从一个位置移到另一位置时临时存储数据。这些缓冲区通常位于 RAM 内存中,可保存相同数据类型的多个实例,如字符数组。
计算机经常使用缓冲区来帮助提高性能,大多数现代硬盘驱动器都利用缓冲优势来有效地访问数据,并且许多在线服务也使用缓冲区。例如,在线视频传送服务经常使用缓冲区以防止中断。流式传输视频时,视频播放器一次下载并存储 20% 的视频到缓冲区,然后从该缓冲区进行流式传输,当连接速度的小幅下降或快速的服务中断都不会影响视频流性能。
(2)缓冲区溢出
缓冲区溢出(buffer overflow)指当一段程序尝试把更多的数据放入一个缓冲区,数据超出了缓冲区本身的容量,使数据溢出到被分配空间之外的内存空间,导致溢出的数据覆盖了其他内存空间的合法数据。
因此攻击者可以利用缓冲区溢出修改计算机的内存,破坏或控制程序的执行,导致数据损坏、程序崩溃,甚至是恶意代码的执行。
2. 缓冲区溢出攻击的类型
缓冲区溢出攻击指利用缓冲区溢出漏洞所进行的攻击行动,通过向程序的缓冲区写入超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃、系统关机或使程序执行其它指令,以达到攻击的目的。缓冲区溢出攻击有很多类型,主要可以分为以下几类:
(1)栈溢出
栈溢出是指在程序执行中,如果在栈上分配的内存超过了栈的大小,就会发生栈溢出。栈是一种后进先出(LIFO)的数据结构,用于存储程序执行过程中的临时变量。当栈溢出时,程序会立即停止执行,并显示栈溢出错误信息。栈溢出攻击是最常见的缓冲区溢出攻击类型,发生栈溢出的基本前提是程序必须向栈上写入数据且写入的数据大小没有被控制。
(2)堆溢出
堆溢出是指程序在动态分配内存时,分配的内存超出了堆的大小。堆是一种先进先出(FIFO)的数据结构,用于存储程序运行时长期需要的数据。当堆溢出时,程序可能不会立即停止执行,但会导致程序的不稳定,甚至崩溃。在恶意攻击中,攻击者可能会利用堆缓冲区溢出来执行任意代码或获取敏感信息。
(3)格式字符串溢出
格式字符串指在编程语言中,涉及使用格式化字符串函数来打印字符串时,如果格式串由用户定制,攻击者就可以任意伪造格式串,利用 *printf()
系列函数的特性就可以窥探堆栈空间的内容,超长输入可以引发传统的缓冲区溢出,或是用“%n”覆盖指针、返回地址等。
(4)整数溢出
计算机语言中整数类型都有一个取值范围,两个整数进行运算时,若结果大于最大值(上溢)或小于最小值(下溢),就是溢出。例如,最大值为a,在最大值与最小值之间发生以下计算:a+1=0或0-1=a,此时会发生溢出,其中a+1=0会发生上溢,0-1=a会发生下溢。利用整数的范围和符号等问题触发安全漏洞,大多数整形溢出不能直接利用,但如果该整形变量决定内存分配等操作,作为漏洞被间接利用。
(5)Unicode 溢出
Unicode 溢出通过将 Unicode 字符插入需要 ASCII 字符的输入中来创建缓冲区溢出。ASCII 和 Unicode 是使计算机表达文本的编码标准。由于 unicode 中有更多可用的字符,所以许多 unicode 字符大于最大的 ASCII 字符。当出现Unicode 溢出,可改变程序的工作方式,出现进一步的安全问题。
3. 攻击者如何利用缓冲区溢出
攻击者可以将精心制作的数据输入程序,程序尝试将该输入数据存储在缓冲区中,输入数据会覆盖连接到缓冲区空间的部分内存。如果程序的内存布局定义明确,则攻击者可故意覆盖已知包含可执行代码的区域,然后用自己的可执行代码替换这些代码,改变程序的工作方式。通常,缓冲区溢出攻击都是按照如下步骤进行:
- 注入攻击代码
- 跳转到攻击代码
- 执行攻击代码
攻击者可利用的方法较多,下面介绍两种攻击者常用的缓冲区溢出攻击:
(1)利用栈溢出攻击破坏栈数据
可能被攻击者利用缓冲区溢出漏洞进行破坏的对象包括栈数据中的ARG(函数调用时的实参)、RETADDR(下一条要执行的操作指令在内存中的地址)、EBP(调用该函数前的栈帧状态值)和LOCVAR(该函数中的本地变量)。
常见的栈溢出攻击的利用方式是改变RETADDR的值,将已经注入到栈中的攻击代码的地址或代码区中某些具有特权的系统函数地址存放至RETADDR。若完成修改RETADDR的值,结束调用该函数后,程序就跳转到攻击者设计好的地址去执行攻击者希望被执行的指令,进而获得系统控制权限,导致严重的后果。EBP也常常作为被攻击的对象。攻击者通过构建一个RETADDR指向攻击代码的虚拟栈帧,再溢出当前栈帧EBP的值,溢出后的EBP值是构造的虚拟栈帧的地址。最终通过构造的虚拟堆栈的承接,执行完当前栈帧则执行虚拟栈帧,执行完虚拟栈帧则跳转到虚拟栈帧的RETADDR值所指向的位置,也使得程序最终跳转到攻击者设计好的地址去执行攻击指令。
(2)利用堆溢出攻击破坏堆数据
由于堆中是不连续地动态分配内存,攻击者预测地址的难度提高,堆溢出攻击比栈溢出攻击更困难,但依然有技术可以利用堆溢出实现攻击。
- Dword Shoot攻击:Dword Shoot指能够向内存任意位置写入任意数据,1WORD=4个bytes,即通过执行程序将4bytes的数据写入4bytes地址中,从而实现某种恶意操作。Dword Shoot攻击指利用Dword Shoot进行的恶意操作。Linux系统和windows系统对堆的管理方式都是双向链表方式,每一个分配的内存块由3部分组成:头指针(head)、尾指针(tail)、内存数据(data)。针对堆内存的管理主要有分配和释放两部分。在释放堆内存M时,会将M从链表上摘除,会执行
M→head→tail=M→tail
操作,如果攻击者通过溢出M临近的内存,将M的头指针、尾指针修改,让M的头指针指向攻击者设计好的虚拟节点,让M的尾指针指向攻击者设计好的位置,比如Shellcode,那么当执行完M→head→tail=M→tail
操作时,该虚拟节点的尾指针就指向Shellcode,调用该虚拟节点的尾指针就会转向Shellcode。摘链时的另一操作M→tail→head=M→head
,利用同样的原理,也可实现攻击。 - Heap Spray攻击:Heap Spray是在Shellcode前面加上大量的slide code(滑板指令,指不会影响程序执行,但占据内存空间,使真正的攻击指令得到执行的无意义指令),组成一个注入代码段。之后向系统申请大量内存,并且反复注入代码段来填充,然后结合其他的漏洞攻击技术控制程序流,使得程序跳转执行到堆上,使Shellcode得到执行,Shellcode中的核心攻击指令得到执行,进而获得系统控制权限,达到攻击目的。
4. 如何防止缓冲区溢出攻击
缓冲区溢出攻击可以使匿名的Internet用户有机会获得一台主机的部分或全部的控制权。如果能有效地消除缓冲区溢出的漏洞,则很大一部分的安全威胁可以得到解决。有几种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响,具体方法如下:
- 使用内置保护的语言:C 和 C++ 这两种脆弱性较高语言,由于不包含内置的保护措施以防止访问或覆盖内存中的数据,易受到缓冲区溢出攻击。Java、PERL 和 C# 等更现代的语言具有内置特性,可帮助减少缓冲区溢出的机会,但不能完全阻止缓冲区溢出。
- 编写安全的代码:使用能够帮助识别不安全函数或错误的编译器,利用编译器的边界检查来实现缓冲区的保护,避免使用不进行缓冲区检查的函数(例如,在C语言中,用
fgets()
代替gets()
)。- 完整性检查:在程序指针失效前进行完整性检查。 - 随机化地址空间: 关键数据区的地址空间位置随机排列。通常,缓冲区溢出攻击需要知道可执行代码的位置,而随机化地址空间使这几乎不可能。
- 防止数据执行:标记内存的某些区域为可执行或不可执行,从而阻止在不可执行区域运行代码的攻击。
- 当发现新漏洞时,尽快需要修补受影响的软件,并确保该软件的用户可以及时获取补丁。