解读Linux安全机制之栈溢出保护

开发 开发工具
今天我们讲一讲有关Linux安全机制之栈溢出保护的内容。

[[181356]]

一、概述

栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary(以下统一使用canary)。

gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all编译参数以支持栈保护功能,4.9新增了-fstack-protector-strong编译参数让保护的范围更广。以下是-fstack-protector和-fstack-protector-strong的区别:

-fstack-protector和-fstack-protector-strong的区别

Linux系统中存在着三种类型的栈:

  • 应用程序栈:工作在Ring3,由应用程序来维护;
  • 内核进程上下文栈:工作在Ring0,由内核在创建线程的时候创建;
  • 内核中断上下文栈:工作在Ring0,在内核初始化的时候给每个CPU核心创建一个。

二、 应用程序栈保护

1. 栈保护工作原理

下面是一个包含栈溢出的例子:

  1. /* test.c */ 
  2.  
  3. #include <stdio.h> 
  4.  
  5. #include <string.h> 
  6.  
  7. int main(int argc, char **argv) 
  8.  
  9.  
  10.     char buf[16]; 
  11.  
  12.     scanf("%s", buf); 
  13.  
  14.     printf("%s\n", buf); 
  15.  
  16.     return 0; 
  17.  

我们先禁用栈保护功能看看执行的结果

  1. [root@localhost stackp]# gcc -o test test.c -fno-stack-protector 
  2.  
  3. [root@localhost stackp]# python -c "print 'A'*24" | ./test 
  4.  
  5. AAAAAAAAAAAAAAAAAAAAAAAA 
  6.  
  7. Segmentation fault   <- RIP腐败,导致异常 

当返回地址被覆盖后产生了一个段错误,因为现在的返回地址已经无效了,所以现在执行的是CPU的异常处理流程。我们打开栈保护后再看看结果:

  1. [root@localhost stackp]# gcc -o test test.c -fstack-protector 
  2.  
  3. [root@localhost stackp]# python -c "print 'A'*25" | ./test 
  4.  
  5. AAAAAAAAAAAAAAAAAAAAAAAAA 
  6.  
  7. *** stack smashing detected ***: ./test terminated  

这时触发的就不是段错误了,而是栈保护的处理流程,我们反汇编看看做了哪些事情:

  1. 0000000000400610 <main>
  2.  
  3.   400610:       55                      push   %rbp 
  4.  
  5.   400611:       48 89 e5                mov    %rsp,%rbp 
  6.  
  7.   400614:       48 83 ec 30             sub    $0x30,%rsp 
  8.  
  9.   400618:       89 7d dc                mov    %edi,-0x24(%rbp) 
  10.  
  11.   40061b:       48 89 75 d0             mov    %rsi,-0x30(%rbp) 
  12.  
  13.   40061f:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax  <- 插入canary值 
  14.  
  15.   400626:       00 00 
  16.  
  17.   400628:       48 89 45 f8             mov    %rax,-0x8(%rbp) 
  18.  
  19.   40062c:       31 c0                   xor    %eax,%eax 
  20.  
  21.   40062e:       48 8d 45 e0             lea    -0x20(%rbp),%rax 
  22.  
  23.   400632:       48 89 c6                mov    %rax,%rsi 
  24.  
  25.   400635:       bf 00 07 40 00          mov    $0x400700,%edi 
  26.  
  27.   40063a:       b8 00 00 00 00          mov    $0x0,%eax 
  28.  
  29.   40063f:       e8 cc fe ff ff          callq  400510 <__isoc99_scanf@plt> 
  30.  
  31.   400644:       48 8d 45 e0             lea    -0x20(%rbp),%rax 
  32.  
  33.   400648:       48 89 c7                mov    %rax,%rdi 
  34.  
  35.   40064b:       e8 80 fe ff ff          callq  4004d0 <puts@plt> 
  36.  
  37.   400650:       b8 00 00 00 00          mov    $0x0,%eax 
  38.  
  39.   400655:       48 8b 55 f8             mov    -0x8(%rbp),%rdx  <- 检查canary值 
  40.  
  41.   400659:       64 48 33 14 25 28 00    xor    %fs:0x28,%rdx 
  42.  
  43.   400660:       00 00 
  44.  
  45.   400662:       74 05                   je     400669 <main+0x59> # 0x400669 
  46.  
  47.   400664:       e8 77 fe ff ff          callq  4004e0 <__stack_chk_fail@plt> 
  48.  
  49.   400669:       c9                      leaveq 
  50.  
  51.   40066a:       c3                      retq 

 

