什么是Canary保护

什么是Canary保护

0x01 简介

Canary是一种用来防护栈溢出的保护机制。其原理是在一个函数的入口处,先从gs/fs寄存器中取出一个4字节(eax)或者8字节(rax)的值存到栈上,当函数结束时会检查这个栈上的值是否和存进去的值一致(相关知识来源于网络)。

在32位程序上:

canary_x86

在64位程序上:

canary_x64

若一致则正常退出,如果是栈溢出或者其他原因导致canary的值发生变化,那么程序将执行___stack_chk_fail函数(见下图),继而终止程序。

stack_chk_fail

如果程序开启了canary保护,并且不知道canary的值是多少,那么就不能够进行ROP来劫持程序流程。

在GCC中开启canary保护:

-fstack-protector      //启用保护,不过只为局部变量中含有数组的函数插入保护
-fstack-protector-all  //启用保护,为所有函数插入保护
-fstack-protector-strong
-fstack-protector-explicit //只对有明确stack_protect attribute的函数开启保护
-fno-stack-protector //禁用保护

(以上知识来源于网络)

0x02 环境

ubuntu 16.04(已安装好pwn所需的各种工具)

0x03 实验

首先,先编写一段简单的测试程序,这里命名为canary.c,代码如下:

#include<stdio.h>
int main()
{
    char* name = "Sunny";
    printf("My name is %s");
    return 0;
}

然后先不开启canary保护(pie保护也不开)来编译一下,命令如下:

gcc -no-pie -fno-stack-protector -m32 -o no_canary canary.c

编译完成后习惯性的用checksec检查一下保护情况,发现canary保护和pie保护都已经关闭了:

checksec_no_canary

之后使用gdb开始调试,直接看一下main函数反汇编:

no_canary_disass

可以看到这里并没有前面简介中说的(从gs寄存器中取出一个4字节(eax)的值存到栈上)那段代码,从反汇编中也是可以看出是不存在canary保护的。

现在,重新编译一下源程序,并开启canary保护(不开启pie),使用以下命令:

gcc -no-pie -fstack-protector-all -m32 -o canary canary.c

编译完成后直接gdb调试,同样看下main函数反汇编:

canary_disass

很明显可以看出比上述未开启canary保护时多出来几行代码,其中就有前面简介中提到的“从gs寄存器中取出一个4字节(这里为ecx)的值存到栈上”以及__stack_chk_fail函数。

下面来动态调试一下,直接在canary保护的位置(0x080484a2)下断,然后运行,程序断到保护这里:

breakpoint

这里是将gs中的值取出来放到ecx中,直接单步一下,然后这里将ecx中的值(0x8fe5f000)放到[ebp-0xC]中,然后继续单步执行,当执行到0x80484d0时可以发现这里它将[ebp-0xC]的值又取了出来放到了ecx中,如下所示:

ebp-0xc

然后将从gs寄存器中取出的值与ecx进行异或,如果相等就直接跳转,这里直接执行,然后发现是满足条件的(通过查看标志寄存器发现ZF置为1):

zf

附标志寄存器图:

eflags