googleprojectzero/Jackalope

GitHub: googleprojectzero/Jackalope

Google Project Zero 出品的跨平台覆盖引导黑盒 Fuzzer,支持在无源码情况下对闭源二进制程序进行安全测试和漏洞挖掘。

Stars: 1293 | Forks: 145

# Jackalope ``` Copyright 2020 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ## 什么是 Jackalope Jackalope 是一个可定制、分布式、覆盖引导(coverage-guided)的 fuzzer,能够处理黑盒二进制文件。 ### 为什么需要另一个 fuzzer? 虽然有许多优秀的覆盖引导 fuzzers 可以在有源代码的目标上工作,但在黑盒二进制文件上工作的相对较少,特别是在 Windows 和 macOS 操作系统上,而且现有的那些主要基于不易定制的代码库。Jackalope 的初始目标是: - 易于针对通用 fuzzers 可能效果不佳的目标进行定制。这可能包括 - 自定义 mutators - 自定义样本投递机制 - 自定义 instrumentation 等 - 易于并行化,无论是在单机还是跨多机 ### 它能做什么? Jackalope 可以独立使用,但作为库使用时更加强大,用户可以插入自定义组件来替代默认行为。默认情况下,Jackalope 包含: - 使用 TinyInst 的二进制 instrumentation - 一组简单的二进制格式通用 mutators 以及一个 [基于语法的变异引擎](https://github.com/googleprojectzero/Jackalope/blob/main/mutators/grammar/README.md) - 通过文件或共享内存进行样本投递 Jackalope 可以并行运行 - 在单机上:通过 `-nthreads` 命令行参数传递 fuzzing 线程数 - 跨多机:通过将一个实例作为服务器运行(`-start_server` 命令行标志),并让工作机上的 fuzzers 连接到此服务器(`-server` 命令行标志)。服务器随后在 workers 之间收集和分发样本、崩溃和覆盖率。 ### 它不能做什么? Jackalope 目前不包括高级变异策略。相反,它附带了一组通用 mutators,这对许多目标都有效,但鼓励用户根据他们想要 fuzz 的目标编写自定义 mutators 和变异策略。 ### 支持哪些平台? 目前,在 Windows、macOS、Linux 和 Android 操作系统上支持黑盒二进制文件的 fuzzing。 在 Linux 上,Jackalope 也可以使用 Sanitizer Coverage 运行(需要目标的源代码)。此模式在 [单独的文档](https://github.com/googleprojectzero/Jackalope/blob/main/README_sancov.md) 中有详细说明。 ## 构建 Jackalope 前置条件:Python 3 1. 打开终端,并在需要时设置构建环境。在 Windows 上,与其打开普通的命令提示符,不如打开针对您构建平台的 Visual Studio 命令提示符,或者运行 `vcvars64.bat` / `vcvars32.bat`。 2. 导航到包含源代码的目录。 3. 运行以下命令: ``` cd Jackalope git clone --recurse-submodules git@github.com:googleprojectzero/TinyInst.git (alternately: git clone --recurse-submodules https://github.com/googleprojectzero/TinyInst.git) mkdir build cd build cmake .. cmake --build . --config Release ``` 生成器参数取决于您的环境。在 macOS 上,您需要使用 `-G Xcode`,而在例如带有 Visual Studio 2019 的 Windows 上进行 64 位构建时,您需要使用 `-G "Visual Studio 16 2019" -A x64`。在 Linux 上,不需要生成器参数。当为 Android 交叉编译时,需要额外的 cmake 参数,这些参数与 [TinyInst Android 构建说明](https://github.com/googleprojectzero/TinyInst/blob/master/Android/README.md) 中描述的相同。如果您在支持通过 /dev/shm 进行共享内存的 VM 上对 Android 进行 fuzzing,`-DANDROID_TARGET=VM` 将启用共享内存样本投递。 关于在 macOS 上针对 arm64e 二进制文件的构建,请参见 [此处](https://github.com/googleprojectzero/TinyInst/blob/master/macOS/arm64e.md)。 在 macOS 上遇到 `No CMAKE_C_COMPILER could be found` 错误?尝试更新 cmake。同时确保已安装 Xcode 并且至少运行过一次(它会在首次运行时安装一些组件)。 ## 运行 Jackalope 用法: `./fuzzer -- <目标命令行>` 支持以下命令行参数: `-in` - 输入目录(包含初始样本集的目录)。如果输入目录为 "-",fuzzer 将尝试恢复之前的会话(与使用 `-restore` 标志相同)。 `-out` - 输出目录 `-t` - 样本超时时间(毫秒) `-t1` - 目标初始化超时时间(例如,在到达目标方法之前,如果定义了的话)。默认为样本超时时间。 `-nthreads` - fuzzer 线程数。默认为 1。 `-delivery ` - 要使用的样本投递机制。如果是 `file`,每个样本将作为文件输出,目标参数中的 "@@" 将替换为该文件的路径。如果是 `shmem`,fuzzer 将创建共享内存,并将目标参数中的 "@@" 替换为共享内存的名称。在这种情况下,目标负责打开共享内存并提取样本。默认为 `file`。 `-file_extension` - 当使用 `file` 样本投递时,将指定的扩展名附加到文件名后。如果目标期望输入文件具有特定扩展名,这很有用。 `-restore` 或 `-resume` - 恢复并继续之前的 fuzzing 会话。fuzzer 和服务器进程均支持恢复。 `-server` - 指定要使用的覆盖率服务器。 `-start_server` - 运行服务器进程而不是 fuzzing 进程。 `-crash_retry` - 尝试重现崩溃的次数。默认为 10。在此重试次数内无法重现的崩溃,或在没有 instrumentation 的情况下运行时无法重现的崩溃,将被标记为不稳定(flaky)。 `-coverage_retry` - 重试重现新覆盖率的次数。在此重试次数内无法可靠重现的覆盖率被视为不稳定。仅包含不稳定覆盖率的样本不会被保存。 `-clean_target_on_coverage` - 在重现覆盖率时重启目标。默认为 true。 `-minimize_samples` - 在将新样本保存到语料库之前尝试将其最小化。默认为 true。 `-iterations_per_round` - 在移至下一个样本之前,对语料库中的样本进行变异和运行的次数。默认为 1000。对于非常慢的目标,您可以考虑降低此值。 `-deterministic_mutations` - 除非确定性变异外,还使用确定性变异。除非使用了 `-server` 标志,否则默认为 true。 `-deterministic_only` - 优先考虑确定性变异。注意:即使使用此标志,fuzzer 仍将使用非确定性变异,但仅在使用完所有确定性变异之后。当使用 `-server` 让单个客户端实例执行确定性变异时,这可能很有用。 `-max_sample_size` - 要使用的最大样本大小。所有大于 `max_sample_size` 的输入样本都会被裁剪,且 mutators 无法生成超过该大小的新样本。默认为 1000000。警告:当使用共享内存样本投递时,`max_sample_size` 必须与目标期望的最大样本大小匹配,例如像 [这里](https://github.com/googleprojectzero/Jackalope/blob/3301a9ac6c6f1483f2d565d372015302e85e6ae2/test.cpp#L33) 的测试目标那样。 `-keep_samples_in_memory` - 是否始终将所有样本保留在内存中。默认为 true。除非语料库太大无法放入内存,否则建议启用。 `-track_ranges` - 启用读取范围跟踪功能。更多信息请参见 [此处](https://github.com/googleprojectzero/Jackalope/blob/main/README_ranges.md)。 `-dry_run` - 使 Jackalope 在处理完所有输入样本后、开始实际 fuzzing 之前退出。适用于语料库最小化(注意:Jackalope 仅将包含先前未见过覆盖率的样本添加到输出语料库中)或重现大量崩溃。 `-add_all_inputs` - 将输入目录中的所有样本添加到 fuzzing 语料库中,即使是那些没有触发任何新覆盖率的样本。默认关闭。 `-dict ` - 提供在变异期间使用的字典。字典应为一个文本文件,每个条目单独占一行。可以使用 `\xXX` 转义序列。 `-dump_coverage` - 定期导出覆盖率(作为输出目录中的 `coverage.txt`),格式适用于导入到 [Lighthouse](https://github.com/gaasedelen/lighthouse)。 有关 TinyInst instrumentation 命令行参数,请参阅 [TinyInst readme](https://github.com/googleprojectzero/TinyInst)。 示例: ``` ./fuzzer -in in -out out -t 1000 -delivery shmem -instrument_module test -target_module test -target_method __Z4fuzzPc -nargs 1 -iterations 10000 -persist -loop -cmp_coverage -- ./test -m @@ ``` 示例: ``` fuzzer.exe -in in -out out -t 1000 -delivery shmem -instrument_module test.exe -target_module test.exe -target_method fuzz -nargs 1 -iterations 10000 -persist -loop -cmp_coverage -- test.exe -m @@ ``` 解释:这将运行 fuzzer,使用 "in" 作为输入目录,"out" 作为输出目录。样本通过共享内存投递,不写入磁盘(`-delivery shmem`)。覆盖率从 `test` / `test.exe` 模块收集(`-instrument_module` 标志)。目标在持久模式下运行,`test` / `test.exe` 模块中的 `fuzz()` 函数在循环中运行。此函数接受 1 个参数,将在循环中最多运行 10000 次迭代,然后重启目标进程。使用了比较覆盖率(`-cmp_coverage` 标志),以便轻松暴力破解多字节比较。`test.exe -m @@` 是目标命令行,其中 @@ 被替换为共享内存名称(如果未使用 `-delivery shmem`,它将被替换为文件名)。 ## 架构 Jackalope 由以下主要类组成: `Fuzzer` - “主”类,处理大多数高级任务,例如跟踪语料库和覆盖率、将作业调度到线程、与服务器通信(如果存在)。Fuzzer 类公开了几个可用于修改其行为的虚方法。用户可以通过继承 `Fuzzer` 类并重载这些方法来创建自定义 fuzzers。 `Sample` - 一个用于存储样本数据(字节)的简单类。 `Mutator` - 处理变异。Mutator 类的主要工作是实现 `Mutate()` 方法,该方法用于修改样本。然而,mutators 也可以更复杂,例如,为每个样本定义额外的上下文,在 `Mutate()` 调用期间传递。Mutators 也可以是“元 mutators”,以不同方式组合其他 mutators。有关内置 mutators,请参阅 `mutator.h`。当 fuzzer 选择一个输入样本时,它会在移至下一个样本之前对其进行一定数量的“轮次”fuzzing,而 mutator 可以控制轮次数量。具体来说,fuzzer 将继续使用相同的输入样本,直到顶层 mutator 从其 `Mutate()` 方法返回 `false`。 `Instrumentation` - 处理目标的运行和覆盖率的收集。该 fuzzer 附带了一个使用 [TinyInst](https://github.com/googleprojectzero/TinyInst) 的 Instrumentation 实现。 `SampleDelivery` - 处理将样本传递给目标。该 fuzzer 附带了通过文件和通过共享内存进行样本投递的功能。 `PRNG` - 伪随机数生成器。默认情况下,Jackalope 使用基于 Mersenne Twister 的 PRNG。 `Server` - 实现 fuzzer 的服务器组件。服务器负责从客户端收集覆盖率以及触发新覆盖率的样本。然后服务器将这些样本分发给其他 fuzzer 进程。 `Client` - 实现与服务器通信的方法。 ## 定制 fuzzer 定制 fuzzer 的“预期”方法是继承 `Fuzzer` 类并重写相关方法。有关简单示例,请参见 [main.cpp](https://github.com/googleprojectzero/Jackalope/blob/main/main.cpp)。可以重写的方法有: `CreateMutator()` - 为 fuzzer 创建 mutator 配置。有关示例,请参见 https://github.com/googleprojectzero/Jackalope/blob/main/main.cpp#L25 `OutputFilter()` - 可以在将样本传递给目标之前对其进行修改,例如修复头部或校验和。默认实现将按原样传递样本。 `AdjustSamplePriority()` - fuzzer 维护一个按优先级排序的待 fuzzing 样本队列。此方法可用于在每次运行后调整样本的优先级。默认实现会降低未产生新覆盖率的每次运行的样本优先级。如果一次运行产生了新覆盖率,则重置该样本的优先级。这确保了 fuzzer 将花费更多时间 fuzzing 那些在变异后产生新覆盖率的样本。 `CreateSampleDelivery()` - 可用于定义将样本投递到目标的自定义机制。例如通过网络或 IPC 发送样本。 `CreateInstrumentation()` - 可以重写以使 fuzzer 使用自定义 instrumentation。 `CreatePRNG()` - 可以重写以使用自定义 PRNG。 ## 常见问题解答 Q: 在 macOS 上,我收到与 `task_for_pid` 相关的错误。 A: 在 macOS 上,调试器(Jackalope 充当目标的调试器)需要拥有适当的权限才能调试另一个进程。这可以通过两种方式解决: - 通过以更高的权限运行 Jackalope(例如使用 `sudo`)。根据目标的不同,可能还需要禁用 SIP。 - 通过使用适当的 entitlements 构建目标,例如 `Get Task Allow` entitlement。最简单的方法是直接使用 XCode 构建目标,因为 XCode 会自动为调试向目标添加适当的 entitlements。 Q: 在 instrumentation 下运行时遇到错误/崩溃/挂起,而在正常运行目标时没有遇到。 A: 这些通常可以通过添加以下标志来解决: - 如果您遇到与自定义异常或 C++ 异常处理相关的错误或减速,在大多数情况下可以通过添加 `-generate_unwind` 标志来解决。如果这不起作用,还有一个更激进的 `-patch_return_addresses` 标志,但请注意它有显著的性能影响。您可以在 [此处](https://github.com/googleprojectzero/TinyInst#return-address-patching) 阅读更多关于这些标志的信息。在 Windows 上,`-generate_unwind`/`-patch_return_addresses` 的另一个替代方案是对目标进行 32 位构建的 fuzzing。 - 尝试添加 `-stack_offset 0x1000` 或其他值。这将解决目标写入地址低于栈指针的 instrumentation 问题(在某些模块的叶函数中在 macOS 上观察到此行为)。 Q: 获取覆盖率很好,但我还能进行内存清理吗? A: 我建议使用操作系统本身提供的分配器来更可靠地捕获内存错误。 - 在 Windows 上:您可以使用 [Page Heap](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags-and-pageheap)。具体来说,您可以使用 `gflags.exe /i +hpa`(注意:需要以管理员身份调用)为目标进程启用 page heap。 - 在 macOS 上:您可以使用 [Guard Malloc](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html),但请注意其在 TinyInst 中的支持是实验性的,可能需要额外的变通方法。有关更多信息,请参见 [此页面](https://github.com/googleprojectzero/TinyInst/tree/master/macOS#tinyinst-and-guard-malloc)。 Q: 我收到关于 "Process hanged before reaching the target method" 的错误 A: 使用 `-t1` 标志设置/增加初始化超时时间。 ## 免责声明 这不是一个官方的 Google 产品。
标签:Android, Bash脚本, CISA项目, DSL, Fuzzing, Google Project Zero, Jackalope, TinyInst, TLS抓取, 二进制安全, 代码生成, 分布式, 安全测试, 攻击性安全, 渗透测试工具, 覆盖率引导, 语法变异, 软件安全, 逆向工具, 黑盒测试