打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
canary分析

canary analysis

@(GCC | Glibc | kernel)[GNU System-Security] –zet

00 导引

由于stack overflow而引发的攻击非常普遍也非常古老,相应地一种叫做canary的mitigation技术很早就出现在gcc/glibc里,直到现在也作为系统安全的第一道防线存在.

canary不管是实现还是设计思想都比较简单高效,就是插入一个值,在stack overflow发生的高危区域的尾部,当函数返回之时检测canary的值是否经过了改变,以此来判断stack/bufferoverflow是否发生.

canary的实现大约是1998年就在gcc里出现出现了第一个合并入upstream的patch,只不过后期的实现将主要的功能移入了Glibc,并且调整了局部数据和canary的位置,以扩大数据的受保护范围.因为canary的出现的很早,所以本文涉及到的GNU toolchains源代码版本来说选择非常自由,我使用的是我熟悉的版本: GCC-4.8.2 & eglibc-2.19 & linux-4.8.

01 示例

在gcc的调用参数里有以下跟canary有关系:

-fstack-protector对包含有malloc族系和内部的buffer大于8字节的函数使能canary.

-fstack-protector-all对所有函数使能canary.

-fstack-protector-strong

对包含有malloc族系或者内部的buffer大于8字节的或者包含局部数组的或者包含对local frame地址引用的函数使能canary.

-fstack-protector-explicit只对有明确stack_protect attribute的函数使能canary.

-fno-stack-protector禁用canary.

下面首先给出一个例子,展示一下canary在应用程序级别对代码的直接影响.

#include <stdio.h>int main () {        int local = 1;        char buffer[8];        buffer[7] = 2;        puts("42\n");        return 0;}
$gcc -m32 -fstack-protector-all -g -o canary canary.c/// gdb查看运行时的反汇编代码///   // classical prologue.   |0x804846d <main at canary.c:3>          push   %ebp   |0x804846e <main+1 at canary.c:3>        mov    %esp,%ebp   |0x8048470 <main+3 at canary.c:3>        and    $0xfffffff0,%esp   |0x8048473 <main+6 at canary.c:3>        sub    $0x20,%esp   // %gs:0x14里面存储的就是canary值,并将其插入在地址esp+1c处.   |0x8048476 <main+9 at canary.c:3>        mov    %gs:0x14,%eax   |0x804847c <main+15 at canary.c:3>       mov    %eax,0x1c(%esp)   |0x8048480 <main+19 at canary.c:3>       xor    %eax,%eax   // 下面的两行代码可以看出gcc对buffer和local的位置进行了重排,按照一般情况来说   // 局部变量的声明先出现的先分配,也就是地址更大,也就是说应该是被赋值为1的局部   // 变量laocal的地址更大.但是这里进行了调整.   |0x8048482 <main+21 at canary.c:5>       movl   $0x1,0x10(%esp)   |0x804848a <main+29 at canary.c:6>       movb   $0x2,0x1b(%esp)   // 入参准备puts()的调用   |0x804848f <main+34 at canary.c:7>       movl   $0x8048550,(%esp)   |0x8048496 <main+41 at canary.c:7>       call   0x8048340 <puts@plt>   |0x804849b <main+46 at canary.c:9>       mov    $0x0,%eax   // 取出插入的canary值与(%gs:0x14)处的原值做比较.   |0x80484a0 <main+51 at canary.c:10>      mov    0x1c(%esp),%edx   |0x80484a4 <main+55 at canary.c:10>      xor    %gs:0x14,%edx   |0x80484ab <main+62 at canary.c:10>      je     0x80484b2 <main+69 at canary.c:10>   // 如果canary的值发生了篡改,那么将调用__stack_chk_fail.   |0x80484ad <main+64 at canary.c:10>      call   0x8048330 <__stack_chk_fail@plt>   |0x80484b2 <main+69 at canary.c:10>      leave   |0x80484b3 <main+70 at canary.c:10>      ret

