在众多的网络攻击中,缓冲区溢出攻击一直是黑客最喜欢利用的攻击手段。关于此类的漏洞事件常有发生,日前,Sudo发布安全通告,修复了一个类Unix操作系统在命令参数中转义反斜杠时存在基于堆的缓冲区溢出漏洞(CVE-2021-3156),普通用户可以通过利用此漏洞,而无需进行身份验证,成功获取root权限,据报道这个漏洞已存在十年了,大部分的linux系统都存在这个sudo漏洞。
根据CNNVD 2020年以来每周的漏洞报告文档中,缓冲区溢出漏洞的所占总漏洞数量的百分比一直高居前五。缓冲区溢出是最常见的内存错误之一,也是攻击者入侵系统时所用到的最强大、最经典的一类漏洞利用方式。利用缓冲区溢出攻击,可以导致进程运行失败、系统宕机、重新启动等后果。更为严重的是,它可被利用来执行非授权命令,甚至可以取得系统特权,进而进行各种非法操作。
而要真正了解缓冲区溢出攻击的问题及其含义,必须首先了解缓冲区溢出漏洞。通过本文,为大家简析缓冲区溢出攻击的相关内容。
什么是缓冲区?
在与大家分享缓冲区溢出漏洞之前,首先跟大家分享一个概念:缓冲区。我们回顾一些计算机体系架构方面的基础知识,搞清楚缓冲区的概念以及CPU、寄存器、内存是怎样协同工作而让程序流畅执行的。
根据不同的操作系统,一个进程可能被分配到不同的内存区域去执行。但是不管什么样的操作系统、什么样的计算机架构,进程使用的内存都可以按照功能大致分成以下 4 个部分。
1、代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行。
2、数据区:用于存储全局变量等。
3、堆区:进程可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。动态分配和回收是堆区的特点。
4、栈区:用于动态地存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行。
以Windows平台为例,高级语言写出的程序经过编译链接,最终会变成PE文件,当PE文件被装载运行后,就成了所谓的进程。
PE文件代码段中包含的二进制级别的机器代码会被装入内存的代码(.text),处理器将到内存的这个区域一条一条地取出指令和操作数,并送入算术逻辑单元进行运算;如果代码中请求开辟动态内存,则会在内存的堆区分配一块大小合适的区域返回给代码区的代码使用;当函数调用发生时,函数的调用关系等信息会动态地保存在内存的栈区,以供处理器在执行完被调用函数的代码时,返回母函数。这个协作过程如图所示:
进程的内存使用示意图如果把计算机看成一个有条不紊的工厂,我们可以得到如下类比。
- CPU 是完成工作的工人。
- 数据区、堆区、栈区等则是用来存放原料、半成品、成品等各种东西的场所。
- 存在代码区的指令则告诉 CPU 要做什么,怎么做,到哪里去领原材料,用什么工具来做,做完以后把成品放到哪个货舱去。
- 值得一提的是,栈除了扮演存放原料、半成品的仓库之外,它还是车间调度主任的办公室。
缓冲区就是一块连续的计算机内存区域,可以是
堆栈(自动变量)、
堆(动态内存)和
数据区(全局或静态),可保存相同数据类型的多个实例。
缓冲区溢出漏洞如何产生?前文已经提到缓冲区是操作系统内存中一段连续的存储空间,当一段程序尝试把更多的数据放入一个缓冲区,数据超出缓冲区的预留范围时, 或者说当一段程序尝试把数据放入的内存位置超出了缓冲区的边界时, 便触发了缓冲区溢出漏洞;如果向一个缓冲区写入数据, 并且写入的数据量比缓冲区大时, 缓冲区溢出漏洞就会被触发,缓冲区溢出漏洞使攻击者能够执行恶意代码或者使程序崩溃,由于广泛使用的C和C++程序语言没有对数组读写数据进行边界检查的机制, 导致了这一漏洞常常被攻击者所利用。成功地利用缓冲区溢出漏洞可以修改内存中变量的值,甚至可以劫持进程,执行恶意代码,最终获得主机的控制权。
缓冲区溢出攻击方式缓冲区溢出攻击按照攻击后果的严重程度可分为导致程序崩溃(多数情况下会导致拒绝服务攻击)和获得系统控制权限两种。通常, 缓冲区溢出攻击都是按照如下步骤进行:1、注入攻击代码;2、跳转到攻击代码;3、执行攻击代码。其中, 第(2)步是利用缓冲区溢出漏洞实现攻击的核心环节。缓冲区溢出按照所攻击对象的不同可分为3类:
1、破坏栈数据栈数据中的ARG(函数调用时的实参)、RETADDR(下一条要执行的操作指令在内存中的地址)、EBP(调用该函数前的栈帧状态值)和LOCVAR(该函数中的本地变量)都是可能被攻击者利用缓冲区溢出漏洞进行破坏的对象。其中最常见的就是利用缓冲区溢出改变RETADDR的值, 使其存放已经注入到栈中的攻击代码的地址或者是代码区中某些具有特权的系统函数地址(比如system)。如果修改成功, 当该函数调用完毕后, 程序就跳转到攻击者设计好的地址去执行攻击者希望被执行的指令, 进而获得系统控制权限, 导致严重的后果。此外, EBP也常常作为被攻击的对象。攻击者通过构建一个RETADDR指向攻击代码的虚拟栈帧, 再溢出当前栈帧EBP的值, 让溢出后的EBP值是构造的虚拟栈帧的地址。这样, 最终通过构造的虚拟堆栈的承接, 执行完当前栈帧则执行虚拟栈帧, 执行完虚拟栈帧则跳转到虚拟栈帧的RETADDR值所指向的位置, 也使得程序最终跳转到攻击者设计好的地址去执行攻击指令。
2、破坏堆数据由于堆中是不连续地动态分配内存, 攻击者预测地址的难度提高, 堆溢出攻击比栈溢出攻击更困难, 但依然有技术可以利用堆溢出实现攻击。
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攻击:攻击者向系统申请大量内存, 并将有大量滑板指令的Shellcode作为注入攻击载荷(所谓滑板指令是指不会影响程序执行, 但却占据内存空间, 因此有利于真正的攻击指令得到执行的无意义指令), 反复进行填充.然后结合ROP等技术控制程序流, 使得程序跳转执行到被大量注入的攻击载荷所占据的堆上, 从而Shellcode得到执行, Shellcode中的滑板指令不会影响程序执行, Shellcode中的核心攻击指令得到执行, 进而获得系统控制权限, 达到攻击目的。
Heap fengshui攻击:对于Windows XP SP2之后的微软的操作系统来说, 由于堆中内存空间的分配函数(HeapAlloc)在其分配的内存块(chunk)的管理头部中加入了cookie校验信息, 并且实现了空闲内存块的双向链表的安全删除技术, 因此利用堆腐烂技术覆盖内存块的管理头部中的数据变得非常困难。另外, Heapspray技术需要占用大量系统资源, 且可能耗时较长, 针对上述问题, Sotirov提出了Heap fengshui攻击技术, 该技术根据操作系统堆内存管理方式, 通过主动设计回收内存、清理堆中内存碎片来保证攻击者分配的内存在物理上是连续的。与Heap Spray攻击相比, 该技术无需反复填充攻击代码, 占用内存较为合理, 实用价值较高。
3、更改类函数指针类函数指针分为两类:一类是真正意义上的函数指针, 它指向函数的存储位置, 可通过解引用指针的方式调用函数; 另一类是固定缓存区(jmp_buf类型变量), 该类型变量存放程序的状态信息, 用于实现C语言中的非局部跳转.固定缓存区的正确用法有如下两步:首先, 通过setjmp()宏把当前程序的状态信息保存到jmp_buf类型变量中; 然后, 通过longjmp()恢复程序状态信息进而实现非局部跳转。固定缓存区和函数指针功能类似, 前者存放的是程序的状态信息, 后者存放的是函数的位置信息.它们都能实现跳转到某位置, 然后去执行某段指令的功能, 这为攻击者提供了机会.攻击者通过溢出类函数指针附近的缓冲区, 以达到改写类函数指针中存放内容的目的, 进而使程序跳转到攻击者设计好的位置, 去执行攻击者希望被执行的代码, 以达到获得系统控制权限或者导致程序崩溃的目的.更改类函数指针可以攻击内存中栈、堆、数据段。当程序采用防止RETADDR被修改的策略进行保护后, 这种攻击方式比较有效, 因为它不会更改保存的RETADDR。讲到这里,为大家分享了缓冲区溢出漏洞的定义及攻击方式,大家对该概念有了更深刻的理解。关于缓冲区溢出攻击的分析技术及安全防御将在缓冲区溢出攻击的分析(下)中为大家继续分享,敬请期待下一篇。
参考资料:http://www。jos。org。cn/html/2018/5/5504。htm#outline_anchor_18