深度解析Linux经典栈溢出攻击方式 (linux 经典栈溢出)
栈(Stack)是计算机内存中的一种数据结构,用于保存函数调用中的参数和局部变量等信息。但是,栈在实现过程中存在缺陷,即栈溢出(Stack Overflow),也就是在栈空间中写入超过其分配空间的数据。攻击者可以利用这个漏洞实现栈溢出攻击。而Linux系统是这类栈溢出攻击的主要受害者,因此本文将深入探讨Linux经典栈溢出攻击的实现原理和防范方法。
一、栈溢出攻击原理
我们需要了解一些基础概念。
1.1 调用栈
当一个函数被调用时,它的程序参数存储在栈中的一部分内存中,同时在栈的顶部存储了该函数的返回地址,也就是说当该函数执行完毕之后会返回到该地址处继续执行。调用栈是这样的一个数据结构,调用函数时使用,当函数返回时撤销之前的函数调用。
1.2 栈溢出
当向一个缓冲区写入超过其本身容量的数据时,数据就会覆盖那个缓冲区后面的内存地址。如果这个被写入的位置正好是调用栈元素的位置,那么就会覆盖这个调用栈上的栈帧(Stack Frame)中的数据,同时也会覆盖存储在栈中的返回地址。由此,攻击者就可以在函数返回时跳转到自己编写的代码中去执行,从而完全控制程序的执行流程。
一旦攻击者控制了程序的执行流程,他就可以做任何事情,例如启动后门程序、读取机密文件、改变环境变量、执行任意代码等等。由于栈溢出是一种常见的漏洞类型,恶意攻击者利用栈溢出攻击常常能够成功。
1.3 栈溢出攻击的实现方式
基本原理已经解释清楚了,攻击者实现这么一个栈溢出攻击的方法通常有两种:
1.3.1 覆盖函数返回地址
攻击者通过覆盖栈上的返回地址,使得当前函数执行完成时会跳转到攻击者编写的代码处继续执行。这个方法实现起来比较简单,只要知道栈帧的大小,计算出攻击者的代码地址就可以,因为跳转地址就是覆盖掉的返回地址。
1.3.2 利用shellcode
Shellcode可以看作是一段正常的程序,但是它的目标是执行攻击者想要的命令。攻击者可以在栈溢出时把shellcode放入到栈缓冲区中,然后同时覆盖掉返回地址,这样函数执行完毕时就会跳转到这个shellcode执行攻击者编写的程序,这种方式在绕过程序栈非执行区域的保护措施上比较有用。
二、Linux下的栈溢出攻击
Linux系统是一种受到极大威胁的操作系统。因此,对于Linux下的栈溢出攻击,我们需要重点关注以下几点。
2.1 编译器优化引起的问题
Ubuntu发行版下,gcc 5 开始默认启用了栈保护技术,即在栈上加入了一段特定的随机值作为stack canary,用于在函数返回时防止栈被破坏,这样就可以一定程度上防止栈溢出攻击。当栈被修改时,会触发 stack canary 检查,导致程序异常终止。
但是,在编写某些特定类型程序时,利用未经验证的用户输入,或者字符串格式化等可疑操作时,这种技术可能未能及时发现栈溢出攻击,同时这种技术对程序带来的额外负荷也是不能忽略的。
2.2 代码注入
代码注入通常是通过覆盖函数栈帧返回地址的方法实现的。在覆盖函数返回地址时,攻击者可以将指向攻击者编写的恶意代码的地址写入该位置。当缓冲区溢出时,程序就会跳转到恶意代码,并执行该代码。这种攻击方法几乎可以越过任何内存保护,从而成为Linux系统下更受欢迎的攻击类型之一。
2.3 思考:是否需要编写地址
上文提到的栈溢出攻击,常常需要覆盖函数栈帧中的返回地址,其中最重要的是攻击者需要知道攻击代码的地址。另外一种思路是直接使用已经存在的函数的地址(终止器函数)将代码放入堆栈上,从而在下次调用时执行该代码。这种方法不需要知道恶意代码的地址,因此会比较高效。这种方法跟之前的方法一样,同时也是比较受欢迎的栈溢出攻击方式,特别是在堆栈上跳转到快速终止器函数调用的时候。
三、防范栈溢出攻击的方法
3.1 栈保护技术
很多操作系统现在都提供了一种在栈中加入 stack canary 的保护机制。这种机制会在栈中添加一个随机数,随机数的值只有在函数返回时才能计算出来。这个随机数会被保存在一次返回的信息中,因此,如果在返回时该值被修改了,那么程序就会中断执行,从而避免了栈溢出攻击的发生。
3.2 编译器选项
编译器有一些选项可以使得栈更安全,比如可以让编译器自动寻找可疑的缓冲区、强制编译器检测函数调用的大小。
3.3 堆栈空间初始化
很多攻击者利用指针未被初始化的漏洞来实现栈溢出攻击,因此,适当的初始化变量非常有必要。在一些高级语言中则更为自动化,例如 Java、Python、C# 等高级语言会将变量自动置为 null 或 0。
3.4 输入验证
正常的输入应该被验证其完整性和大小,并且需要确保不会造成过度的数据输入。同时,输入验证还可以保证程序在运行期间不会出现意外溢出。
3.5 限制系统权限
操作系统不需要 root 权限就可以对程序进行控制和执行,因此操作系统对程序所属的用户、资源的权限等方面的控制也是很重要的。
对于栈溢出攻击方面,Linux系统并没有真正解决这个问题,因此,良好的程序设计、开发和实施安全措施是保护 Linux 系统的更佳方法。希望今天的文章能够为大家了解 Linux 系统栈溢出攻击提供一些参考,同时也帮助大家更好地应对这种致命漏洞。