Linux中的栈缓冲区溢出
总结Linux中的栈缓冲区溢出漏洞的缓解方法
前言
缓冲区溢出漏洞是一种广泛存在且危害严重的漏洞,发现至今,关于此类漏洞的利用与防御技术不断发展.其中,栈缓冲区溢出漏洞的缓解机制主要有不可执行技术(NX,on executable)
、地址随机化技术(ASLR,address space layout randomization)
和栈保护机制(SSP,stack smashing protector)
,相对应地也存在一些绕过手段,例如return-to-libc
和ROP
技术可以绕过NX
;基于信息泄露的攻击使ASLR
失效等.然而针对SSP
防御机制,目前却没有有效的绕过技术
Stack Canaries
Canaries(金丝雀),取名自地下煤矿的金丝雀,因为它能比矿工更早地发现煤气泄漏,有预警的作用。这是一种对抗栈溢出的技术,又称之SSP栈保护机制,或者Stack Cookies,目前有4种具体的实现技术:StackGuard、StackShield、ProPolice以及XOR技术。
Canary的值是栈上的一个随机数,在程序启动时随机生成并保存在比函数返回地址更低的位置。由于栈溢出是从低地址向高地址进行覆盖,因此攻击者要想控制函数的返回指针,就一定要先覆盖到Canary。程序只需要在函数返回前检查Canary是否被篡改,就可以达到保护栈的目的。
Canaries通常可分为3类:terminator、random和random XOR,具体的实现有StackGuard、StackShield、ProPoliced等。
例子
我们通过一个例子来了解金丝雀机制是如何工作的。
1 |
|
1 |
|
通过objdump -S ./a.out
查看反汇编。
1 |
|
在开启了栈保护之后,我们如果输入超过10字节的内容会发生什么?
1 |
|
可以看到有东西阻止了错误。如果没有使用保护机制的话就会直接发生段错误。
分析二进制代码
现在我们查看main
函数的二进制代码,可以看到mov %fs:0x28,%rax
这样一句话。在Linux中,fs寄存器被用于存放线程局部存储(Thread Local Storage, TLS),TLS主要是为了避免多个线程同时访存同一全局变量或者静态变量时所导致的冲突,尤其是多个线程同时需要修改这一变量时。TLS为每一个使用该全局变量的线程都提供一个变量值的副本,每一个线程均可以独立地改变自己的副本,而不会和其他线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。而从全局变量的角度看,就好像一个全局变量被克隆成了多份副本,每一份副本都可以被一个线程独立地改变。在glibc的实现里,TLS结构体tcbhead_t的定义如下所示,偏移0x28的地方正是stack_guard。
Ⅰ. Glibc中
1.内核提供的随机数生成器_dl_random产生随机数
2.函数_dl_setup_stack_chk_guard根据系统是32位还是64位将随机数处理成4byte或8byte的canary,并赋给变量__stack_chk_guard
3.宏THREAD_SET_STACK_GUARD通过处理将变量__stack_chk_guard放入结构体tcbhead_t的成员stack_guard中(即fs:28h)
Ⅱ. GCC中
4.选择canary的插入位置, 以及引入与canary有关的汇编代码
Ⅲ. 程序运行中
5.程序函数开头从 fs:28h 中取出8字节的值插入到栈中,同时清空rax中的副本
6.函数结束时,程序会再次从 fs:28h 中将canary的值取出与栈上的canary进行比较
7.如果canary不同则跳到函数 ___stack_chk_fail 直接终止程序,否则继续执行程序
gcc中的安全选项
1 |
|
控制流保护
有时我们在反汇编的时候会看到endbr64
这样一条指令,这条指令就是控制流保护指令,防止攻击者对于控制流的劫持。需要注意的是,endbr64
指令通常是由编译器在生成代码时自动插入的,而不是由程序员直接编写的。这是因为它是与控制流保护相关的低级指令,通常不需要直接操作。