• GCC栈溢出保护


    逆向过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 ",
                           (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 ",
                                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)

  • 相关阅读:
    【Python】多态、协议和鸭子类型
    【Python】魔法方法之__call__,将对象当方法使用
    【Python】策略模式
    【Python】if 后怎么就可以跟对象?变量交换写法是语法糖吗?
    【Python 库】NumPy 超详细教程(3):ndarray 的内部机理及高级迭代
    【Python 库】NumPy 超详细教程(2):数据类型
    【Python 库】NumPy 超详细教程(1):NumPy 数组
    【杂谈】10 年三线小城 IT 开发的感悟
    【PostgreSQL】安装及中文显示
    【Python 库】轻量级 ORM 框架 peewee 用法详解之——增删改查
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9864080.html
Copyright © 2020-2023  润新知