我们看到函数开头(地址:0x40061f)处gcc编译时在栈帧的返回地址和临时变量之间插入了一个canary值,该值是从%fs:0x28里取的,栈帧的布局如下:

  1. stack: 
  2.  
  3. | ......          | 
  4.  
  5. | orig_return     | 
  6.  
  7. | orig_rbp        |  <- %rbp 
  8.  
  9. | canary          |  <- -0x8(%rpb), 既 %fs:0x28 
  10.  
  11. | local variables | 
  12.  
  13. |                 |  <- %rsp 

在函数即将返回时(地址:0x400655)检查栈中的值是否和原来的相等,如果不相等就调用glibc的_stackchk_fail函数,并终止进程。

2. canary值的产生

这里以x64平台为例,canary是从%fs:0x28偏移位置获取的,%fs寄存器被glibc定义为存放tls信息的,我们需要查看glibc的源代码:

  1. typedef struct 
  2.  
  3.  
  4.   void *tcb;        /* Pointer to the TCB.  Not necessarily the 
  5.  
  6.                thread descriptor used by libpthread.  */ 
  7.  
  8.   dtv_t *dtv; 
  9.  
  10.   void *self;       /* Pointer to the thread descriptor.  */ 
  11.  
  12.   int multiple_threads; 
  13.  
  14.   int gscope_flag; 
  15.  
  16.   uintptr_t sysinfo; 
  17.  
  18.   uintptr_t stack_guard;   <- canary值,偏移位置0x28处 
  19.  
  20.   uintptr_t pointer_guard; 
  21.  
  22.   ...... 
  23.  
  24. } tcbhead_t; 

结构体tcbheadt就是用来描述tls的也就是%fs寄存器指向的位置,其中+0x28偏移位置的成员变量stackguard就是canary值。另外通过strace ./test看到在进程加载的过程中会调用arch_prctl系统调用来设置%fs的值,

  1. root@localhost stackp]# strace ./test 
  2.  
  3. execve("./test", ["./test"], [/* 24 vars */]) = 0 
  4.  
  5. ...... 
  6.  
  7. arch_prctl(ARCH_SET_FS, 0x7f985a041740) = 0 
  8.  
  9. ...... 

产生canary值的代码在glibc的dlmain和_libcstart_main函数中:

  1.   /* Set up the stack checker's canary.  */ 
  2.  
  3.   uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random); 
  4.  
  5. # ifdef THREAD_SET_STACK_GUARD 
  6.  
  7.   THREAD_SET_STACK_GUARD (stack_chk_guard); 
  8.  
  9. # else 
  10.  
  11.   __stack_chk_guard = stack_chk_guard; 
  12.  
  13. # endif 

dlrandom是一个随机数,它由dlsysdepstart函数从内核获取的。dlsetupstackchkguard函数负责生成canary值,THREADSETSTACK_GUARD宏将canary设置到%fs:0x28位置。

在应用程序栈保护中,进程的%fs寄存器是由glibc来管理的,并不涉及到内核提供的功能。

3. x32应用程序栈保护

解读完了x64的实现,我们来看看x32下面的情况,我们还是使用上面例子的代码在x32的机器上编译,得到下面的代码:

  1. 08048464 <main>
  2.  
  3.  ...... 
  4.  
  5.  8048474:    65 a1 14 00 00 00      mov   %gs:0x14,%eax  # 插入canary值 
  6.  
  7.  804847a:    89 44 24 3c            mov   %eax,0x3c(%esp) 
  8.  
  9.  ...... 
  10.  
  11.  80484aa:    65 33 15 14 00 00 00   xor   %gs:0x14,%edx  # 检查canary值 
  12.  
  13.  80484b1:    74 05                  je    80484b8 <main+0x54> # 0x80484b8 
  14.  
  15.  80484b3:    e8 c0 fe ff ff         call  8048378 <__stack_chk_fail@plt> 
  16.  
  17.  80484b8:    c9                     leave 
  18.  
  19.  80484b9:    c3                     ret 

 

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2009-07-24 17:35:24

2017-06-11 14:37:00

2010-04-17 19:20:26

2024-02-27 17:30:11

2009-07-03 18:59:02

2020-05-26 18:50:46

JVMAttachJava

2009-10-21 14:36:53

漏洞补丁

2019-01-11 09:00:00

2013-07-15 10:15:28

2021-09-01 13:51:06

网络安全法基础设施安全网络安全

2011-04-06 16:51:59

Windows Ser数据安全

2019-01-28 18:05:04

2019-02-13 13:31:42

2012-11-30 11:08:57

2022-02-22 09:33:38

LIFO数据结构

2022-05-19 07:09:29

机制沙箱安全JVM

2022-01-03 00:15:06

安全网络物联网

2023-03-20 10:09:13

2017-03-10 09:28:58

2021-12-21 06:07:10

网络安全网络攻击网络威胁
点赞
收藏

51CTO技术栈公众号