首先看一下Stack Canaries演进历史:
Stack Guard 是第一个使用 Canaries 探测的堆栈保护实现,它于 1997 年作为 GCC 的一个扩展发布。最初版本的 Stack Guard 使用 0x00000000 作为 canary word。尽管很多人建议把 Stack Guard 纳入 GCC,作为 GCC 的一部分来提供堆栈保护。但实际上,GCC 3.x 没有实现任何的堆栈保护。
GCC4.1开始,引入了Stack-smashing Protection(SSP,又称 ProPolice),它实现了两个功能:
Canaries值的生成,一般有几种方法:
一张图帮助理解:

下面通过调试,探索一下GCC中Canaries的具体实现。依然是使用Ubuntu x86环境,gcc version 4.9.2。
使用的程序代码如下:
void func(){ int i; char buffer[64]; i = 1; buffer[0] = 'a';} int main() { func(); return 0;}分别编译开启栈保护和去除栈保护的程序:
ez@ubuntu:~/workdir/Canaries$ gcc -fstack-protector -o demo_sp demo.c ez@ubuntu:~/workdir/Canaries$ gcc -fno-stack-protector -o demo_nosp demo.c
分别展示func函数的反汇编代码。
无栈保护代码:
(gdb) disass func Dump of assembler code for function func: 0x080483eb <+0>: push %ebp 0x080483ec <+1>: mov %esp,%ebp 0x080483ee <+3>: sub 0𝑥50, 0x50, 0x50,%esp 0x080483f1 <+6>: movl 0x1,-0x4(%ebp) 0x080483f8 <+13>: movb $0x61,-0x44(%ebp) 0x080483fc <+17>: leave 0x080483fd <+18>: ret End of assembler dump. (gdb)
观察到,使用-fno-stack-protector选项编译的程序,栈上没有任何保护措施。
开启栈保护代码:
(gdb) disass func Dump of assembler code for function func: 0x0804843b <+0>: push %ebp 0x0804843c <+1>: mov %esp,%ebp 0x0804843e <+3>: sub 0𝑥58, 0x58, 0x58,%esp 0x08048441 <+6>: mov %gs:0x14,%eax #读取gs寄存器,生成Canaries 0x08048447 <+12>: mov %eax,-0xc(%ebp) #在栈底部写入Canaries 0x0804844a <+15>: xor %eax,%eax 0x0804844c <+17>: movl 0x1,-0x50(%ebp) 0x08048453 <+24>: movb $0x61,-0x4c(%ebp) 0x08048457 <+28>: mov -0xc(%ebp),%eax 0x0804845a <+31>: xor %gs:0x14,%eax #函数返回前,检查Canaries值 0x08048461 <+38>: je 0x8048468 <func+45> #若未改变,跳到+45处正常返回 0x08048463 <+40>: call 0x8048310 <__stack_chk_fail@plt> #若发生栈溢出,执行__stack_chk_fail函数 0x08048468 <+45>: leave 0x08048469 <+46>: ret End of assembler dump. (gdb)
其中,从gs寄存器中读取的值,每次函数调用都是随机的,我们使用GDB调试验证之:
(gdb) b *0x08048447 Breakpoint 4 at 0x8048447 (gdb) r Starting program: /home/ez/workdir/Canaries/demo_sp Breakpoint 4, 0x08048447 in func () (gdb) i r eax eax 0xa3850c00 -1551561728 (gdb) r Starting program: /home/ez/workdir/Canaries/demo_sp Breakpoint 4, 0x08048447 in func () (gdb) i r eax eax 0xcc46de00 -867770880
通过跟踪__stack_chk_fail函数可以发现,它的实现比较复杂。大体流程是,首先调用__GI___fortify_fail函数,__GI___fortify_fail又调用__libc_message函数,__libc_message的最后调用backtrace_and_maps和__GI_abort函数,产生SIGABRT信号,并通过__GI___libc_secure_getenv根据系统环境变量决定是否产生coredump文件,__GI_abort执行到最后调用_exit,程序退出。
关于Canary名称的由来,我搜到一则有趣的小故事,矿井中的金丝雀

转载于:https://www.cnblogs.com/gm-201705/p/9901570.html
免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删