根据上面的代码可以画出如下的stack结构.

        High        Address |                 |                +-----------------+                | arg1            |                +-----------------+                | return address  |                +-----------------+        ebp =>  | old ebp         |                +-----------------+     esp+1c =>  | canary value    |                +-----------------+     esp+1b =>  | char[7]         |                | ...             |     esp+14 =>  | char[0]         |                +-----------------+     esp+10 =>  | local           |                +-----------------+        Low     |                 |        Address

上面的分析可以清楚地看出canary的原理,下面将分析canary在gcc和glibc里的实现细节.

02 细节

canary的实现分为两部分,gcc编译时选择canary的插入位置,以及生成含有canary的汇编代码,glibc产生实际的canary值,以及提供错误捕捉函数和报错函数.也就是gcc使用glibc提供的组件,gcc本身并不定义.这样会让canary的值会是一个运行时才动态知道的值,而不能通过查看静态的bianry得到.

gcc

在gcc里跟canary的实现分为两部分: 引入canary值的比较的代码,这是通过对外部变量(__stack_chk_guard)引用来实现,以及插入canary比较出错时输出异常的代码,这是通过对外部定义的异常函数(__stack_chk_fail)的调用来实现的.

也就是说gcc只是声明和使用了__stack_chk_guard/__stack_chk_fail,并没有定义.定义是在glibc里.

__stack_chk_guard和__stack_chk_fail的插入是在gcc将GIMPLE转换为RTL的pass里分别通过函数default_stack_protect_guard()和ix86_stack_protect_fail()构建手动的tree,然后调用expand_normal()自动转换为RTL再插入待分析的用户代码来进行的.

在gcc中这部分的实现代码的调用栈如下:

// 注意由下至上的调用序.#0  ix86_stack_protect_fail () at ../../gcc/config/i386/i386.c:37607#0  default_stack_protect_guard () at ../../gcc/targhooks.c:635#1  stack_protect_prologue () at ../../gcc/function.c:4646#2  gimple_expand_cfg () at ../../gcc/cfgexpand.c:4641#3  execute_one_pass () at ../../gcc/passes.c:2333#4  execute_pass_list () at ../../gcc/passes.c:2381#5  expand_function () at ../../gcc/cgraphunit.c:1640#6  output_in_order () at ../../gcc/cgraphunit.c:1833#7  compile () at ../../gcc/cgraphunit.c:2037#8  finalize_compilation_unit () at ../../gcc/cgraphunit.c:2119#9  c_write_global_declarations () at ../../gcc/c/c-decl.c:10118#10 compile_file () at ../../gcc/toplev.c:557#11 do_compile () at ../../gcc/toplev.c:1864#12 toplev_main () at ../../gcc/toplev.c:1940#13 main () at ../../gcc/main.c:36

gcc对__stack_chk_guard的使用其实还涉及到跟linux kernel的相关的部分.

gcc-4.8.2/gcc/config/i386/gnu-user.h
#line 153// 也就是其实__stack_chk_guard的值是由kernel提供的一个随机数.// 其实这也是最基本的情况,glibc还实现了不由kernel提供的另外的代码.// gs寄存器是一个跟TSL有关系的寄存器,一般情况下TLS肯定开启使用的,所以最普遍的情// 况就是__stack_chk_guard由kernel提供.#ifdef TARGET_LIBC_PROVIDES_SSP/* i386 glibc provides __stack_chk_guard in %gs:0x14.  */#define TARGET_THREAD_SSP_OFFSET	0x14

gcc使用了__stack_chk_guard和__stack_chk_fail之后就完成了canary实现的协议的编译器的那部分,接着查看glibc的细节.

glibc

首先查看用户代码canary出错时的glibc错误输出代码:

eglibc-2.19/debug/stack_chk_fail.c
#line 24void__attribute__ ((noreturn))__stack_chk_fail (void){  __fortify_fail ("stack smashing detected");}

上面的代码非常简单就是输出出错信息.

接着我们将分析glibc对于整个canary机制的实现过程的代码.

首先给出跟canary相关的调用栈:

