核保护机制(SSP)是缓解栈缓冲区溢出漏洞攻击最有效的安全机制,通过系统生成的随机数保证栈不被修改,目前关于SSP机制的绕过技术主要是基于暴力破解.为此,揭示了一种可以泄露随机数的安全缺陷模型,由于操作系统没有及时清空死亡栈帧,导致随机数可能存在于无效空间,利用此特性的绕过方式被称为Canary复用.实验验证了这种安全缺陷的可利用性与稳定性,基于此特点,提出了两种有效的解决方案.
Stack smashing protector(SSP) is the most effective security mechanism to mitigate the stack buffer overflow vulnerability, which guarantees stack unmodified by generating random numbers. At present, the main technology to bypass SSP mechanism is based on brute force attack. This paper reveals a security defect model which can reveal the random number. Because the operating system does not empty the dead stack frame in time, the random number exists in the invalid space, and the bypass to leverage this characteristic is called reuse of canary attack. The experiment proves the usability and stability of this security model. Based on this feature, two effective solutions are proposed.
缓冲区溢出漏洞是一种广泛存在且危害严重的漏洞,发现至今,关于此类漏洞的利用与防御技术不断发展.其中,栈缓冲区溢出漏洞的缓解机制主要有不可执行技术(NX,on executable)、地址随机化技术(ASLR,address space layout randomization)和栈保护机制(SSP,stack smashing protector),相对应地也存在一些绕过手段,例如return-to-libc[1]和ROP[2]技术可以绕过NX;基于信息泄露的攻击[3]使ASLR失效等.然而针对SSP防御机制,目前却没有有效的绕过技术.
SSP安全机制又被称为Canary或者Stack Cookies,目前有4种具体的实现技术:StackGuard、StackShield、ProPolice以及XOR技术[4].历史上存在一些绕过SSP的手段,例如通过溢出栈内指针劫持进程[5];根据守护进程(forking daemon)每次重新启动时Canary值不变的特性,逐字节猜测Canary[6];通过rdtsc指令来破解随机数等.然而对于现代最新版本的操作系统来说都已失去了作用,现在操作系统一般根据计算机周围环境的噪声信息生成熵池,并利用SHA、MD5等哈希算法产生随机数[7],使得攻击者不能根据环境信息和当前随机数猜测熵池中的任何有用信息,即猜测Canary几乎成为不可能的事情.因此,Canary的安全性成为栈缓冲区溢出漏洞利用与防御的关键.
基于这种现状,发现了一种有效绕过SSP防御机制的技术,并提出相应的防御机制.总体来说,本文主要贡献如下.
1) 证明根据环境信息猜测系统随机数的不可能性;
2) 分析Linux随机数生成和传递机制,发现Canary泄露的缺陷;
3) 基于Canary泄露缺陷,提出Canary复用模型,并通过实验验证此模型在绕过SSP机制的有效性和稳定性;
4) 针对这种攻击给出2条有效的防御措施.
1 Canary机制 1.1 Linux随机数生成器现代Linux操作系统的随机数系统由3个熵池构成,熵池为内核和用户提供随机数.由内核文件random.c可知,熵池通过4个函数
add_device_randomness、add_input_randomness、add_interrupt_randomness和add_disk_randomness 4个函数.分别提供设备、键盘和鼠标、中断、硬盘信息生成随机数.以add_input_randomness为例(图 1),系统根据以下步骤添加随机数:
1) 将鼠标和键盘的信息编码;
2) 调用add_timer_randomness函数增加时间信息,最终生成12字节;
3) 调用_mix_pool_bytes函数通过一个本原多项式和数据表逐字节处理并添加进熵池.
熵池通过get_random_bytes函数为内核提供随机数,通过设备/dev/random和/dev/urandom为用户提供随机数,无论哪种方式最终都是通过函数extract_entropy函数产生随机数. extract_entropy函数通过3次SHA-1算法抽出需求长度的随机数,并且修改当前熵池.由Canary生成过程可知:
1) 系统对环境信息的编码包含大量的当前时间信息,并且精确到10-9,为攻击者判断和控制增加了难度;
2) 3次SHA-1算法的操作后,使得可控制的环境信息分散到巨大的熵池中,以至于可控信息几乎为零.
由上述分析可知,随机数的添加与熵池当前状态、时间信息和噪声信息密切相关,随机数的提取也与熵池当前状态息息相关,并且算法复杂.因此,试图通过控制环境噪声来控制熵池难度极大,通过已知随机数反向推导熵池信息或者预测其后随机数也是不可行的.
1.2 Canary传递由内核头文件stackguard-macro.h可知,对于32位Linux系统Canary存储在gs:0x14,而对于64位Linux系统Canary存储在fs:0x28.在gdb调试器下对gs:0x14监视,可以发现其在security_init函数发生变化,此函数来源于动态链接库ld-linux.so.2.代码1是函数security_init的反汇编代码,此代码表明gs:0x14在security_init+40被赋值,追溯代码发现security_init是从地址为0xbffff53b<_dl_random>中取值,并且将最低位清零,最终赋值给gs:0x14. Canary的生成和传递过程如下.
Dump of assembler code for function security_init:
0xb7fdff20<+32>:mov ecx, DWORD PTR [eax]
0xb7fdff22<+34>:xorcl, cl
0xb7fdff24<+36>:mov DWORD PTR [esp+0x28], ecx
0xb7fdff28<+40>:movDWORD PTR gs:0x14, ecx
继续追踪_dl_random的值,发现其在动态链接库开始前已经被赋值,因此_dl_random是由操作系统内核初始化得到的.事实上,当Linux装载并执行ELF文件时,随机数的生成和传递遵循以下过程(见图 2):
① 首先在用户层面通过bash调用fork函数生成新的进程,新进程通过execve系统调用将控制权交给内核[8];
② 内核通过get_random_bytes函数申请随机数,并最终通过extract_entropy函数获得;
③ ELF的装载是通过动态链接库完成的,在这一阶段security_init函数将随机数Canary存储在_dl_random的位置;
④ ELF执行时从_dl_random位置取得Canary,并将其放入栈中.
2 安全缺陷 2.1 Canary泄露风险尽管Linux随机数生成器安全性很高,但是在Canary废弃之后,系统并没有及时对Canary信息进行清除,具体的是没有对承载Canary信息的栈帧进行清空操作,因此存在泄露风险.首先,提出2个断言:
1) 同一线程的Canary值完全相同;
2) 当栈帧被回收时,栈帧内容没有及时清空.
根据Linux系统内核头文件stackprotector.h的说明可知,Canary作为一个全局变量对于每个线程只有一份值,也即对于所有需要保护的栈帧都是相同的.另一方面,函数返回时操作系统只是简单地修改栈顶指针和栈底指针,并没有清空死亡栈帧的数据,这样就造成栈数据泄露的风险.具体地,对于已被回收栈帧的Canary值,由于没有被清零,所以存在再次被分配到其他栈帧的风险,此时Canary就可能被保存到局部变量,造成Canary泄露.
2.2 Canary复用模型基于Canary泄露风险机制,提出Canary复用模型,即利用死亡栈帧未及时清除的Canary信息,完成随机数验证,进而绕过SSP.以图 3为例,假设脆弱性软件依次调用了函数func1、func2,并且在2个函数返回时继续调用func函数,且函数大小满足func1.size<func.size.
因此当调用func函数时,func2.Canary很可能被包含在func的局部变量中,如果此变量没有赋值,那么Canary的值就会被func函数操作.对于普通的缓冲区溢出攻击,在覆盖func的返回地址前会不可避免地修改func.canary,这会触发SSP机制,进而遏制攻击.然而如果在缓冲区溢出攻击之后,存在类似于memcpy (ptr1, ptr2) 的栈内操作,其中ptr1和ptr2分别是以func2.canary和func.canary结尾的缓冲区,那么就可以将func.canary修改为func2.canary,又由于同一线程不同函数的Canary值相同,因此这种攻击就可以绕过SSP的安全校验.
更一般的模式是,在线程的函数调用过程中,如果存在func1, func2, …, funcn的函数调用序列,对于funci (i>2),如果存在funci. size>funci-1. size,并且funci函数中存在一个额外的栈内复制操作,就可以完成Canary复用的操作.
综上所述,基于Canary复用模型的缓冲区溢出攻击必须满足以下3个条件:
1) 存在经典的栈缓冲区溢出漏洞;
2) 栈帧存在前栈帧Canary残留信息;
3) 存在Canary重用途径.
3 实验验证据上述Canary复用的原理和模型,实现了一个验证程序,并通过此程序验证Canary复用模型的有效性(所有实验和原理分析都是基于版本号为3.8的32位Ubuntu系统,并且其共享库glib为2.5版本).代码2为验证程序伪代码,函数func1和func2分别生成2个包含Canary保护的栈帧,而当func函数被调用时,func2函数栈帧残留的Canary被存储在buf[36],因此func函数最后一行会修正其栈帧的Canary值.当main函数的输入参数长度过大时,将会造成经典的栈缓冲区溢出,而无视已经开启的SSP防御机制.
代码2 Canary复用模型的验证程序
function func2:
var buf[1…20]
function func1:
var buf[1…20]
call fun2
function func (arg):
var c[1…20]
var buf[1…64]
memcpy (c, arg, 40) △buffer overflow
memcpy (c, & buf[16], 26) △correct Canary
func main:
func1
func (arg)
实际实验中,控制main参数如下:
gdb
执行程序,程序崩溃并停止在“0x41414141”.表明程序成功绕过了SSP机制.如果精确构造输入,那么可以通过经典的ret2libc或ROP方法很容易达到任意代码执行的目的.
4 防御措施上述验证程序需要2次字符串操作,第1次通过传统方法覆盖返回地址,第2次通过残留的Canary来回填被第1次字符串操作修改的Canary,由于不同栈帧的Canary是相同的,因此能够成功触发溢出.据上述特点,提出如下防御建议.
1) 为了使Canary不被复用,可以考虑每个栈帧使用不同的Canary.首先通过Linux系统生成一个真随机数,并进一步生成一个伪随机数序列.每次需要Canary时都从这个序列读取随机数,保证了不同栈帧的随机数是不同的.虽然伪随机数序列安全度不高,但是如果其种子是由内核生成的随机数,那么对于攻击者,Canary的值还是不易被猜测.
2) 如果函数返回时,能够在回收栈空间的同时,对不用数据清零,那么就会避免随机数的泄露,进而避免这种利用方式.这种办法能从根源上杜绝栈帧信息的泄露,需要从编译器上操作,并且开销也是比较大的.假设除Canary泄露的其他信息泄露不会产生严重威胁,那么可以考虑只将Canary的信息清零,这样代价比较小,而且可以避免Canary复用攻击.
在程序调试态下,通过gdb手动清理func2的Canary值,结果显示程序会触发SSP安全机制,也证明了这种防御机制的有效性.
5 结束语Canary是操作系统生成的高强度随机数,从生成到应用都具有很强的安全性,然而操作系统并没有对Canary进行有效的清除. Canary复用的安全缺陷能够在不知道Canary具体值的情况下,通过栈上操作完成稳定的栈缓冲区溢出攻击,危害巨大.提出的防御措施,能够从编译的层面消除这种安全隐患,清除死亡栈帧的残留信息,有力避免基于Canary复用的攻击.
[1] | McDonald J. Defeating Solaris/SPARC non-executable stack protection[J]. Bugtraq, 1999. |
[2] | Buchanan E, Roemer R, Shacham H, et al. When good instructions go bad: generalizing return-oriented programming to RISC[C]//Proceedings of the 15th ACM conference on Computer and Communications Security. New York: ACM, 2008: 27-38. |
[3] | Roglia G F, Martignoni L, Paleari R, et al. Surgically returning to randomized lib (c)[C]//25th Amual Computer Security Applications Conference. Honolulu: IEEE, 2009: 60-69. |
[4] |
吴咏, 汪晓茵, 刘凤霞. Linux系统缓冲区溢出防护技术[J]. 保密科学技术, 2012(5): 21–25.
Wu Yong, Wang Xiaoyin, Liu Fengxia. Buffer overflow protection technology of Linux system[J]. Secrecy Science and Technology, 2012(5): 21–25. |
[5] | Bulba K. Bypassing stackguard and stackshield[J]. Phrack, 2000, 10(56). |
[6] | Bittau A, Belay A, Mashtizadeh A, et al. Hacking blind[C]//2014 IEEE Symposium on Security and Privacy. San Jose: IEEE, 2014: 227-242. |
[7] | Gutterman Z, Pinkas B, Reinman T. Analysis of the linux random number generator[C]//2006 IEEE Symposium on Security and Privacy. Berkeley/Oakland: IEEE, 2006: 371-385. |
[8] | 俞甲子, 石凡, 潘爱民. 程序员的自我修养[M]. 北京: 电子工业出版社, 2009: 175-176. |