内核漏洞挖掘技术系列(6)——使用AFL进行内核漏洞挖掘(2)
字数 1317 2025-08-03 16:44:51
使用AFL进行Linux内核漏洞挖掘技术详解
1. 概述
本教程详细介绍了如何使用AFL (American Fuzzy Lop) fuzzer对Linux内核进行漏洞挖掘,特别是针对netlink子系统。这是一种相对轻量级的内核fuzz方法,相比复杂的工具如syzkaller更易于理解和上手。
2. 技术背景
2.1 基于代码覆盖率的fuzz原理
基于代码覆盖率的fuzz工作流程如下:
- 测试用例选择:fuzzer选择最有希望的测试用例
- 变异生成:fuzzer将测试用例变异为大量新的测试用例
- 执行监控:目标代码运行变异的测试用例,并报告代码覆盖率
- 反馈优化:fuzzer根据覆盖范围计算得分,确定测试用例优先级并删除冗余
2.2 相关工具比较
- syzkaller:功能强大的持续集成内核fuzzer,已发现数百个问题
- Trinity:专门的内核fuzzer
- AFL:以易用性、速度和优秀的变异逻辑著称,适合初学者
3. 准备工作
3.1 内核配置
首先需要配置内核以支持KCOV (代码覆盖率)和KASAN (内存错误检测):
cd linux
./scripts/config \
-e KCOV \
-d KCOV_INSTRUMENT_ALL \
-e KASAN
3.2 选择性启用KCOV
在net目录下启用KCOV:
find net -name Makefile | xargs -L1 -I {} bash -c 'echo "KCOV_INSTRUMENT := y" >> {}'
3.3 虚拟化支持配置
为KVM运行配置必要的选项:
./scripts/config \
-e VIRTIO -e VIRTIO_PCI -e NET_9P -e NET_9P_VIRTIO -e 9P_FS \
-e VIRTIO_NET -e VIRTIO_CONSOLE -e DEVTMPFS
4. KCOV使用详解
4.1 基本操作
KCOV使用简单,通过ioctl控制:
ioctl(kcov_fd, KCOV_ENABLE, KCOV_TRACE_PC); // 启用
/* 被分析的代码 */
ioctl(kcov_fd, KCOV_DISABLE, 0); // 禁用
4.2 读取覆盖率数据
n = __atomic_load_n(&kcov_ring[0], __ATOMIC_RELAXED);
for (i = 0; i < n; i++) {
printf("0x%lx\n", kcov_ring[i + 1]);
}
5. AFL与KCOV集成
5.1 AFL工作原理
AFL使用64KB的共享内存数组(trace_bits)记录分支对(branch_src, branch_dst)的命中计数:
cur_location = <COMPILE_TIME_RANDOM>;
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;
5.2 KCOV数据转换
将KCOV的%rip值转换为AFL格式:
n = __atomic_load_n(&kcov_ring[0], __ATOMIC_RELAXED);
uint16_t prev_location = 0;
for (i = 0; i < n; i++) {
uint16_t cur_location = hash_function(kcov_ring[i + 1]);
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;
}
6. Netlink Fuzzing实现
6.1 读取AFL输入
char buf[512 * 1024];
int buf_len = read(0, buf, sizeof(buf));
6.2 Netlink操作
使用输入数据的前5字节作为netlink协议和组ID:
netlink_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, buf[0]);
struct sockaddr_nl sa = {
.nl_family = AF_NETLINK,
.nl_groups = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4],
};
bind(netlink_fd, (struct sockaddr*)&sa, sizeof(sa));
struct iovec iov = { &buf[5], buf_len - 5 };
struct sockaddr_nl sax = { .nl_family = AF_NETLINK };
struct msghdr msg = { &sax, sizeof(sax), &iov, 1, NULL, 0, 0 };
r = sendmsg(netlink_fd, &msg, 0);
6.3 主循环结构
forksrv_welcome();
while (1) {
forksrv_cycle();
test_data = afl_read_input();
kcov_enable();
/* netlink操作 */
kcov_disable();
/* 填充shared_map */
if (new_crash_in_dmesg) {
forksrv_status(1);
} else {
forksrv_status(0);
}
}
7. 运行环境设置
7.1 运行选项比较
| 选项 | 优点 | 缺点 |
|---|---|---|
| Native | 速度最快 | 崩溃会丢失数据 |
| UML | 无需特权 | 不支持KASAN |
| KVM | 稳定隔离 | 需要虚拟化支持 |
7.2 使用virtme运行
virtme-run \
--kimg bzImage \
--rw --pwd --memory 512M \
--script-sh "./afl-fuzz -i inp -o out -- fuzznetlink"
8. 输入语料库
即使没有专门准备的语料库,也可以从简单输入开始:
mkdir inp
echo "hello world" > inp/01.txt
9. 优化方向
- AFL共享内存设置:优化shared_memory配置
- 持久模式:实现AFL持久模式提高效率
- 命名空间隔离:使用网络命名空间隔离netlink命令
- 崩溃检测:改进/dev/kmsg读取以检测内核崩溃
- 稳定性提升:在KVM外运行AFL提高稳定性
10. 总结
本方法不仅适用于netlink子系统,还可扩展到其他Linux内核子系统,如文件系统或BPF验证器。关键点包括:
- 正确配置内核支持KCOV和KASAN
- 实现KCOV到AFL的代码覆盖率数据转换
- 设计简单的netlink接口fuzzer
- 使用KVM提供隔离的运行环境
虽然这是一个基础实现,但已经能够发现一些内核问题。进一步的优化可以显著提高fuzzing效率和发现漏洞的能力。