、前言
Linux下常用的拷贝函数(如strcpy、memcpy等)如果使用不规范,常常会导致栈溢出。也就是你的软件系统本身存在bug,这样就会成为别人攻击的目标。本文主要讲的是Linux系统如何给栈溢出增加防护网?攻击者又有什么手段绕过一层层防护网实现攻击?
2、栈溢出防御
2.1 地址随机化技术
Linux系统提供设置堆栈地址随机化技术,例如函数内部变量存在栈上,每次打印函数内部变量的地址,其值都是随机的。
用户可以通过读取或设置/proc/sys/kernel/randomize_va_space节点值对其属性进行修改。
其中0表示关闭地址随机化;2表示开启地址随机化。
Linux系统默认是开启了地址随机化技术的。
2.2 栈不可执行
当我们通过读取/proc/pid/maps会发现stack的权限是“rw-p”,这说明栈上的数据只有读写权限,没有执行权限。这样如果你把一段shellcode放到栈上,是无法执行的。
2.3 栈保护
在函数的入口和出口处加一段栈检测的汇编代码,用来检测rbp和返回地址是否被修改,这就是Linux的栈保护机制。
GCC编译的时候可以加-fstack-protector则开启栈保护机制,如果加上-fno-stack-protector则会取消栈保护机制。
可执行文件是否开启栈保护,可以objdump查看下汇编,如下是某函数的反汇编,可以确定该可执行文件开启了栈保护机制。
2.4 Intel X64改变函数参数存放方式
在Intel X86平台,函数的参数是压栈存放的,这样攻击者构造攻击函数的时候,攻击函数的参数直接放在栈上即可。但是在Intel X64平台上,函数的前6个参数是存在寄存器中的,这样提升了攻击者攻击的难度。
3、栈溢出攻击
3.1 Shellcode攻击
当程序执行call指令调用一个函数时,会把下一条指令的地址(返回地址)压栈,当这个函数执行完毕最终需要执行ret执行,该指令会从栈中取出返回地址给ip,程序继续执行。
所以针对栈溢出攻击,常规方法是修改栈中返回地址,使其指向一段shellcode。
如果这段shellcode存放在栈中,同时环境开启了栈不可执行话,这样的攻击就失效了,所以可以把shellcode存放在代码段中,就可以绕过栈不可执机制了。但是在代码段中新增代码涉及ELF文件的重新布局,这部分还是有一定难度的,后续细讲。
3.2 ReturnToLib技术
该技术是利用库函数代替shellcode实现攻击的一种方法。
具体讲,把攻击代码拆分,利用libc库函数,使得函数返回时,调用libc库函数来实现,这样就可以绕过栈不可执机制实现攻击。但是该技术存在一个问题,如果系统开启了地址随机化,libc的库函数每次运行的时候地址是随机的,这样攻击就失效了。
怎么解决地址随机化问题呢?需要寻找不变的量。ELF文件镜像本身是不变的,所以我们可以根据每个libc库函数的地址=libc基地址(变)+函数偏移(不变,readelf -s可查到)。
Eg. execve地址=运行中printf地址(变)-printf函数偏移(不变)+execve函数偏移(不变)
3.3 ReturnToProgramming
该技术是利用未随机化的模块内部的汇编代码,实现攻击的一种方法。
针对Intel X64平台,函数的前6个参数是存在寄存器中的,我们构造攻击的时候,需要寻找模块内部的汇编代码,实现控制流。
例如攻击代码需要执行setuid(0),假如模块内存存在”pop rdi ;ret”这样的汇编代码,则栈溢出时返回地址指向这样的汇编代码,同时往栈里一次压人参数0,setuid的函数地址。
4、后续
这里只是介绍了常见的栈溢出的攻防手段,后面会先针对Intel X64平台ROP攻击做深入分析。
欢迎扫码关注公众号“嵌入式Linux技术分享”,一起学习Linux吧