#0  security_init () at rtld.c:854// 相当于glibc/dynamic-linker的main#1  dl_main () at rtld.c:1818#2  _dl_sysdep_start () at ../elf/dl-sysdep.c:249#3  _dl_start_final () at rtld.c:331#4  _dl_start () at rtld.c:557// glibc/dynamic-linker入口#5  _start () from /lib/ld-linux.so.2// security_init初始化canary的值到%gs:0x14,所以这个函数是真正的关键所在.我们在// 下面的代码里会添加注释详细解释.static voidsecurity_init (void){  /* Set up the stack checker's canary.  */  // _dl_random的值其实在进入这个函数的时候就已经由kernel写入了.也就是说glibc直  // 接使用了_dl_random的值并没有给赋值,进入下面的函数会看到其实如果不是采用TLS  // 这种模式支持,glibc是可以自己产生随机数的.但是做为普遍情况来说,_dl_random就  // 是由kernel写入的.所以_dl_setup_stack_chk_guard()的行为就是将_dl_random的最  // 后一个字节设置为0x00.  uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);#ifdef THREAD_SET_STACK_GUARD  // TLS会进入这里.macro的定义及其展开详见下面.  THREAD_SET_STACK_GUARD (stack_chk_guard);  // 删掉部分代码  // ...  /* We do not need the _dl_random value anymore.  The less     information we leave behind, the better, so clear the     variable.  */  _dl_random = NULL;}// 这个宏只是一个过渡,header的定义见下面.///* Set the stack guard field in TCB head.  */#define THREAD_SET_STACK_GUARD(value)   THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)// header的定义.// TLS相关的数据结构,注意元素stack_guard的偏移是20也就是0x14.//typedef struct{  void *tcb;		/* Pointer to the TCB.  Not necessarily the			   thread descriptor used by libpthread.  */  dtv_t *dtv;  void *self;		/* Pointer to the thread descriptor.  */  int multiple_threads;  uintptr_t sysinfo;  uintptr_t stack_guard;  uintptr_t pointer_guard;  int gscope_flag;#ifndef __ASSUME_PRIVATE_FUTEX  int private_futex;#else  int __unused1;#endif  /* Reservation of some values for the TM ABI.  */  void *__private_tm[4];  /* GCC split stack support.  */  void *__private_ss;} tcbhead_t;// 这个是会将canary的值写入%gs:0x14.进行了删减之保留主要部分.///* Same as THREAD_SETMEM, but the member offset can be non-constant.  */# define THREAD_SETMEM(descr, member, value)   ({if (sizeof (descr->member) == 4)				                     asm volatile ("movl %0,%%gs:%P1" :				      		     : "ir" (value),					      		       "i" (offsetof (struct pthread, member)));	         })

由此就完成了glibc对canary的值的写入工作.

linux kernel

linux初始化gs,就是跟TLS相关的寄存器,TLS相关的部分i386比较复杂,由于跟canary没有太大关系,具体其他细节可以参考下面的源文件的注释部分描述.

linux-4.8/arch/x86/include/asm/stackprotector.h
#line 99static inline void load_stack_canary_segment(void)1{#ifdef CONFIG_X86_32	asm("mov %0, %%gs" : : "r" (__KERNEL_STACK_CANARY) : "memory");#endif}

到这里我们就基本上解释清楚了canary的这条代码线.

03 总结

通过本文可以比较清楚地知道canary的值的流向,kernel初始化了跟TLS相关的寄存器gs,并且提供了canary这个随机值,glibc写入%gs:0x14这个保存随机值的位置并且提供变量定义和打印函数定义,最上面是gcc插入对canary的值的引用和出错函数到用户代码里.

live long and prosper.

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Stack buffer overflow protection 學習筆記 – Stack canaries mechanism in User space – SZ Lin & Embed
__stack_chk_fail栈检查失败
PWN之Canary学习
Linux中的保护机制
GCC 中的编译器堆栈保护技术
栈溢出技巧(下)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服