EF/CF 是一种智能合约模糊测试的新方法:它不是使用一个全新的 自定义模糊测试器,而是将现有的 C/C++ 代码模糊测试基础设施重新用于 智能合约。目前,AFL++ 是主要支持的模糊测试器, 尽管对 libfuzzer 和 honggfuzz 也有一些非常基础的支持 。
为什么要使用现有的模糊测试基础设施?
在此过程中我们会遇到哪些问题?
./src/ethmutator/./src/evm2cpp/本仓库是 EF/CF 项目的主要入口点。它包含所有
相关代码,以子项目形式位于 ./src/ 中,并提供一些便利脚本,
用于安装、启动模糊测试活动,以及各种数据集,用以
测试模糊测试器(并与其他工具进行比较)。
./src/ - 包含构建和运行 EF/CF 所需的全部源码;为了
可重现性,所有直接依赖项都以 Git 子模块的形式添加。./data/ - 包含评估期间使用的数据集./scripts - 包含运行实验、安装等的脚本。./docker - 用于基于容器的工作流的 Dockerfile
./docker/tools/ 包含我们用于对比评估 EF/CF 的工具的 Dockerfile。
我们尽力在 Dockerfile 中固定我们在论文中评估所使用的
版本。./EXPERIMENTS.md - 包含一个指南,
用于复现我们论文中的实验。./examples - 包含 EF/CF 生成的示例输出我们在论文中描述了 EF/CF 的架构、实现,并总结了我们的评估 结果:arxiv.org 预印本
在学术工作中引用 EF/CF 时,请使用以下 bibtex 条目 进行引用:```bibtex @InProceedings{efcf2023, author = "Michael Rodler and David Paaßen and Wenting Li and Lukas Bernhard and Thorsten Holz and Ghassan Karame and Lucas Davi", title = "EF/CF: High Performance Smart Contract Fuzzing for Exploit Generation", booktitle = "{IEEE} European Symposium on Security and Privacy ({EuroS&P})", publisher = "{IEEE}", year = "2023", }
## 快速入门
推荐的方式是作为交互式 docker 容器运行 EF/CF。
1. 使用 shell 进入容器 ```
docker run --rm -it ghcr.io/uni-due-syssec/efcf-framework
或从克隆的存储库构建容器 ``` make gitmodules # to fetch the git submodules make container-enter
1. 编译并对 Solidity 合约进行模糊测试,直到第一个崩溃/错误
被发现: ```
efcfuzz --until-crash --out ./baby_bank_results/ --source ./data/examples/baby_bank.sol
没有 Git? 如果你使用 tarball/docker 版本,请忽略此项。
运行 git submodule update --init 以在已克隆的仓库中获取最新的子模块提交。
确保也在 ./src/eEVM 中运行此命令。```
git submodule update --init; cd src/eEVM/; git submodule update --init; cd ../../
*警告:* 运行 `git clone --recursive $repo` 或将 `--recursive` 参数传递给 `git sumbodule (update|init)` 会让 git 递归进入 AFL++ 仓库的子模块,而本项目不需要这些子模块。因此,为了节省空间,最好避免递归检出子模块。
### 容器
我们为基于容器的工作流提供以下便捷的 make 目标:```sh
make container-build # build default efcf container
make container-enter # enter default efcf container in current working dir
如果您想确保干净的构建,可以使用以下命令```sh make container-build CLEAN_CHECKOUT=1
或者,可以使用以下 docker 命令构建该容器:```sh
docker build \
-f docker/ubuntu.Dockerfile \
-t efcf:latest \
.
请注意,还有一个基于 Archlinux 和 Fedora 的 Dockerfile。它们应该 也能正常工作,但未经过同样充分的测试。
如需手动分发 Docker 镜像(例如,如果包含一些本地更改),请使用:``` make container-release docker load -i ./efcf*.tar
我们建议使用以下 Docker 选项启动:
* `--security-opt seccomp=unconfined` — 更好的模糊测试性能
* `--net=host` — 便于访问本地以太坊节点
* `--tmpfs "/tmp/efcf/":exec,size=6g` — 尽可能将 EF/CF 的临时文件放到内存盘上(减少磁盘磨损)
* `--privileged` — 用于运行 `afl-system-config` 或 `efcfuzz --configure-system`
* `-v` — 用于持久化 EF/CF 的输出数据
### 虚拟机 / 裸机
对于基于虚拟机或裸机的工作流程:```sh
make system-install # install efcf to current system (requires root or sudo rights)
请注意,许多脚本反正都基于相对目录布局运行,因此这一步主要是安装依赖项以及一些方便加入你 PATH 的工具。我们已在以下 Linux 发行版上测试过 EF/CF:
(发行版关系不大,我们测试了 LLVM 13 和 14,其中 14 是首选。LLVM 11 或 12 也许仍能用,但一如既往——越新越好。关键是要有一个与我们 fork 的 AFL++ 兼容的 LLVM。)
我们尚未在 Mac OS 上原生测试 EF/CF。很可能无法正常工作(例如,afl-clang-lto 在 Mac OS 上似乎无法运行)。最佳选择是使用 docker。```sh
make gitmodules
docker pull ubuntu:jammy --platform linux/amd64
docker build -t efcf:latest -f docker/ubuntu.Dockerfile --platform linux/amd64 .
docker run --tmpfs "/tmp/efcf/":exec,size=8g --platform linux/amd64 --rm -it -v $(pwd):$(pwd) -w $(pwd) efcf:latest
我们使用 docker desktop v4.21.1 进行了测试,基本的 EF/CF 用法可以正常工作。但请考虑以下事项:
* 如果在构建时看到段错误(segfaults):尝试增加 docker 在 Mac OS 上使用的虚拟机内存限制。
* 尝试在 docker 中使用 rosetta 启用加速——希望这样会快一点。
### 开发环境设置
这些工具通常无需安装。请按照 `system-install.sh` 脚本或 Dockerfiles 中的说明安装所需依赖。
为方便起见,我们提供了一些脚本来更新你的 `PATH`:```sh
# POSIX-like shells (i.e., bash, ...)
source ./scripts/env.sh
# for the fish shell
source ./scripts/env.fish
某些脚本需要 API 密钥来从 Etherscan 服务获取元数据(例如 ABI)。如果你有 API 密钥,则必须设置 ETHERSCAN_API_KEY 环境变量以将其传递给脚本。对于基于 docker 的工作流程,你可以使用 --env 标志启动 docker 容器,或者将你的 API 密钥放入 .etherscan_api_key 文件中,该文件会将 API 密钥嵌入 docker 容器中。
为方便起见,我们使用一个包装脚本,在启动 EF/CF 模糊器时为你处理所有细节:efcfuzz
你可以设置许多命令行选项来配置模糊器在构建和模糊测试过程中的行为。有关选项列表,请查看 efcfuzz --help。
示例
将 solidity 源代码编译为 EF/CF 原生代码,并开始模糊测试 5 分钟(即 300 秒)。```bash efcfuzz --timeout 300 --source ./data/examples/baby_bank.sol
或者,以减少的模糊测试输出启动(`--quiet` 会抑制基础
模糊测试器的输出,而 `--print-progress` 将打印模糊测试
进度的简短摘要),并在 4 个核心上启动模糊测试器。```bash
efcfuzz --quiet --print-progress --cores 4 --timeout 300 --source ./data/examples/baby_bank.sol
使用已编译的字节码,并将字节码编译为 EF/CF 原生代码,然后开始模糊测试。```bash
pushd ./data/examples/; make baby_bank.combined.json; popd efcfuzz --timeout 300 --bin-runtime ./data/examples/baby_bank.combined.json
pushd ./data/examples/; make baby_bank; popd
efcfuzz --timeout 300
--bin-runtime ./data/examples/baby_bank.bin-runtime
--bin-deploy ./data/examples/baby_bank.bin
--abi ./data/examples/baby_bank.abi
该封装器可以从 go-ethereum/erigon 节点导出合约状态,并从此处开始模糊测试。```bash
$ efcfuzz --timeout 300 --live-state 0xfffF8D17CB019E0825c478c666B251A7099df3FD
此外,你可以传递 --include-address-deps=y 来递归搜索导出合约存储中其他账户的地址,并将这些地址也包含到状态导出中。但是,这不会包含 Solidity mapping 类型中存储的其他合约。要真正递归导出整个状态,还需要同时传递 --include-mapping-deps=y 标志。
但请注意,这种递归查找可能导致较长的编译时间和较差的模糊测试性能。尤其是频繁使用的合约可能拥有大量内部状态,使用它们导出的状态会使模糊测试变慢。请检查模糊器是否能够达到超过 1k execs/sec 的执行速度。如果不能,你最好尝试构建一个人工的、更小的状态。尝试在 --dev 模式下运行本地 go-ethereum 节点,并在那里部署你的合约。然后从那里导出实时状态。
该包装器会缓存构建结果,因此第二次模糊测试运行应该会启动得快得多,因为不再需要初始编译时间。如果你只想构建并将结果放入缓存,可以传递 --build-only 参数。
示例:使用属性进行模糊测试
EF/CF 也支持基于属性的模糊测试,使用与 echidna 模糊器相同的属性定义。属性(或不变式)被表示为充当模糊器缺陷预言机的 Solidity 函数。例如,你可以添加一个 Solidity 函数:```solidity function test_property_balance() public view returns (bool) { return total_balance < 1000; }
这表示属性为:total_balance 应始终低于 1000。如果 EF/CF 通过某个交易序列设法违反了该属性,即预言机返回 `false`,它就会报告一个 bug。
要让 EF/CF 知道这是一个属性,你需要在文件中指定一组函数签名;EF/CF 会将其作为模糊测试期间要检查的属性列表读取。
最简单的方法是使用 Solidity 编译器的 `--hashes` 标志获取相关签名,例如:```
solc --hashes ./path/to/your.sol | grep test_property > property_list
现在你可以使用以下命令启动 fuzzer:``` efcfuzz --source ./path/to/your.sol --properties ./property_list -C
你还可以添加 `--disable-detectors` 来禁用内置的基于 ether 的缺陷预言机。