逆向过elf程序都知道,GCC的canary,x86_64下从fs:0x28偏移处获取,32位下从gs:0x14偏移处获取。但知道canary如何产生,为什么在这里取的人比较少。

下面以x86_64平台为例,通过glibc源码分析一下。

看第一个问题:为什么从%fs:0x28处取。%fs寄存器被glibc定义为存放tls信息,查看tls结构:

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;
        int gscope_flag;
        uintptr_t sysinfo;
        uintptr_t stack_guard;   /* canary,0x28偏移 */
        uintptr_t pointer_guard;
        ……
} tcbhead_t;

可以看到%fs:0x28实际取的是当前线程控制块的stack_guard变量,这个变量在线程创建时已经固定。下面看第二个问题,stack_guard如何赋值的。

Linux加载器完成elf加载后,会将入口设置为_start,并在栈上为_start提供入参。_start的代码在sysdeps/x86_64/start.S文件中。

_start从栈上取参数,然后调用__libc_start_main()函数,这个函数也是在main()函数之前执行:

58 ENTRY (_start)
 59         /* Clearing frame pointer is insufficient, use CFI.  */
 60         cfi_undefined (rip)
 61         /* Clear the frame pointer.  The ABI suggests this be done, to mark
 62            the outermost frame obviously.  */
 63         xorl %ebp, %ebp
 64
 65         /* Extract the arguments as encoded on the stack and set up
 66            the arguments for __libc_start_main (int (*main) (int, char **, char **),
 67                    int argc, char *argv,
 68                    void (*init) (void), void (*fini) (void),
 69                    void (*rtld_fini) (void), void *stack_end).
 70            The arguments are passed via registers and on the stack:
 71         main:           %rdi
 72         argc:           %rsi
 73         argv:           %rdx
 74         init:           %rcx
 75         fini:           %r8
 76         rtld_fini:      %r9
 77         stack_end:      stack.  */

__libc_start_main()首先以_dl_random这个全局变量为入参,生成canary,然后通过THREAD_SET_STACK_GUARD宏将canary赋值给tls的stack_guard变量。

198   /* Set up the stack checker's canary.  */
199   uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
200 # ifdef THREAD_SET_STACK_GUARD
201   THREAD_SET_STACK_GUARD (stack_chk_guard);
202 # else
203   __stack_chk_guard = stack_chk_guard;
204 # endif

看下_dl_random哪里来的,在glibc源码中,有2处但实现大致相同:

126 /* Random data provided by the kernel.  */
127 void *_dl_random;
288       case AT_RANDOM:
289         _dl_random = (void *) av->a_un.a_val;

注意av这个变量,逆向跟踪发现其最终来自__libc_start_main()的argv参数。也就是_dl_random是由加载器提供的。而AT_RANDOM表示内核提供了接口,支持canary的随机数生成。可以使用下面命令查看:

kiiim@ubuntu :~/glibc-2.22$ LD_SHOW_AUXV=1 /bin/true grep AT_RANDOM
AT_RANDOM:       0x7fffdaf776e9

看下实际代码中,这个内核接口指的是什么,canary值又如何取。

rand_size = CONFIG_SECURITY_AUXV_RANDOM_SIZE * sizeof(unsigned long);
u_rand_bytes = NULL;
if (rand_size) {
        unsigned char k_rand_bytes[CONFIG_SECURITY_AUXV_RANDOM_SIZE * sizeof(unsigned long)];
        get_random_bytes(k_rand_bytes, rand_size);

u_rand_bytes = (elf_addr_t __user *)STACK_ALLOC(p, rand_size);
        if (__copy_to_user(u_rand_bytes, k_rand_bytes, rand_size))
                return -EFAULT;
}

发现在内核中通过get_random_bytes()接口产生,并copy_to_user()到用户空间。而内核中的安全随机数,也推荐使用get_random_bytes()生成。下面看下实现:

http://lxr.free-electrons.com/source/drivers/char/random.c

