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 引用计数问题

  1. netlink_getsockbyfilp()调用sock_hold()增加sock引用计数
  2. netlink_attachskb()在返回1时会调用sock_put()减少引用计数
  3. 如果进入重试路径,sock指针未被置NULL,导致后续netlink_detachskb()再次减少引用计数

4.3 触发条件

要触发此漏洞,需要满足两个条件:

  1. 第一次调用netlink_attachskb()返回1(需要重试)
  2. 在重试期间,fget()返回NULL(文件描述符被关闭)

5. 漏洞利用

5.1 利用步骤概述

  1. 创建netlink socket
  2. 通过多线程操作触发竞态条件
  3. 在正确时机关闭文件描述符
  4. 利用释放后的内存实现控制流劫持

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 利用技术细节

  1. 内存布局控制:通过大量分配目标大小的对象来占据释放的内存
  2. 竞态条件精确触发:使用多线程精确控制时序
  3. SMEP绕过:使用ROP技术绕过SMEP保护
  4. 权限提升:通过修改cred结构体实现提权

6. 防御措施

6.1 官方修复

应用补丁,在重试路径中将sock指针置NULL:

if (ret == 1) {
    sock = NULL;
    goto retry;
}

6.2 通用防御

  1. 启用SMEP/SMAP
  2. 启用KASLR
  3. 使用更新的内核版本
  4. 限制用户权限

7. 总结

CVE-2017-11176是一个典型的Linux内核释放后重用漏洞,通过分析此漏洞可以学习到:

  1. Linux内核关键数据结构(task_struct, file, sock等)
  2. 内核对象生命周期管理(引用计数)
  3. 竞态条件漏洞的发现与利用
  4. 复杂内核漏洞的利用技术

这个漏洞展示了即使是一行代码的错误也可能导致严重的安全问题,强调了内核开发中资源管理的重要性。

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 漏洞补丁 补丁非常简单,只添加了一行代码: 2. 环境准备 2.1 目标环境配置 为了成功利用此漏洞,需要配置以下环境: 内核版本 :低于4.11.9(建议使用3.x版本) 架构 :amd64 (x86-64) 权限 :需要root权限进行调试 内存分配器 :SLAB分配器 安全特性 : SMEP启用 kASLR和SMAP禁用 内存 :至少512MB CPU :建议设置为1个CPU 2.2 检查系统配置 2.3 工具安装 需要安装SystemTap用于调试: 3. 核心概念 3.1 进程描述符(task_ struct) 每个任务都有一个 task_struct 对象存在于内存中,包含重要信息: 3.2 文件描述符与文件对象 Linux中"一切都是文件"的概念实现: 文件描述符 :对进程有意义的整数 文件对象(struct file) :表示已打开的文件 3.3 虚函数表(VFT) Linux内核使用面向对象的设计模式,通过虚函数表实现多态: 3.4 Socket相关结构体 struct socket :网络栈顶层结构 struct sock :下层和高级套接字间的中间层 struct sk_ buff (skb) :网络数据包表示 3.5 Netlink Socket Netlink socket(AF_ NETLINK)允许内核和用户空间通信: 3.6 引用计数 Linux内核使用引用计数管理对象生命周期: 4. 漏洞分析 4.1 mq_ notify系统调用 mq_notify() 用于POSIX消息队列的异步通知注册/撤销。漏洞代码路径如下: 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 关键利用代码 5.3 利用技术细节 内存布局控制 :通过大量分配目标大小的对象来占据释放的内存 竞态条件精确触发 :使用多线程精确控制时序 SMEP绕过 :使用ROP技术绕过SMEP保护 权限提升 :通过修改cred结构体实现提权 6. 防御措施 6.1 官方修复 应用补丁,在重试路径中将sock指针置NULL: 6.2 通用防御 启用SMEP/SMAP 启用KASLR 使用更新的内核版本 限制用户权限 7. 总结 CVE-2017-11176是一个典型的Linux内核释放后重用漏洞,通过分析此漏洞可以学习到: Linux内核关键数据结构(task_ struct, file, sock等) 内核对象生命周期管理(引用计数) 竞态条件漏洞的发现与利用 复杂内核漏洞的利用技术 这个漏洞展示了即使是一行代码的错误也可能导致严重的安全问题,强调了内核开发中资源管理的重要性。