22、Binder模糊测试:syzkaller与Binder fuzzing、binder_ioctl命令字fuzz、崩溃分析与复现
说到Binder安全,我个人的习惯是,光靠静态代码审查远远不够。你想想看,Binder驱动在内核里,每天要处理成千上万的IPC请求,光靠人眼去翻代码找漏洞,效率太低了。这时候,模糊测试(Fuzzing)就派上用场了。
说白了,模糊测试就是让机器替你“乱搞”Binder的接口,看看会不会搞出什么幺蛾子。今天我们就聊聊怎么用syzkaller这个神器,对Binder驱动做系统性的模糊测试。
22.1 syzkaller与Binder Fuzzing
syzkaller是Google开源的内核模糊测试工具。它厉害的地方在于,能自动生成系统调用序列,并且能感知代码覆盖率。我当年第一次用它测Binder时,心里也没底,结果跑了不到半小时,就崩了一次——嗯,那种感觉,又兴奋又紧张。
syzkaller对Binder的fuzzing,主要分两块:
- 系统调用级别的fuzzing:通过生成open、ioctl、mmap等系统调用的组合,来测试Binder驱动的边界情况。
- 命令字级别的fuzzing:专门针对binder_ioctl的各个命令字,构造畸形参数。
syzkaller的配置里,需要描述Binder设备节点的路径和权限。我记得第一次配的时候,忘了给/dev/binder设置正确的权限,结果fuzzer一直报权限错误,白白浪费了半天时间。
22.2 binder_ioctl命令字fuzz
Binder驱动的核心入口就是binder_ioctl。这个函数接收的命令字大概有十几个,每个命令字对应的数据结构都不一样。fuzz的时候,我们需要针对每个命令字,构造各种畸形数据。
常见的命令字包括:
| 命令字 | 功能 | 常见fuzz点 |
|---|---|---|
| BINDER_WRITE_READ | 读写Binder事务 | buffer大小、偏移量、句柄值 |
| BINDER_SET_CONTEXT_MGR | 设置上下文管理器 | 节点重复注册、权限绕过 |
| BINDER_THREAD_EXIT | 线程退出通知 | 空指针、重复退出 |
| BINDER_VERSION | 获取版本号 | 输出缓冲区溢出 |
我个人习惯,在fuzz BINDER_WRITE_READ时,会特别关注binder_transaction_data结构体中的偏移量字段。为什么?因为很多历史漏洞,都是因为偏移量计算错误导致的越界读写。
下面是一个简单的fuzz用例片段,用C语言模拟构造畸形命令字:
// 构造一个畸形的BINDER_WRITE_READ请求
struct binder_write_read bwr = {0};
bwr.write_size = 0xFFFFFFFF; // 故意填一个超大值
bwr.write_consumed = 0;
bwr.write_buffer = (unsigned long)malloc(0x1000);
// 发送ioctl
int fd = open("/dev/binder", O_RDWR);
int ret = ioctl(fd, BINDER_WRITE_READ, &bwr);
// 如果内核没有做好校验,这里就可能触发崩溃
22.3 崩溃分析与复现
syzkaller跑出崩溃后,会生成一个日志文件和一个C语言的reproducer程序。这个reproducer可以直接编译运行,复现崩溃。
分析崩溃时,我一般按这个步骤来:
- 看dmesg:先看内核日志,找到Oops信息,定位崩溃的指令地址和函数名。
- 看syzkaller日志:找到触发崩溃的系统调用序列,理解上下文。
- 编译reproducer:在本地环境编译运行,确认崩溃可复现。
- 反汇编分析:用objdump或GDB反汇编崩溃点的代码,找到根本原因。
举个例子,有一次syzkaller报了一个Binder的崩溃,日志里显示在binder_thread_read函数中发生了空指针解引用。我一看reproducer,发现它先调用了BINDER_THREAD_EXIT,然后又发了一个BINDER_WRITE_READ请求。问题就出在这里:线程退出后,binder_thread结构体被释放了,但后续的读操作还在引用它。
复现这个漏洞的代码大概长这样:
int fd = open("/dev/binder", O_RDWR);
// 先让线程退出
ioctl(fd, BINDER_THREAD_EXIT, NULL);
// 再尝试读写,此时binder_thread已经没了
struct binder_write_read bwr = {0};
ioctl(fd, BINDER_WRITE_READ, &bwr); // 这里会崩溃
这个漏洞的本质,就是缺少对线程生命周期的正确管理。修复方案也很直接:在binder_thread_read入口处,检查当前线程是否已经标记为退出状态。
22.4 知识体系总览
下面这张图,是我自己总结的Binder模糊测试知识体系。你可以把它当作一个检查清单,看看自己覆盖了哪些点:
这张图把Binder模糊测试分成了三条主线:syzkaller配置、命令字fuzz、崩溃分析。我个人建议,初学者先从命令字fuzz入手,因为这一块最容易上手,也最容易出成果。等你跑通了基本的fuzz流程,再深入去研究syzkaller的配置优化和覆盖率提升。
好了,关于Binder模糊测试的内容就聊到这里。记住,fuzzing不是万能的,但它绝对是你发现Binder漏洞的一把利器。用好它,能帮你省下大量手动审计的时间。