void get_random_bytes(void *buf, int nbytes)
{
#if DEBUG_RANDOM_BOOT > 0
        if (unlikely(nonblocking_pool.initialized == 0))
                printk(KERN_NOTICE "random: %pF get_random_bytes called "
                       "with %d bits of entropy available\n",
                       (void *) _RET_IP_,
                       nonblocking_pool.entropy_total);
#endif
        trace_get_random_bytes(nbytes, _RET_IP_);
        extract_entropy(&nonblocking_pool, buf, nbytes, 0, 0);
}
EXPORT_SYMBOL(get_random_bytes);

而看一下read /dev/urandom的内核实现:

static ssize_t
urandom_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
        int ret;

if (unlikely(nonblocking_pool.initialized == 0))
                printk_once(KERN_NOTICE "random: %s urandom read "
                            "with %d bits of entropy available\n",
                            current->comm, nonblocking_pool.entropy_total);

nbytes = min_t(size_t, nbytes, INT_MAX >> (ENTROPY_SHIFT + 3));
        ret = extract_entropy_user(&nonblocking_pool, buf, nbytes);

trace_urandom_read(8 * nbytes, ENTROPY_BITS(&nonblocking_pool),
                           ENTROPY_BITS(&input_pool));

return ret;
}

可以看到get_random_bytes()与read /dev/urandom实现是相同的,都是通过extract_entropy*从"entropy pool"中取的随机数。只不过一个在内核空间用,将结果返回到一块内核buffer,一个在用户空间使用,将结果返回到一块用户buffer。

下面再来看下,程序中如何使用这个canary。分析a()函数:

void a() {
        int a = 3;
        char str[16];
}

x86_64平台汇编如下:

(gdb) disass a
Dump of assembler code for function a:
   0x000000000040055d <+0>:     push   %rbp
   0x000000000040055e <+1>:     mov    %rsp,%rbp
   0x0000000000400561 <+4>:     sub    $0x30,%rsp
   0x0000000000400565 <+8>:     mov    %fs:0x28,%rax
   0x000000000040056e <+17>:    mov    %rax,-0x8(%rbp)
   0x0000000000400572 <+21>:    xor    %eax,%eax
   0x0000000000400574 <+23>:    movl   $0x3,-0x24(%rbp)   ;变量重排,a的地址低于str地址
   0x000000000040057b <+30>:    mov    -0x8(%rbp),%rax
   0x000000000040057f <+34>:    xor    %fs:0x28,%rax
   0x0000000000400588 <+43>:    je     0x40058f <a+50>
   0x000000000040058a <+45>:    callq  0x400440 <__stack_chk_fail@plt>
   0x000000000040058f <+50>:    leaveq
   0x0000000000400590 <+51>:    retq
End of assembler dump.

可以看到,GCC的栈保护还实现了变量重排。但与微软实现不同,GCC取出canary后并没有与ebp异或,直接放到栈上。也就是说,同一线程中,所有的canary值都是相同的,通过调试验证也中如此:

