canary是如何生成并且写入TLS中0x28的?

canary是rbp之前8byte大小的随机区域(最低位为0),从fs:0x28读取并且在函数返回前校验。

简而言之,在ELF加载到内存后,执行_start函数时,最终会调用security_init()函数来向tcbhead_t.stack_guard中写入canary的值。其中canary的值是内核已经生成好保存在_dl_random中的。glibc仅仅是将其最低位置为0后就存入tcbhead_t.stack_guard中。具体细节如下:

canary的结构

canary本质上是tcbhead_t结构体中的stack_guard。

sysdeps/x86_64/nptl/tls.h line 53 中 tcbhead_t结构体定义如下:

typedef struct                                                // glibc canary相关结构
{
  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;                                        // fs:0x28 canary的值
  uintptr_t pointer_guard;
  unsigned long int vgetcpu_cache[2];
# ifndef __ASSUME_PRIVATE_FUTEX
  int private_futex;
# else
  int __glibc_reserved1;
# endif
  int __glibc_unused1;
  /* Reservation of some values for the TM ABI.  */
  void *__private_tm[4];
  /* GCC split stack support.  */
  void *__private_ss;
  long int __glibc_reserved2;
  /* Must be kept even if it is no longer used by glibc since programs,
     like AddressSanitizer, depend on the size of tcbhead_t.  */
  __128bits __glibc_unused2[8][4] __attribute__ ((aligned (32)));

  void *__padding[8];
} tcbhead_t;

写入过程

调用链:_start->_dl_start->_dl_start_final->_dl_sysdep_start->security_init

pwndbg> bt
#0  security_init () at rtld.c:721
#1  dl_main (phdr=<optimized out>, phnum=<optimized out>, user_entry=<optimized out>, auxv=<optimized out>) at rtld.c:1727
#2  0x00007ffff7df0632 in _dl_sysdep_start (start_argptr=start_argptr@entry=0x7fffffffe650, dl_main=dl_main@entry=0x7ffff7dd91e0 <dl_main>) at ../elf/dl-sysdep.c:249
#3  0x00007ffff7dd8c2a in _dl_start_final (arg=0x7fffffffe650) at rtld.c:323
#4  _dl_start (arg=0x7fffffffe650) at rtld.c:429
#5  0x00007ffff7dd7c38 in _start () from /lib64/ld-linux-x86-64.so.2
#6  0x0000000000000001 in ?? ()
#7  0x00007fffffffe87e in ?? ()
#8  0x0000000000000000 in ?? ()

elf/rtld.c 中 security_init中获取kernel设置好的_dl_random,清零最后一位,调用THREAD_SET_STACK_GUARD存储。

static void
security_init (void)
{
  /* Set up the stack checker's canary.  */
  uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random); //清零最后一位
#ifdef THREAD_SET_STACK_GUARD
  THREAD_SET_STACK_GUARD (stack_chk_guard);     // set canary
#else
  __stack_chk_guard = stack_chk_guard;
#endif

  /* Set up the pointer guard as well, if necessary.  */
  uintptr_t pointer_chk_guard
    = _dl_setup_pointer_guard (_dl_random, stack_chk_guard);
#ifdef THREAD_SET_POINTER_GUARD
  THREAD_SET_POINTER_GUARD (pointer_chk_guard);
#endif
  __pointer_chk_guard_local = pointer_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;
}

fbtl/sysdeps/x86_64/tls.h line 351 (THREAD_SET_STACK_GUARD宏的定义)

/* Set the stack guard field in TCB head.  */
# define THREAD_SET_STACK_GUARD(value) \
    THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)
# define THREAD_COPY_STACK_GUARD(descr) \
    ((descr)->header.stack_guard					      \
     = THREAD_GETMEM (THREAD_SELF, header.stack_guard))

fbtl/sysdeps/x86_64/tls.h line 253 : (THREAD_SETMEM的定义)

/* Same as THREAD_SETMEM, but the member offset can be non-constant.  */
# define THREAD_SETMEM(descr, member, value) \
  ({ if (sizeof (descr->member) == 1)					      \
       asm volatile ("movb %b0,%%fs:%P1" :				      \
		     : "iq" (value),					      \
		       "i" (offsetof (struct pthread, member)));	      \
     else if (sizeof (descr->member) == 4)				      \
       asm volatile ("movl %0,%%fs:%P1" :				      \
		     : IMM_MODE (value),				      \
		       "i" (offsetof (struct pthread, member)));	      \
     else								      \
       {								      \
	 if (sizeof (descr->member) != 8)				      \
	   /* There should not be any value with a size other than 1,	      \
	      4 or 8.  */						      \
	   abort ();							      \
									      \
	 asm volatile ("movq %q0,%%fs:%P1" :				      \
		       : IMM_MODE ((uint64_t) cast_to_integer (value)),	      \
			 "i" (offsetof (struct pthread, member)));	      \
       }})

THREAD_SETMEM宏的定义很复杂,具体调试起来就是一个写操作。源码调试时设置源码搜索路径:

directory /root/Pwn/glibc/glibc-2.23/elf/
directory /root/Pwn/glibc/glibc-2.23/fbtl/
break security_init

set canary时的汇编如下:

image-20190217210028804

todo问题

一个进程的中的canary值是一样的吗?
_dl_random的值是每次读区都随机吗?
为什么进程重启,canary的值就改变了?
tcb是什么?如果有多个thread,每个thread里的canary值还一样吗?

参考

  1. canary分析 https://hardenedlinux.github.io/2016/11/27/canary.html
  2. ctf wiki https://ctf-wiki.github.io/ctf-wiki/pwn/linux/mitigation/canary/
  3. sakura sixstar2018 babystack writeup http://eternalsakura13.com/2018/04/24/starctf_babystack/
  4. https://github.com/balsn/ctf_writeup/tree/master/20180421-*ctf#babystack-sces60107
  5. 栈溢出中关于canary的总结 https://zhakul.top/archives/216