漏洞利用的姿势各种各样,相应的操作系统、编译器等肯定也会有各种对应着的防护措施。本文就简单谈谈现有的利用缓解措施,以及对部分措施要如何去绕过实现利用(本文主要参考 Exploit mitigation techniques and defeat methods)
Address Space Layout Randomization (ALSR)
- 随机化的地址包括:mmap()创建的内存地址(库、vdso...)、栈、堆
- ASLR是系统级的保护,由/proc/sys/kernel/randomize_va_space文件中的数值控制
- 0:关闭ASLR
- 1:将mmap基址、栈随机化,从而库地址、vdso页面也会被随机化
- 2:在1的基础上增加对堆的随机化
- 在32位的linux上运行程序,或在64位linux上运行32位的程序,可以通过 ulimit -s unlimited 关闭系统对mmap()的随机化,因此库地址、vdso的地址都是固定的。这是由arch/x86/mm/mmap.c中代码逻辑导致的(参考CVE-2016-3672)
首先,只有当mmap_is_legacy()!=0时,mm->mmap_base才有可能是固定值(通过ulimit -s unlimited 将栈大小设为无限大时,mmap_is_legacy()=1);
其次,当mm->mmap_base = mm->mmap_legacy_base后:
在打补丁前版本中(至少4.5.2版本还未打补丁),mm->mmap_legacy_base=mmap_legacy_base(),即若在32位(x86)机器上,mm->mmap_legacy_base是固定值,否则会加一个随机偏移。因此在32位机器上,通过ulimit -s unlimited 可以使得 mmap_base是固定值。
在打补丁后版本中,mm->mmap_legacy_base直接就是基址+随机偏移,因此就算将栈设为无限大,也无法使得mmap的地址固定。
- 现有的针对ASLR的攻击主要是通过暴力破解(brute force),通过多次查看内存布局来了解地址的哪些位是在变化,从而只对变化的位进行暴力破解
- ldd:查看加载的库地址
- /proc/$pid/maps:查看进程pid的内存布局
- gdb中 info proc map:查看当前进程的内存布局
Non-executable Stack (NX)
- 添加对栈不可执行的保护,大部分现代CPU都会限制栈、堆中数据执行
- 用于防止通过缓冲区溢出在栈中写入shellcode并覆盖返回地址,来实现控制流劫持的攻击,但无法抵抗return-to-libc和ROP攻击
- gcc编译时添加选项 -z execstack 可以关闭NX保护
Stack protector (ProPolice)
- gcc编译时添加选项 -fstack-protector,则会在返回地址前加上一个随机的stack canary/cookie,当函数返回时会去判断当前位置的数值与canary是否一致,若不一致则报错
- 相应的,编译时添加选项 -fno-stack-protector 即可关闭canary保护
- 检查canary是否一致的函数在libc中,因此可以通过其他方法(如格式化字符串漏洞)修改该函数在GOT表中的值,从而绕过该保护
Heap protector
- GNU C函数库提供了用于检测堆内存一致性的库函数,可用于检测corrupted-list/unlink/double-free/overflow等漏洞,如mcheck函数(定义在/usr/include/mcheck.h中)在malloc之前被调用,来检测堆内存的一致性(参考 Heap Consistency Checking)
- 这些函数主要是用于检测:是否存在堆溢出破坏堆块的头信息从而造成任意代码执行的行为
Tips
- 如何查看目标程序开启了哪些安全防护:checksec.sh工具、gdb中checksec命令