用 Nix 搭建 netconsd 的 Fuzz 测试工作流
原文由 Michael Lynch 于 发布,订阅该博客
最近,睡不着的时候我就会找些软件来做 Fuzz 测试。
本周早些时候,我想起了 Fady Othman 的文章“Meta Bug Bounty — Fuzzing ’netconsd’ for fun and profit.”这是一篇关于如何彻底进行 Fuzz 测试的优秀教程。
和大多数关于 Fuzz 测试的博客文章一样,我觉得这项工作不太容易复现,因为读者需要自行摸索如何复刻作者的环境和工具链。
我想试试用 Nix 做一个易于复现的 Othman Fuzzer 版本,就像我对 xpdf 做的那样。我那篇关于 xpdf 的文章要详尽、完善得多,这里只是给感兴趣的人留下的一些简短笔记。
fuzz-netconsd
这是我花了几个小时为 netconsd 搭建的 Fuzz 测试工作流:
如果你已经安装了 git 和 Nix,可以用以下命令运行我的工作流:
nix run gitlab:mtlynch/fuzz-netconsd
这是我写的第一个 Fuzzer,Fuzz 工具不是通过 Shell 去执行编译好的二进制文件,而是通过函数调用直接进入被测代码。在 honggfuzz 和 AFL++ 中,这被称为“持久模式(persistent mode)”,我想是因为 Fuzzer 可以让一个进程持续运行,而不必为每次 Fuzz 都新起一个进程。
通过函数调用来做 Fuzz 很有意思,因为即使目标代码根本没有发布共享库,你也能这么做。我在工作流的 installPhase 阶段就是这么做的,把包含待调用代码的 .o 文件和对应的 .h 头文件复制到了该步骤的 Nix 输出目录中。
拿到所需的头文件和目标文件后,我写了一个简单的测试桩,只包含足以调用被测函数 ncrx_process 的代码:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "ncrx.h"
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
// Convert the random bytes of the payload to a null-terminated string.
char *payload = (char *)malloc(len + 1);
if (!payload) return 0;
memcpy(payload, buf, len);
payload[len] = '\0';
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
perror("clock_gettime");
return 1;
}
uint64_t now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
struct ncrx *ncrx = ncrx_create(NULL);
// Call the function that we're fuzzing.
ncrx_process(payload, now, 0, ncrx);
// Clean up the resources we allocated.
ncrx_destroy(ncrx);
free(payload);
return 0;
}
我发现持久模式的 Fuzz 比我预想的要简单。我甚至不需要对 netconsd 有太多了解。我到现在仍不太清楚它具体是做什么的,但已经足够理解到能在 Fuzzer 中调用它的某个解析函数。
大概也不会有什么发现
Othman 报告说他的 Fuzz 测试达到了 100% 的覆盖率,做得非常彻底。
我查看了netconsd 的提交历史,自 Othman 完成那项工作以来,几乎没有任何更新。他在文章中没有明确说明,但我猜测修复他所发现漏洞的就是这次改动:
我怀疑到这个阶段再做 Fuzz 也很难有什么收获。我让 Fuzzer 跑了 12 个小时都没有触发崩溃,就停掉了。
未完成的工作
- Othman 使用 klee 工具为 Fuzz 语料库生成边界用例输入。我没有实现这部分,但它很适合放进这个工作流,因为 klee 已经在 Nix 上打包好了。
- 我用的是 honggfuzz,而不是 Othman 使用的 AFL++。我更喜欢 honggfuzz 的简洁,但 Othman 对 AFL++ 的各种参数足够了解,能获得更高的覆盖率。
- 我的初版实现用的是 AFL++,如果你更想尝试那个版本的话。
随机一篇博客
评论
登录后参与讨论