Breakpoint 1, 0x000000000040056e in a () at 1.c:4
4       void a() {
(gdb) p/x $rax
$1 = 0xc609d364696f6000
(gdb) c
Continuing.

Breakpoint 2, 0x00000000004005a2 in b () at 1.c:9
9       void b() {
(gdb) p/x $rax
$2 = 0xc609d364696f6000
(gdb) c
Continuing.

Breakpoint 1, 0x000000000040056e in a () at 1.c:4
4       void a() {
(gdb) p/x $rax
$3 = 0xc609d364696f6000
(gdb)

GCC栈溢出保护的更多相关文章

  1. gcc栈溢出保护机制:stack-protector

    关键词:stack-protector.stack-protector-strong.stack-protector-all等等. 1. gcc栈保护机制stack-protector简介 gcc提供 ...

  2. 逆向工程学习第四天--Windows栈溢出保护机制(GS)原理及绕过测试

    GS简介: Windows的缓冲区安全监测机制(GS)可以有效的阻止经典的BOF攻击,因为GS会在函数调用前往函数栈帧内压入一个随机数(canary),然后等函数返回前,会对canary进行核查,判断 ...

  3. GCC 中的编译器堆栈保护技术

    GCC 中的编译器堆栈保护技术 前几天看到的觉得不错得博客于是转发了,但这里我补充一下一些点. GCC通过栈保护选项-fstack-protector-all编译时额外添加两个符号,__stack_c ...

  4. [转] GCC 中的编译器堆栈保护技术

    以堆栈溢出为代表的缓冲区溢出已成为最为普遍的安全漏洞.由此引发的安全问题比比皆是.早在 1988 年,美国康奈尔大学的计算机科学系研究生莫里斯 (Morris) 利用 UNIX fingered 程序 ...

  5. linux程序的常用保护机制

    操作系统提供了许多安全机制来尝试降低或阻止缓冲区溢出攻击带来的安全风险,包括DEP.ASLR等.在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了DEP(Linux下对应NX).ASLR(Lin ...

  6. Linux下基本栈溢出攻击【转】

    转自:http://blog.csdn.net/wangxiaolong_china/article/details/6844415 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[ ...

  7. Linux中的保护机制

    Linux中的保护机制 在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了NX.PIE等机制,例如存在NX的话就不能直接执行栈上的数据,存在PIE 的话各个系统调用的地址就是随机化的. 一:ca ...

  8. Linux保护机制和绕过方式

    Linux保护机制和绕过方式 CANNARY(栈保护) ​ 栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行.用C ...

  9. C++栈溢出

    先看一段代码 #include<iostream> using namespace std; #define n 510 void sum(int a,int b) { cout<& ...

随机推荐

  1. Redis 系列之CentOS下Redis的安装

    前言 安装Redis需要知道自己需要哪个版本,有针对性的安装,比如如果需要redis GEO这个地理集合的特性,那么redis版本就不能低于3.2版本,由于这个特性是3.2版本才有的.另外需要注意的是 ...

  2. 【LeetCode】Valid Parentheses合法括号

    给定一个仅包含 '('.')'.'{'.'}'.'['.']'的字符串,确定输入的字符串是否合法. e.g. "()"."()[]{}"."[()]( ...

  3. [CodeForces - 614A] A - Link/Cut Tree

    A - Link/Cut Tree Programmer Rostislav got seriously interested in the Link/Cut Tree data structure, ...

  4. #pragma 处理警告 clang diagnostic 的使用

    首先#pragma在本质上是声明,常用的功能就是注释,尤其是给Code分段注释:而且它还有另一个强大的功能是处理编译器警告,但却没有上一个功能用的那么多. clang diagnostic 是#pra ...

  5. jsp 内置对象(五)

    1.Request对象 该对象封装了用户提交的信息,通过调用该对象相应的方法可以获取封装的信息,即使用该对象可以 获取用户提交的信息. 当Request对象获取客户提交的汉字字符时,会出现乱码问题,必 ...

  6. echo * 打印当前目录列表

    所以在脚本中 类似 echo $a*  如果$a为空  则会打印 目录列表.

  7. php 图片添加水印和二维码

    $host = $_SERVER['HTTP_HOST']; $save_code_file = './qrcodes/qrcode.png'; QrCode::format()->backgr ...

  8. u-boot的内存分布

    cpu会自动从NAND flash 中读取前4KB的数据放置在片内SRAM里(s3c2440是soc),同时把这段片内SRAM映射到nGCS0片选的空间(即0x00000000).cpu是从0x000 ...

  9. oo作业总结(一)

    概述 经历了三次oo作业的洗礼,让我对java语言的强大以及面向对象编程有了初步的理解(当然,我是小白).本文接下来就将对自己这三次作业的代码进行分析以及分享自己的心路历程. 基础知识点考核 针对前三 ...

  10. MySQL5.6数据库8小时内无请求自动断开连接

    问题: 最近的项目中,发现Mysql数据库在8个小时内,没有请求时,会自动断开连接,这是MySQL服务器的问题.The last packet successfully received from t ...