CVE-2017-11176: 一步一步linux内核漏洞利用 (一)(原理部分)
字数 1711 2025-08-05 08:18:25
Linux内核漏洞利用教程:CVE-2017-11176分析与利用
1. 漏洞概述
CVE-2017-11176是Linux内核中的一个双重释放漏洞,存在于mq_notify()系统调用中。该漏洞允许攻击者通过竞态条件导致内核释放后重用(UAF),可能实现权限提升或导致系统崩溃。
1.1 漏洞基本信息
- CVE编号:CVE-2017-11176
- 漏洞名称:mq_notify: double sock_put()
- 影响版本:Linux内核v2.6.32.x至4.11.9
- 漏洞类型:释放后重用(Use-After-Free)
- 危险等级:高危,可能导致权限提升
1.2 漏洞补丁
补丁非常简单,只添加了一行代码:
diff --git a/ipc/mqueue.c b/ipc/mqueue.c
index c9ff943..eb1391b 100644
--- a/ipc/mqueue.c
+++ b/ipc/mqueue.c
@@ -1270,8 +1270,10 @@ retry:
timeo = MAX_SCHEDULE_TIMEOUT;
ret = netlink_attachskb(sock, nc, &timeo, NULL);
- if (ret == 1)
+ if (ret == 1) {
+ sock = NULL;
goto retry;
+ }
if (ret) {
sock = NULL;
nc = NULL;
2. 环境准备
2.1 目标环境配置
为了成功利用此漏洞,需要配置以下环境:
- 内核版本:低于4.11.9(建议使用3.x版本)
- 架构:amd64 (x86-64)
- 权限:需要root权限进行调试
- 内存分配器:SLAB分配器
- 安全特性:
- SMEP启用
- kASLR和SMAP禁用
- 内存:至少512MB
- CPU:建议设置为1个CPU
2.2 检查系统配置
# 检查SMEP状态
grep "smep" /proc/cpuinfo
# 检查SLAB分配器
grep "CONFIG_SL.B=" /boot/config-$(uname -r)
# 禁用kASLR
# 在/etc/default/grub中添加nokaslr
GRUB_CMDLINE_LINUX_DEFAULT="quiet nokaslr nosmap"
2.3 工具安装
需要安装SystemTap用于调试:
# 下载特定内核版本的.deb包
wget https://snapshot.debian.org/archive/debian-security/20160904T172241Z/pool/updates/main/l/linux/linux-image-3.16.0-4-amd64_3.16.36-1%2Bdeb8u1_amd64.deb
wget https://snapshot.debian.org/archive/debian-security/20160904T172241Z/pool/updates/main/l/linux/linux-image-3.16.0-4-amd64-dbg_3.16.36-1%2Bdeb8u1_amd64.deb
wget https://snapshot.debian.org/archive/debian-security/20160904T172241Z/pool/updates/main/l/linux/linux-headers-3.16.0-4-amd64_3.16.36-1%2Bdeb8u1_amd64.deb
# 安装
dpkg -i linux-image-3.16.0-4-amd64_3.16.36-1+deb8u1_amd64.deb
dpkg -i linux-image-3.16.0-4-amd64-dbg_3.16.36-1+deb8u1_amd64.deb
dpkg -i linux-headers-3.16.0-4-amd64_3.16.36-1+deb8u1_amd64.deb
# 安装SystemTap
apt install systemtap
3. 核心概念
3.1 进程描述符(task_struct)
每个任务都有一个task_struct对象存在于内存中,包含重要信息:
struct task_struct {
volatile long state; // 进程状态
void *stack; // 任务栈指针
int prio; // 进程优先级
struct mm_struct *mm; // 内存地址空间
struct files_struct *files; // 打开文件信息
const struct cred *cred; // 凭证信息
// ...
};
3.2 文件描述符与文件对象
Linux中"一切都是文件"的概念实现:
- 文件描述符:对进程有意义的整数
- 文件对象(struct file):表示已打开的文件
struct file {
loff_t f_pos; // 文件读取位置
atomic_long_t f_count; // 引用计数器
const struct file_operations *f_op; // 虚函数表
void *private_data; // 专用数据
// ...
};
3.3 虚函数表(VFT)
Linux内核使用面向对象的设计模式,通过虚函数表实现多态:
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
// ...
};
3.4 Socket相关结构体
- struct socket:网络栈顶层结构
- struct sock:下层和高级套接字间的中间层
- struct sk_buff (skb):网络数据包表示
struct socket {
struct file *file;
struct sock *sk;
const struct proto_ops *ops;
// ...
};
struct sock {
int sk_rcvbuf; // 接收缓冲区最大大小
int sk_sndbuf; // 发送缓冲区最大大小
atomic_t sk_rmem_alloc; // 接收缓冲区当前大小
atomic_t sk_wmem_alloc; // 发送缓冲区当前大小
struct sk_buff_head sk_receive_queue; // 接收队列
struct sk_buff_head sk_write_queue; // 发送队列
struct socket *sk_socket;
// ...
};
3.5 Netlink Socket
Netlink socket(AF_NETLINK)允许内核和用户空间通信:
struct netlink_sock {
/* struct sock has to be the first member */
struct sock sk;
u32 pid;
u32 dst_pid;
u32 dst_group;
// ...
};
3.6 引用计数
Linux内核使用引用计数管理对象生命周期:
// 增加引用计数
static inline void sock_hold(struct sock *sk) {
atomic_inc(&sk->sk_refcnt);
}
// 减少引用计数
static inline void sock_put(struct sock *sk) {
if (atomic_dec_and_test(&sk->sk_refcnt))
sk_free(sk);
}
4. 漏洞分析
4.1 mq_notify系统调用
mq_notify()用于POSIX消息队列的异步通知注册/撤销。漏洞代码路径如下:
SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, const struct sigevent __user *, u_notification)
{
struct sock *sock = NULL;
retry:
filp = fget(notification.sigev_signo); // [1] 获取文件对象
if (!filp) {
ret = -EBADF;
goto out;
}
sock = netlink_getsockbyfilp(filp); // [2] 获取sock对象(增加引用计数)
fput(filp); // [3] 释放文件对象引用
ret = netlink_attachskb(sock, nc, &timeo, NULL); // [4] 尝试附加skb
if (ret == 1) // [5] 需要重试
goto retry; // 漏洞点:未将sock置NULL
if (ret) {
sock = NULL;
goto out;
}
out:
if (sock) {
netlink_detachskb(sock, nc); // [6] 释放sock引用
}
// ...
}
4.2 引用计数问题
netlink_getsockbyfilp()调用sock_hold()增加sock引用计数netlink_attachskb()在返回1时会调用sock_put()减少引用计数- 如果进入重试路径,sock指针未被置NULL,导致后续
netlink_detachskb()再次减少引用计数
4.3 触发条件
要触发此漏洞,需要满足两个条件:
- 第一次调用
netlink_attachskb()返回1(需要重试) - 在重试期间,
fget()返回NULL(文件描述符被关闭)
5. 漏洞利用
5.1 利用步骤概述
- 创建netlink socket
- 通过多线程操作触发竞态条件
- 在正确时机关闭文件描述符
- 利用释放后的内存实现控制流劫持
5.2 关键利用代码
// 创建netlink socket
int sock_fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_USERSOCK);
// 设置mq_notify参数
struct sigevent sev = {
.sigev_notify = SIGEV_THREAD,
.sigev_signo = sock_fd,
.sigev_value.sival_ptr = NULL,
.sigev_notify_function = NULL,
.sigev_notify_attributes = NULL,
};
// 触发漏洞
mq_notify(mqdes, &sev);
// 在另一个线程中关闭文件描述符
close(sock_fd);
5.3 利用技术细节
- 内存布局控制:通过大量分配目标大小的对象来占据释放的内存
- 竞态条件精确触发:使用多线程精确控制时序
- SMEP绕过:使用ROP技术绕过SMEP保护
- 权限提升:通过修改cred结构体实现提权
6. 防御措施
6.1 官方修复
应用补丁,在重试路径中将sock指针置NULL:
if (ret == 1) {
sock = NULL;
goto retry;
}
6.2 通用防御
- 启用SMEP/SMAP
- 启用KASLR
- 使用更新的内核版本
- 限制用户权限
7. 总结
CVE-2017-11176是一个典型的Linux内核释放后重用漏洞,通过分析此漏洞可以学习到:
- Linux内核关键数据结构(task_struct, file, sock等)
- 内核对象生命周期管理(引用计数)
- 竞态条件漏洞的发现与利用
- 复杂内核漏洞的利用技术
这个漏洞展示了即使是一行代码的错误也可能导致严重的安全问题,强调了内核开发中资源管理的重要性。