canary
简介:
我们知道,通常栈溢出的利用方式是通过溢出存在于栈上的局部变量,从而让多出来的数据覆盖ebp、eip等,从而达到劫持控制流的目的。然而stack canary这一技术的应用使得这种利用手段变得难以实现。canary的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。这个概念应用在栈保护上则是在初始化一个栈帧时在栈底设置一个随机的canary值,栈帧销毁前测试该值是否“死掉”,即是否被改变,若被改变则说明栈溢出发生,程序走另一个流程结束,以免漏洞利用成功。
原理:在一个函数的入口处,先从fs/gs寄存器中取出一个4字节(eax)或者8字节(rax)的值存到栈上,当函数结束时会检查这个栈上的值是否和存进去的值一致
如果一个程序开启canary保护,并且不知道canary的值是多少,就不能够进行ROP来劫持程序流程
在GCC中开启canary保护:
-fstack-protector 启用保护,不过只为局部变量中含有数组的函数插入保护
-fstack-protector-all 启用保护,为所有函数插入保护
-fstack-protector-strong
-fstack-protector-explicit 只对有明确stack_protect attribute的函数开启保护
-fno-stack-protector 禁用保护.
当一个程序开启了canary保护时,使用checksec检查会出现:
adef@ubuntu:~$ checksec microwave
[*] '/microwave'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
在Stack一行显示Canary found。此外,在函数栈帧初始化时也会在栈上放置canary的值并在退出时验证。我们看这个程序:
显然,一旦我们触发栈溢出漏洞,除非猜到canary的值,否则函数退出时必然会通过xor异或操作检测canary的值是否被修改,从而执行stack_chk_fail函数。
因此,我们要想办法获取canary的值,要么就要防止触发stack_chk_fail函数,或者利用这个函数。
0x01 泄露canary
很显然,通过利用漏洞来泄露出canary的值,从而在栈溢出构造payload时加入canary以通过检查。
首先我们看一下这里:
这里存在格式化字符串漏洞,可以通过这个漏洞来泄露出canary的值。
main函数中使用fgets函数将获取的输入作为参数传递给sub_F00,然后使用__printf_chk函数直接输出,存在格式化串漏洞,可以泄露内存:
这里要说一下FOPTIFY保护,这个保护比较少见,这是一个由GCC实现的源码级别的保护机制,其功能是在编译的时候检查源码以避免潜在的缓冲区溢出等错误。简单地说,加了这个保护之后(编译时加上参数-D_FORTIFY_SOURCE=2)一些敏感函数如read, fgets, memcpy, printf等等可能导致漏洞出现的函数都会被替换成__