1. 项目概述为什么AFL是模糊测试的“瑞士军刀”如果你在安全研究、漏洞挖掘或者软件质量保障的圈子里待过一阵子肯定对“模糊测试”Fuzzing这个词不陌生。简单来说它就是一种自动化的软件测试技术通过向程序输入大量非预期的、畸形的数据来触发程序内部的异常行为比如崩溃、断言失败或者内存错误从而发现潜在的漏洞。这就像是一个不知疲倦的测试员用各种稀奇古怪的“咒语”去试探一个程序的“魔法防御”看哪里会出纰漏。而在模糊测试的工具箱里American Fuzzy Lop (AFL)无疑是一把传奇的“开山斧”。它由安全研究员 Michał Zalewski 开发以其高效的编译时插桩和遗传算法极大地推动了灰盒模糊测试的普及。然而自2017年底起官方的AFL项目就进入了维护停滞状态。但开源社区的活力在于总有人会接过火炬。AFL正是这样一个项目它汇集了多年来社区为AFL系列开发的各种补丁、优化和新功能成为了一个更强大、更现代、持续活跃的模糊测试框架。今天我们就以Kali Linux这个渗透测试和安全研究的“瑞士军刀”系统为舞台来深入探索AFL。为什么是Kali因为它预装了海量的安全工具和开发环境能让我们免去很多繁琐的依赖配置直接聚焦于AFL的核心使用和实战技巧。这篇文章的目标不仅仅是让你在Kali上把AFL跑起来更是要带你理解它的核心机制、掌握关键参数的调优并分享我在实际漏洞挖掘项目中积累的“踩坑”经验。无论你是刚入门的安全爱好者还是想提升自动化测试效率的开发人员这篇超过5000字的详细指南都将为你提供一条清晰的路径。2. AFL核心架构与模式深度解析在开始敲命令之前我们必须先理解AFL的“内力心法”。它之所以强大是因为它提供了多种“攻击模式”以适应不同的目标场景。盲目地使用默认配置往往事倍功半。2.1 核心组件与工作流程AFL的核心是一个反馈驱动的遗传算法模糊测试器。它的工作流程可以概括为以下几步插桩Instrumentation这是AFL的“眼睛”。它通过修改目标程序的源代码或二进制代码插入一些“探针”。这些探针会在程序运行时记录代码的执行路径例如哪些基本块被执行了边覆盖情况如何。初始种子Seed输入你需要提供一个或多个正常的输入文件作为起点。AFL会从这些文件开始变异。模糊测试循环变异MutationAFL对当前的输入种子进行各种随机变异比如翻转比特、插入/删除字节、拼接文件等生成大量新的测试用例。执行Execution将变异后的输入喂给插桩后的目标程序执行。反馈收集Feedback Collection程序运行时插桩代码会收集覆盖信息例如发现了新的代码路径。进化Evolution基于反馈信息是否发现了新路径、执行速度等AFL会评估测试用例的“价值”。有价值的用例能触发新路径的会被加入“语料库”corpus作为下一轮变异的父本。这就是其“遗传算法”的核心——优胜劣汰让测试用例不断进化去探索更深的代码区域。2.2 四大插桩模式详解与选型指南AFL支持多种插桩模式这是它相比原版AFL的一大优势。选择正确的模式是成功的第一步。2.2.1 LLVM模式afl-clang-fast等—— 首选推荐这是性能最高、信息最丰富的模式。它利用LLVM编译器框架在编译阶段对源代码进行插桩。工作原理使用afl-cc或afl-clang-fast、afl-clang-fast等封装器替代系统的gcc/clang进行编译。编译器会在生成中间代码时插入高效的覆盖率跟踪代码。优势速度快插桩开销极低通常只有2倍左右。精度高能提供更细粒度的覆盖率信息。功能强支持Persistent Mode持久模式对于可以反复调用的函数如图像解析库的某个解析函数可以绕过进程创建的开销将速度提升10倍以上。支持复杂特性如CmpLog用于捕捉魔法字节比较、Context/Ngram覆盖率提供执行上下文信息能更有效地通过复杂的校验逻辑。何时使用只要你有目标程序的源代码这就是你的第一选择。例如测试一个开源的图像库如libpng、一个文件解析工具如file或一个网络协议库。Kali上的准备确保安装了LLVM开发工具。sudo apt update sudo apt install clang llvm llvm-dev2.2.2 GCC_PLUGIN模式afl-gcc-fast—— 备选方案与LLVM模式类似但基于GCC的插件系统。在LLVM不可用或项目构建系统严重依赖GCC时使用。性能稍逊于LLVM模式但依然远好于传统的GCC模式。2.2.3 QEMU模式-Q—— 无源码的利器当你没有目标程序的源代码时QEMU模式是你的救星。它使用二进制翻译技术在程序运行时动态插桩。工作原理AFL包含一个修改版的QEMU它模拟执行目标二进制文件并在模拟过程中动态跟踪代码覆盖。优势无需源码可以对任何Linux用户态二进制程序进行模糊测试包括闭源的软件、固件提取出的二进制文件等。劣势速度慢由于是动态模拟速度通常比源码插桩慢2-5倍。稳定性对某些涉及特殊指令或内核交互的程序可能不稳定。何时使用测试闭源软件、第三方二进制工具、或者那些编译极其复杂的项目暂时不想折腾编译环境。Kali上的注意Kali通常已安装QEMU但AFL需要其特定的补丁版本。通过源码编译AFL时make distrib会自动构建QEMU模式。2.2.4 Unicorn模式-U—— 嵌入式与跨架构测试这是AFL的“黑科技”模式基于Unicorn引擎。工作原理Unicorn是一个纯CPU指令模拟器框架。AFL的Unicorn模式允许你对非原生架构如ARM、MIPS程序在x86主机上的代码片段进行模糊测试甚至可以对没有操作系统的裸机固件代码段进行测试。典型场景物联网设备固件中的某个解析函数。游戏模拟器中的特定模块。任何你只能拿到一小段二进制代码的情况。使用难度较高。你需要编写一个“harness”测试套件来加载目标代码到Unicorn引擎中设置好内存和寄存器状态并告诉AFL从哪里开始执行、在哪里结束、如何提供输入。何时使用进行嵌入式设备安全研究、逆向工程中的漏洞复现验证时。实操心得模式选择速查表目标条件首选模式关键理由有源代码追求最高性能LLVM模式 (afl-clang-fast)速度快支持持久模式等高级特性有源代码但构建系统强依赖GCCGCC_PLUGIN模式 (afl-gcc-fast)兼容性好性能尚可无源代码测试Linux二进制程序QEMU模式 (-Q)无需源码通用性强测试跨架构代码或固件片段Unicorn模式 (-U)唯一选择需自行编写harness快速尝试不确定目标性质先试QEMU模式快速验证目标是否可fuzz再决定深入方向3. 在Kali Linux上部署与构建AFL实战Kali Linux 2023及以后的版本通常已经通过apt预装了afl包。但为了获得最新特性并进行深度定制从源码编译安装仍然是推荐的做法。3.1 方案一使用APT安装最快捷这是上手最快的方式适合快速体验和简单测试。sudo apt update sudo apt install afl afl-clang afl-qemu安装后主要工具如afl-fuzz,afl-cc,afl-gcc等会直接可用。但APT仓库中的版本可能不是最新的。3.2 方案二从源码编译安装推荐用于生产这种方式可以确保获得最新版本并完整构建所有模式LLVM, QEMU, Unicorn等。步骤1获取源码git clone https://github.com/AFLplusplus/AFLplusplus.git cd AFLplusplus步骤2解决构建依赖在Kali上我们需要安装编译所需的依赖包。核心是gcc,make,clang,llvm以及构建QEMU模式需要的libtool,automake等。sudo apt update sudo apt install -y build-essential clang llvm llvm-dev libtool automake flex bison libglib2.0-dev libpixman-1-dev python3-setuptools步骤3编译与安装AFL的构建系统很智能。推荐使用make distrib来构建一个完整的发行版。# 这将会编译所有支持的模式 make distrib # 编译完成后进行安装默认安装到 /usr/local/bin sudo make installmake distrib会依次编译核心的afl-fuzz等工具。LLVM模式afl-cc,afl-clang-fast等。QEMU模式需要一些时间因为它会下载并编译QEMU。其他工具如afl-tmin,afl-cmin等。步骤4验证安装安装完成后运行以下命令检查是否成功afl-fuzz --help | head -20如果看到详细的帮助信息说明核心工具安装成功。再检查LLVM编译器包装器afl-cc --help | head -5注意事项编译过程中的常见坑QEMU编译失败最常见的原因是缺少依赖或网络问题。确保步骤2的依赖已全部安装。如果卡在下载QEMU源码可以尝试手动下载并放置到qemu_mode目录下或者暂时跳过QEMU编译make all代替make distrib。权限问题安装到系统目录需要sudo。如果你只想在当前用户下使用可以make install时不加sudo并确保~/bin或你指定的PREFIX在PATH环境变量中。Clang/LLVM版本AFL对较新的LLVM支持更好。Kali滚动更新通常LLVM版本较新但如果遇到问题可以尝试安装特定版本的llvm-xx和clang-xx。3.3 方案三使用Docker环境隔离如果你不想污染主机环境或者需要在不同版本间切换Docker是完美选择。AFL官方提供了镜像。# 拉取官方镜像 docker pull aflplusplus/aflplusplus # 运行一个交互式容器并将当前目录挂载到容器内的 /src docker run -it -v $(pwd):/src aflplusplus/aflplusplus在容器内你就可以直接使用afl-fuzz等所有工具了。这对于复现漏洞研究环境特别有用。4. AFL全参数详解与实战调优指南afl-fuzz拥有大量的命令行参数理解它们是你从“会用”到“精通”的关键。下面我们分类详解最核心、最常用的参数。4.1 输入输出与控制参数-i, -o, -t, -m这是每次运行都必须指定的基础参数。-i dir指定输入种子目录。里面放一个或多个有效的初始测试用例文件。种子质量至关重要技巧不要只放一个超大文件。可以放几个不同大小、不同结构的典型文件。例如测试一个JPEG解析器可以放一个小尺寸、标准尺寸和一个畸形的JPEG文件。-o dir指定输出目录。AFL的所有发现语料库、崩溃用例、挂起用例等都会保存在这里。技巧为每次测试任务创建独立的输出目录便于管理。例如-o ./fuzz_output_nginx_20250401。-t timeout设置单个测试用例的超时时间毫秒。超过此时间子进程会被杀死。这是防止模糊测试卡死的关键如何设置先手动用time命令运行几次目标程序取最大执行时间的2-5倍并加上一些余量。对于网络服务或复杂解析可能需要设置更长如-t 5000表示5秒。设置过短会误杀正常慢速用例过长会严重拖慢整体进度。-m memory设置目标程序的内存限制MB。AFL会使用cgroups或setrlimit()来限制子进程的内存使用防止内存泄漏导致系统崩溃。建议对于未知目标可以从-m 200200MB开始。如果目标程序本身需要大量内存如浏览器则需要根据情况调整。使用-m none可以禁用内存限制不推荐。基础命令示例afl-fuzz -i ./seeds -o ./findings -t 1000 -m 200 -- ./target_program 这里的是AFL的占位符表示测试用例文件。AFL会将每个生成的输入文件重命名为一个临时文件并将其路径替换后传递给目标程序。4.2 目标程序控制与插桩模式选择--分隔符。之后的所有内容都是目标程序的命令行。输入文件占位符。如果目标程序从标准输入读取则不需要并在命令中省略它。AFL会将输入通过管道传递给程序的标准输入。标准输入示例afl-fuzz -i seeds -o out -t 100 -- ./target_program文件参数示例afl-fuzz -i seeds -o out -t 100 -- ./target_program -Q使用QEMU模式进行二进制插桩。-U使用Unicorn模式。-O使用FRIDA模式另一种二进制插桩工具在某些场景下比QEMU更快。LLVM模式无需特殊参数在编译时就已经决定4.3 模糊测试策略与调度器AFL内置了多种变异策略和调度算法你可以通过参数进行微调。-p schedule电源调度策略。这是AFL相比AFL的重大改进之一它决定了如何分配能量即对某个种子进行变异的次数给不同的种子。fast默认。快速探索新路径。explore倾向于探索低频路径。exploit倾向于在已发现的路径上深度挖掘。seek寻找崩溃。rare关注覆盖率低的边缘。mmopt使用MOpt算法一种基于粒子群优化的全局搜索策略对于陷入局部最优时非常有效。建议对于新目标可以从-p explore开始。如果长时间没有新发现可以尝试-p mmopt。-p seek在已知有崩溃倾向时使用。-R启用Radamsa变异器。Radamsa是另一个著名的模糊测试工具AFL将其集成作为一个变异策略。它可以生成一些非常“聪明”的畸形数据有时能突破简单变异无法到达的角落。用法-R会以一定概率使用Radamsa变异。-RR则只使用Radamsa变异不推荐通常混合使用更好。4.4 并行化与状态管理模糊测试通常是长时间运行的任务并行化可以充分利用多核CPU。-M main设置一个主Fuzzer实例。主实例负责探索新的路径。-S secondary设置一个或多个从属Fuzzer实例。从属实例使用不同的随机种子进行独立的探索并定期从主实例的语料库中同步有趣的发现。如何操作打开第一个终端运行主实例afl-fuzz -i seeds -o sync_dir -M master -- ./target 打开第二个终端运行从属实例必须指向同一个输出目录afl-fuzz -i seeds -o sync_dir -S slave1 -- ./target 你可以继续开第三个、第四个终端启动-S slave2,-S slave3。状态同步所有实例的-o目录相同它们会自动通过该目录下的queue/和crashes/等子目录进行同步。afl-whatsup sync_dir命令可以查看所有实例的汇总状态。4.5 高级特性与性能调优-d跳过确定性变异阶段。确定性变异如顺序位翻转、字节增减非常耗时但系统化。在并行模糊测试中可以让主实例 (-M) 进行确定性变异而从实例 (-S) 使用-d只进行随机变异提高整体吞吐量。-B启用崩溃模式探索。当发现一个崩溃后AFL会尝试围绕这个崩溃输入进行更密集的变异以发现更多相关的崩溃。-l设置输入长度限制。默认情况下AFL会生成越来越大的文件。对于某些有严格长度限制的解析器如网络协议头使用-l 100可以限制输入不超过100字节避免生成大量无效的超长测试用例。环境变量AFL_NO_FORKSRV1禁用forkserver。某些程序如某些Python脚本、或需要复杂初始化的程序与forkserver不兼容导致启动失败。设置此变量可回退到传统的fork()模式但速度会变慢。AFL_SKIP_CPUFREQ1跳过CPU频率检查。在虚拟机或某些限制频率的云主机中AFL可能误判CPU状态此变量可避免警告。AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES1忽略因超时/内存限制导致的子进程退出不将其记录为崩溃。在测试不稳定的目标时有用。5. 从零开始一个完整的AFL漏洞挖掘实战理论说得再多不如动手一试。我们以一个经典的、有漏洞的示例程序test.c为目标完成一次完整的模糊测试实战。5.1 目标准备一个简单的缓冲区溢出程序首先我们编写一个存在栈缓冲区溢出漏洞的C程序// test.c #include stdio.h #include string.h #include stdlib.h void vulnerable_function(char *input) { char buffer[64]; // 只有64字节的栈缓冲区 strcpy(buffer, input); // 危险没有检查输入长度 printf(Input: %s\n, buffer); } int main(int argc, char **argv) { if (argc ! 2) { printf(Usage: %s input_string\n, argv[0]); return 1; } vulnerable_function(argv[1]); return 0; }这个程序很简单它从命令行读取一个字符串然后直接使用不安全的strcpy复制到一个只有64字节的栈缓冲区中。如果输入超过63字节加上结尾的NULL字符就会导致栈溢出可能覆盖函数返回地址造成崩溃或代码执行。5.2 使用LLVM模式编译并插桩我们使用AFL的LLVM编译器来编译这个程序。# 确保使用 afl-clang-fast (或 afl-cc) 进行编译 afl-clang-fast -o test_llvm test.c # 或者使用更通用的 afl-cc # afl-cc -o test_llvm test.c编译成功后会生成test_llvm可执行文件。这个文件已经被插桩内部包含了覆盖率收集代码。5.3 准备种子输入创建一个种子目录并放入一些初始输入文件。即使是最简单的种子也很有用。mkdir seeds echo hello seeds/seed1.txt echo A seeds/seed2.txt echo 1234567890 seeds/seed3.txt # 也可以放一个刚好64字节的边界用例 python3 -c print(A*63) seeds/seed_boundary.txt5.4 启动模糊测试现在启动AFL进行模糊测试。我们使用一个相对较短的超时和内存限制。afl-fuzz -i seeds -o findings -t 50 -m 50 -- ./test_llvm 运行后你会看到AFL经典的ASCII艺术界面。关注以下几个关键区域process timing运行时间、最近发现路径的时间等。overall results总周期数、总路径数、崩溃数、超时数。stage progress当前正在进行的变异阶段如“bitflip 1/1”, “arith 8/8”等。map coverage位图覆盖率。cfg_表示边缘覆盖率。让它在后台运行你可以按CtrlC暂停然后输入screen或tmux命令在后台会话中运行或者直接使用nohupnohup afl-fuzz -i seeds -o findings -t 50 -m 50 -- ./test_llvm fuzz.log 21 5.5 分析结果运行一段时间后可能只需要几秒钟到几分钟对于这个简单程序你应该能在findings目录下看到结果。ls -la findings/你会看到类似这样的结构queue/这是最重要的目录里面保存了所有发现新执行路径的测试用例。这些是进化后的“精英”种子。crashes/导致目标程序崩溃如段错误SIGSEGV的测试用例。id:000000,sig:06,src:000000,...这样的文件名。hangs/导致目标程序超时挂起的测试用例。plot_data用于生成图表的性能数据。查看崩溃# 查看第一个崩溃文件的内容可能是二进制用hexdump或xxd查看 xxd findings/crashes/id\:000000\,sig\:06\,src\:000000\,time\:123\,op\:havoc\,rep\:4 # 或者直接用它触发程序 ./test_llvm cat findings/crashes/id\:000000\,sig\:06\,src\:000000\,time\:123\,op\:havoc\,rep\:4你应该会看到Segmentation fault。恭喜你刚刚用AFL找到了第一个漏洞5.6 使用辅助工具精炼结果AFL自带强大的工具来管理生成的测试用例。afl-cmin语料库最小化。queue/里的文件可能会越来越多其中很多是冗余的。这个工具可以找出一个最小的子集仍然能覆盖所有已发现的路径。mkdir minimized_corpus afl-cmin -i findings/queue -o minimized_corpus -- ./test_llvm afl-tmin测试用例最小化。对于一个导致崩溃的特定文件它可以尝试删除其中无关的字节得到一个最小的、仍然能触发崩溃的输入。这对于分析根本原因至关重要。# 对某个崩溃文件进行最小化 afl-tmin -i findings/crashes/id:000000,... -o minimized_crash -- ./test_llvm 最小化后的文件可能只有几十个字节清晰地展示了触发溢出所需的模式。6. 常见问题、排查技巧与高级实战心得即使按照教程操作你也可能会遇到各种问题。下面是我在多年使用中总结的“避坑指南”。6.1 目标程序编译与链接问题问题使用afl-clang-fast编译大型项目如nginx、openssl时失败。排查检查构建系统很多项目使用autoconf/cmake。你需要覆盖编译器变量。# 对于 configure 脚本 CCafl-clang-fast CXXafl-clang-fast ./configure --prefix/some/path make # 对于 cmake mkdir build cd build CCafl-clang-fast CXXafl-clang-fast cmake .. make处理静态链接库如果项目依赖第三方静态库.a文件这些库也必须用AFL编译器编译否则链接的代码没有插桩会产生覆盖率“黑洞”。使用afl-cc的包装脚本对于复杂的构建系统可以创建一个包装脚本my_afl_cc.sh#!/bin/bash # 将某些特殊的编译标志如 -fsanitizeaddress传递给真正的编译器 # 但移除可能影响插桩的链接时优化LTO标志 ARGS() for arg in $; do [[ $arg *-fsanitize* ]] continue # 移除sanitizer或根据需要保留 [[ $arg *-flto* ]] continue # 通常需要移除LTO ARGS($arg) done exec /usr/local/bin/afl-clang-fast ${ARGS[]}然后CC/path/to/my_afl_cc.sh ./configure ...6.2 模糊测试运行时问题问题AFL启动后速度极慢如每秒只有几次执行或者一直显示“calibrating”。排查检查-t超时设置设置得太短会导致大量进程因超时被杀死拖慢速度。先用一个正常输入手动测试运行时间。检查目标程序目标程序本身是否非常耗时是否每次启动都要初始化大量资源考虑使用持久模式Persistent Mode。持久模式修改你的目标代码使其在一个循环中反复调用要测试的函数而不是每次启动新进程。在AFL命令行中用-p参数指定循环次数。这可以将速度提升一个数量级。// 示例test_persistent.c #include stdio.h #include string.h #include unistd.h #include __AFL_FUZZ_INIT.h // AFL提供的头文件 void vulnerable_function(char *buf) { char local_buf[64]; strcpy(local_buf, buf); // ... do something ... } int main() { __AFL_FUZZ_INIT(); unsigned char *buf __AFL_FUZZ_TESTCASE_BUF; while (__AFL_LOOP(10000)) { // AFL持久循环 int len __AFL_FUZZ_TESTCASE_LEN; vulnerable_function((char*)buf); } return 0; }编译后运行afl-fuzz -i seeds -o out -p exploit -- ./test_persistent检查系统配置CPU频率调控确保CPU运行在性能模式。sudo cpupower frequency-set -g performance。核心绑定避免Fuzzer在CPU核心间跳跃可以使用taskset绑定核心如taskset -c 0 afl-fuzz ...。虚拟化环境在VMware/VirtualBox中确保为虚拟机分配了足够的CPU核心并启用VT-x/AMD-V虚拟化支持。在KVM/QEMU中性能更好。问题发现了很多崩溃但看起来都是同一个问题比如都是NULL指针解引用。排查使用afl-collect或编写脚本对崩溃进行去重。AFL本身会基于崩溃地址进行初步去重但更深层的去重需要结合栈回溯信息。可以结合gdb脚本或exploitable工具进行分类。6.3 提升模糊测试效率的进阶技巧字典Dictionary如果你知道目标程序解析的数据格式如HTTP关键词、XML标签、PNG块类型可以提供一个字典文件-x参数。AFL会在变异过程中尝试插入这些关键字极大提高命中有效语法的概率。# 创建一个简单的字典文件 http.dict # 内容如 # GET # POST # HTTP/1.1 # Host: afl-fuzz -i seeds -o out -x ./http.dict -- ./http_parser 语料库蒸馏与调度不要只运行一次就结束。定期例如每24小时用afl-cmin最小化queue/。将最小化后的语料库作为新的种子输入 (-i)重新开始一轮模糊测试。这能确保能量集中在最有效的测试用例上。结合Sanitizer在编译目标程序时可以同时启用AddressSanitizer (ASan) 或 UndefinedBehaviorSanitizer (UBSan)。这能让AFL发现更多的内存错误如use-after-free, buffer underflow和未定义行为而不仅仅是段错误。AFL_USE_ASAN1 afl-clang-fast -fsanitizeaddress,undefined -o test_asan test.c注意Sanitizer会带来性能开销约2倍并且会占用更多内存。建议在初步广覆盖测试后用ASan版本进行深度测试。并行与分布式对于大型项目在一台机器上并行多个实例 (-M,-S) 是基础。更进一步可以使用afl-network或自定义脚本将语料库在多个物理机器间同步实现分布式模糊测试集群。6.4 解读AFL状态界面理解状态界面的信息能帮你判断Fuzzer的运行健康度。cycles done当这个数字变绿意味着模糊测试器已经对当前语料库完成了至少一次完整的“轮回”可能进入了瓶颈期。此时可以考虑引入新的种子、使用-p mmopt调度策略或者检查是否有字典可以添加。stability如果这个值低于90%说明目标程序的行为存在不确定性比如使用了未初始化的内存、随机数。这会导致覆盖率反馈不准确严重影响效率。你需要设法让目标程序确定性更强。uniq crashes/hangs唯一崩溃/挂起的数量。这是你成果的核心指标。AFL是一个极其强大且灵活的工具但它的威力需要正确的配置和耐心的调优才能完全发挥。在Kali Linux这个集成了大量辅助工具的环境中你可以很方便地结合调试器gdb、逆向工具radare2, Ghidra和脚本语言Python对AFL发现的崩溃进行深入分析完成从漏洞挖掘到利用分析的完整链条。记住模糊测试不是一蹴而就的魔法而是一个需要持续观察、分析和调整的工程过程。现在你已经掌握了启动这个过程的钥匙接下来就是选择目标开始你的探索之旅了。
AFL++模糊测试实战:从核心原理到Kali Linux漏洞挖掘
发布时间:2026/7/4 2:11:33
1. 项目概述为什么AFL是模糊测试的“瑞士军刀”如果你在安全研究、漏洞挖掘或者软件质量保障的圈子里待过一阵子肯定对“模糊测试”Fuzzing这个词不陌生。简单来说它就是一种自动化的软件测试技术通过向程序输入大量非预期的、畸形的数据来触发程序内部的异常行为比如崩溃、断言失败或者内存错误从而发现潜在的漏洞。这就像是一个不知疲倦的测试员用各种稀奇古怪的“咒语”去试探一个程序的“魔法防御”看哪里会出纰漏。而在模糊测试的工具箱里American Fuzzy Lop (AFL)无疑是一把传奇的“开山斧”。它由安全研究员 Michał Zalewski 开发以其高效的编译时插桩和遗传算法极大地推动了灰盒模糊测试的普及。然而自2017年底起官方的AFL项目就进入了维护停滞状态。但开源社区的活力在于总有人会接过火炬。AFL正是这样一个项目它汇集了多年来社区为AFL系列开发的各种补丁、优化和新功能成为了一个更强大、更现代、持续活跃的模糊测试框架。今天我们就以Kali Linux这个渗透测试和安全研究的“瑞士军刀”系统为舞台来深入探索AFL。为什么是Kali因为它预装了海量的安全工具和开发环境能让我们免去很多繁琐的依赖配置直接聚焦于AFL的核心使用和实战技巧。这篇文章的目标不仅仅是让你在Kali上把AFL跑起来更是要带你理解它的核心机制、掌握关键参数的调优并分享我在实际漏洞挖掘项目中积累的“踩坑”经验。无论你是刚入门的安全爱好者还是想提升自动化测试效率的开发人员这篇超过5000字的详细指南都将为你提供一条清晰的路径。2. AFL核心架构与模式深度解析在开始敲命令之前我们必须先理解AFL的“内力心法”。它之所以强大是因为它提供了多种“攻击模式”以适应不同的目标场景。盲目地使用默认配置往往事倍功半。2.1 核心组件与工作流程AFL的核心是一个反馈驱动的遗传算法模糊测试器。它的工作流程可以概括为以下几步插桩Instrumentation这是AFL的“眼睛”。它通过修改目标程序的源代码或二进制代码插入一些“探针”。这些探针会在程序运行时记录代码的执行路径例如哪些基本块被执行了边覆盖情况如何。初始种子Seed输入你需要提供一个或多个正常的输入文件作为起点。AFL会从这些文件开始变异。模糊测试循环变异MutationAFL对当前的输入种子进行各种随机变异比如翻转比特、插入/删除字节、拼接文件等生成大量新的测试用例。执行Execution将变异后的输入喂给插桩后的目标程序执行。反馈收集Feedback Collection程序运行时插桩代码会收集覆盖信息例如发现了新的代码路径。进化Evolution基于反馈信息是否发现了新路径、执行速度等AFL会评估测试用例的“价值”。有价值的用例能触发新路径的会被加入“语料库”corpus作为下一轮变异的父本。这就是其“遗传算法”的核心——优胜劣汰让测试用例不断进化去探索更深的代码区域。2.2 四大插桩模式详解与选型指南AFL支持多种插桩模式这是它相比原版AFL的一大优势。选择正确的模式是成功的第一步。2.2.1 LLVM模式afl-clang-fast等—— 首选推荐这是性能最高、信息最丰富的模式。它利用LLVM编译器框架在编译阶段对源代码进行插桩。工作原理使用afl-cc或afl-clang-fast、afl-clang-fast等封装器替代系统的gcc/clang进行编译。编译器会在生成中间代码时插入高效的覆盖率跟踪代码。优势速度快插桩开销极低通常只有2倍左右。精度高能提供更细粒度的覆盖率信息。功能强支持Persistent Mode持久模式对于可以反复调用的函数如图像解析库的某个解析函数可以绕过进程创建的开销将速度提升10倍以上。支持复杂特性如CmpLog用于捕捉魔法字节比较、Context/Ngram覆盖率提供执行上下文信息能更有效地通过复杂的校验逻辑。何时使用只要你有目标程序的源代码这就是你的第一选择。例如测试一个开源的图像库如libpng、一个文件解析工具如file或一个网络协议库。Kali上的准备确保安装了LLVM开发工具。sudo apt update sudo apt install clang llvm llvm-dev2.2.2 GCC_PLUGIN模式afl-gcc-fast—— 备选方案与LLVM模式类似但基于GCC的插件系统。在LLVM不可用或项目构建系统严重依赖GCC时使用。性能稍逊于LLVM模式但依然远好于传统的GCC模式。2.2.3 QEMU模式-Q—— 无源码的利器当你没有目标程序的源代码时QEMU模式是你的救星。它使用二进制翻译技术在程序运行时动态插桩。工作原理AFL包含一个修改版的QEMU它模拟执行目标二进制文件并在模拟过程中动态跟踪代码覆盖。优势无需源码可以对任何Linux用户态二进制程序进行模糊测试包括闭源的软件、固件提取出的二进制文件等。劣势速度慢由于是动态模拟速度通常比源码插桩慢2-5倍。稳定性对某些涉及特殊指令或内核交互的程序可能不稳定。何时使用测试闭源软件、第三方二进制工具、或者那些编译极其复杂的项目暂时不想折腾编译环境。Kali上的注意Kali通常已安装QEMU但AFL需要其特定的补丁版本。通过源码编译AFL时make distrib会自动构建QEMU模式。2.2.4 Unicorn模式-U—— 嵌入式与跨架构测试这是AFL的“黑科技”模式基于Unicorn引擎。工作原理Unicorn是一个纯CPU指令模拟器框架。AFL的Unicorn模式允许你对非原生架构如ARM、MIPS程序在x86主机上的代码片段进行模糊测试甚至可以对没有操作系统的裸机固件代码段进行测试。典型场景物联网设备固件中的某个解析函数。游戏模拟器中的特定模块。任何你只能拿到一小段二进制代码的情况。使用难度较高。你需要编写一个“harness”测试套件来加载目标代码到Unicorn引擎中设置好内存和寄存器状态并告诉AFL从哪里开始执行、在哪里结束、如何提供输入。何时使用进行嵌入式设备安全研究、逆向工程中的漏洞复现验证时。实操心得模式选择速查表目标条件首选模式关键理由有源代码追求最高性能LLVM模式 (afl-clang-fast)速度快支持持久模式等高级特性有源代码但构建系统强依赖GCCGCC_PLUGIN模式 (afl-gcc-fast)兼容性好性能尚可无源代码测试Linux二进制程序QEMU模式 (-Q)无需源码通用性强测试跨架构代码或固件片段Unicorn模式 (-U)唯一选择需自行编写harness快速尝试不确定目标性质先试QEMU模式快速验证目标是否可fuzz再决定深入方向3. 在Kali Linux上部署与构建AFL实战Kali Linux 2023及以后的版本通常已经通过apt预装了afl包。但为了获得最新特性并进行深度定制从源码编译安装仍然是推荐的做法。3.1 方案一使用APT安装最快捷这是上手最快的方式适合快速体验和简单测试。sudo apt update sudo apt install afl afl-clang afl-qemu安装后主要工具如afl-fuzz,afl-cc,afl-gcc等会直接可用。但APT仓库中的版本可能不是最新的。3.2 方案二从源码编译安装推荐用于生产这种方式可以确保获得最新版本并完整构建所有模式LLVM, QEMU, Unicorn等。步骤1获取源码git clone https://github.com/AFLplusplus/AFLplusplus.git cd AFLplusplus步骤2解决构建依赖在Kali上我们需要安装编译所需的依赖包。核心是gcc,make,clang,llvm以及构建QEMU模式需要的libtool,automake等。sudo apt update sudo apt install -y build-essential clang llvm llvm-dev libtool automake flex bison libglib2.0-dev libpixman-1-dev python3-setuptools步骤3编译与安装AFL的构建系统很智能。推荐使用make distrib来构建一个完整的发行版。# 这将会编译所有支持的模式 make distrib # 编译完成后进行安装默认安装到 /usr/local/bin sudo make installmake distrib会依次编译核心的afl-fuzz等工具。LLVM模式afl-cc,afl-clang-fast等。QEMU模式需要一些时间因为它会下载并编译QEMU。其他工具如afl-tmin,afl-cmin等。步骤4验证安装安装完成后运行以下命令检查是否成功afl-fuzz --help | head -20如果看到详细的帮助信息说明核心工具安装成功。再检查LLVM编译器包装器afl-cc --help | head -5注意事项编译过程中的常见坑QEMU编译失败最常见的原因是缺少依赖或网络问题。确保步骤2的依赖已全部安装。如果卡在下载QEMU源码可以尝试手动下载并放置到qemu_mode目录下或者暂时跳过QEMU编译make all代替make distrib。权限问题安装到系统目录需要sudo。如果你只想在当前用户下使用可以make install时不加sudo并确保~/bin或你指定的PREFIX在PATH环境变量中。Clang/LLVM版本AFL对较新的LLVM支持更好。Kali滚动更新通常LLVM版本较新但如果遇到问题可以尝试安装特定版本的llvm-xx和clang-xx。3.3 方案三使用Docker环境隔离如果你不想污染主机环境或者需要在不同版本间切换Docker是完美选择。AFL官方提供了镜像。# 拉取官方镜像 docker pull aflplusplus/aflplusplus # 运行一个交互式容器并将当前目录挂载到容器内的 /src docker run -it -v $(pwd):/src aflplusplus/aflplusplus在容器内你就可以直接使用afl-fuzz等所有工具了。这对于复现漏洞研究环境特别有用。4. AFL全参数详解与实战调优指南afl-fuzz拥有大量的命令行参数理解它们是你从“会用”到“精通”的关键。下面我们分类详解最核心、最常用的参数。4.1 输入输出与控制参数-i, -o, -t, -m这是每次运行都必须指定的基础参数。-i dir指定输入种子目录。里面放一个或多个有效的初始测试用例文件。种子质量至关重要技巧不要只放一个超大文件。可以放几个不同大小、不同结构的典型文件。例如测试一个JPEG解析器可以放一个小尺寸、标准尺寸和一个畸形的JPEG文件。-o dir指定输出目录。AFL的所有发现语料库、崩溃用例、挂起用例等都会保存在这里。技巧为每次测试任务创建独立的输出目录便于管理。例如-o ./fuzz_output_nginx_20250401。-t timeout设置单个测试用例的超时时间毫秒。超过此时间子进程会被杀死。这是防止模糊测试卡死的关键如何设置先手动用time命令运行几次目标程序取最大执行时间的2-5倍并加上一些余量。对于网络服务或复杂解析可能需要设置更长如-t 5000表示5秒。设置过短会误杀正常慢速用例过长会严重拖慢整体进度。-m memory设置目标程序的内存限制MB。AFL会使用cgroups或setrlimit()来限制子进程的内存使用防止内存泄漏导致系统崩溃。建议对于未知目标可以从-m 200200MB开始。如果目标程序本身需要大量内存如浏览器则需要根据情况调整。使用-m none可以禁用内存限制不推荐。基础命令示例afl-fuzz -i ./seeds -o ./findings -t 1000 -m 200 -- ./target_program 这里的是AFL的占位符表示测试用例文件。AFL会将每个生成的输入文件重命名为一个临时文件并将其路径替换后传递给目标程序。4.2 目标程序控制与插桩模式选择--分隔符。之后的所有内容都是目标程序的命令行。输入文件占位符。如果目标程序从标准输入读取则不需要并在命令中省略它。AFL会将输入通过管道传递给程序的标准输入。标准输入示例afl-fuzz -i seeds -o out -t 100 -- ./target_program文件参数示例afl-fuzz -i seeds -o out -t 100 -- ./target_program -Q使用QEMU模式进行二进制插桩。-U使用Unicorn模式。-O使用FRIDA模式另一种二进制插桩工具在某些场景下比QEMU更快。LLVM模式无需特殊参数在编译时就已经决定4.3 模糊测试策略与调度器AFL内置了多种变异策略和调度算法你可以通过参数进行微调。-p schedule电源调度策略。这是AFL相比AFL的重大改进之一它决定了如何分配能量即对某个种子进行变异的次数给不同的种子。fast默认。快速探索新路径。explore倾向于探索低频路径。exploit倾向于在已发现的路径上深度挖掘。seek寻找崩溃。rare关注覆盖率低的边缘。mmopt使用MOpt算法一种基于粒子群优化的全局搜索策略对于陷入局部最优时非常有效。建议对于新目标可以从-p explore开始。如果长时间没有新发现可以尝试-p mmopt。-p seek在已知有崩溃倾向时使用。-R启用Radamsa变异器。Radamsa是另一个著名的模糊测试工具AFL将其集成作为一个变异策略。它可以生成一些非常“聪明”的畸形数据有时能突破简单变异无法到达的角落。用法-R会以一定概率使用Radamsa变异。-RR则只使用Radamsa变异不推荐通常混合使用更好。4.4 并行化与状态管理模糊测试通常是长时间运行的任务并行化可以充分利用多核CPU。-M main设置一个主Fuzzer实例。主实例负责探索新的路径。-S secondary设置一个或多个从属Fuzzer实例。从属实例使用不同的随机种子进行独立的探索并定期从主实例的语料库中同步有趣的发现。如何操作打开第一个终端运行主实例afl-fuzz -i seeds -o sync_dir -M master -- ./target 打开第二个终端运行从属实例必须指向同一个输出目录afl-fuzz -i seeds -o sync_dir -S slave1 -- ./target 你可以继续开第三个、第四个终端启动-S slave2,-S slave3。状态同步所有实例的-o目录相同它们会自动通过该目录下的queue/和crashes/等子目录进行同步。afl-whatsup sync_dir命令可以查看所有实例的汇总状态。4.5 高级特性与性能调优-d跳过确定性变异阶段。确定性变异如顺序位翻转、字节增减非常耗时但系统化。在并行模糊测试中可以让主实例 (-M) 进行确定性变异而从实例 (-S) 使用-d只进行随机变异提高整体吞吐量。-B启用崩溃模式探索。当发现一个崩溃后AFL会尝试围绕这个崩溃输入进行更密集的变异以发现更多相关的崩溃。-l设置输入长度限制。默认情况下AFL会生成越来越大的文件。对于某些有严格长度限制的解析器如网络协议头使用-l 100可以限制输入不超过100字节避免生成大量无效的超长测试用例。环境变量AFL_NO_FORKSRV1禁用forkserver。某些程序如某些Python脚本、或需要复杂初始化的程序与forkserver不兼容导致启动失败。设置此变量可回退到传统的fork()模式但速度会变慢。AFL_SKIP_CPUFREQ1跳过CPU频率检查。在虚拟机或某些限制频率的云主机中AFL可能误判CPU状态此变量可避免警告。AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES1忽略因超时/内存限制导致的子进程退出不将其记录为崩溃。在测试不稳定的目标时有用。5. 从零开始一个完整的AFL漏洞挖掘实战理论说得再多不如动手一试。我们以一个经典的、有漏洞的示例程序test.c为目标完成一次完整的模糊测试实战。5.1 目标准备一个简单的缓冲区溢出程序首先我们编写一个存在栈缓冲区溢出漏洞的C程序// test.c #include stdio.h #include string.h #include stdlib.h void vulnerable_function(char *input) { char buffer[64]; // 只有64字节的栈缓冲区 strcpy(buffer, input); // 危险没有检查输入长度 printf(Input: %s\n, buffer); } int main(int argc, char **argv) { if (argc ! 2) { printf(Usage: %s input_string\n, argv[0]); return 1; } vulnerable_function(argv[1]); return 0; }这个程序很简单它从命令行读取一个字符串然后直接使用不安全的strcpy复制到一个只有64字节的栈缓冲区中。如果输入超过63字节加上结尾的NULL字符就会导致栈溢出可能覆盖函数返回地址造成崩溃或代码执行。5.2 使用LLVM模式编译并插桩我们使用AFL的LLVM编译器来编译这个程序。# 确保使用 afl-clang-fast (或 afl-cc) 进行编译 afl-clang-fast -o test_llvm test.c # 或者使用更通用的 afl-cc # afl-cc -o test_llvm test.c编译成功后会生成test_llvm可执行文件。这个文件已经被插桩内部包含了覆盖率收集代码。5.3 准备种子输入创建一个种子目录并放入一些初始输入文件。即使是最简单的种子也很有用。mkdir seeds echo hello seeds/seed1.txt echo A seeds/seed2.txt echo 1234567890 seeds/seed3.txt # 也可以放一个刚好64字节的边界用例 python3 -c print(A*63) seeds/seed_boundary.txt5.4 启动模糊测试现在启动AFL进行模糊测试。我们使用一个相对较短的超时和内存限制。afl-fuzz -i seeds -o findings -t 50 -m 50 -- ./test_llvm 运行后你会看到AFL经典的ASCII艺术界面。关注以下几个关键区域process timing运行时间、最近发现路径的时间等。overall results总周期数、总路径数、崩溃数、超时数。stage progress当前正在进行的变异阶段如“bitflip 1/1”, “arith 8/8”等。map coverage位图覆盖率。cfg_表示边缘覆盖率。让它在后台运行你可以按CtrlC暂停然后输入screen或tmux命令在后台会话中运行或者直接使用nohupnohup afl-fuzz -i seeds -o findings -t 50 -m 50 -- ./test_llvm fuzz.log 21 5.5 分析结果运行一段时间后可能只需要几秒钟到几分钟对于这个简单程序你应该能在findings目录下看到结果。ls -la findings/你会看到类似这样的结构queue/这是最重要的目录里面保存了所有发现新执行路径的测试用例。这些是进化后的“精英”种子。crashes/导致目标程序崩溃如段错误SIGSEGV的测试用例。id:000000,sig:06,src:000000,...这样的文件名。hangs/导致目标程序超时挂起的测试用例。plot_data用于生成图表的性能数据。查看崩溃# 查看第一个崩溃文件的内容可能是二进制用hexdump或xxd查看 xxd findings/crashes/id\:000000\,sig\:06\,src\:000000\,time\:123\,op\:havoc\,rep\:4 # 或者直接用它触发程序 ./test_llvm cat findings/crashes/id\:000000\,sig\:06\,src\:000000\,time\:123\,op\:havoc\,rep\:4你应该会看到Segmentation fault。恭喜你刚刚用AFL找到了第一个漏洞5.6 使用辅助工具精炼结果AFL自带强大的工具来管理生成的测试用例。afl-cmin语料库最小化。queue/里的文件可能会越来越多其中很多是冗余的。这个工具可以找出一个最小的子集仍然能覆盖所有已发现的路径。mkdir minimized_corpus afl-cmin -i findings/queue -o minimized_corpus -- ./test_llvm afl-tmin测试用例最小化。对于一个导致崩溃的特定文件它可以尝试删除其中无关的字节得到一个最小的、仍然能触发崩溃的输入。这对于分析根本原因至关重要。# 对某个崩溃文件进行最小化 afl-tmin -i findings/crashes/id:000000,... -o minimized_crash -- ./test_llvm 最小化后的文件可能只有几十个字节清晰地展示了触发溢出所需的模式。6. 常见问题、排查技巧与高级实战心得即使按照教程操作你也可能会遇到各种问题。下面是我在多年使用中总结的“避坑指南”。6.1 目标程序编译与链接问题问题使用afl-clang-fast编译大型项目如nginx、openssl时失败。排查检查构建系统很多项目使用autoconf/cmake。你需要覆盖编译器变量。# 对于 configure 脚本 CCafl-clang-fast CXXafl-clang-fast ./configure --prefix/some/path make # 对于 cmake mkdir build cd build CCafl-clang-fast CXXafl-clang-fast cmake .. make处理静态链接库如果项目依赖第三方静态库.a文件这些库也必须用AFL编译器编译否则链接的代码没有插桩会产生覆盖率“黑洞”。使用afl-cc的包装脚本对于复杂的构建系统可以创建一个包装脚本my_afl_cc.sh#!/bin/bash # 将某些特殊的编译标志如 -fsanitizeaddress传递给真正的编译器 # 但移除可能影响插桩的链接时优化LTO标志 ARGS() for arg in $; do [[ $arg *-fsanitize* ]] continue # 移除sanitizer或根据需要保留 [[ $arg *-flto* ]] continue # 通常需要移除LTO ARGS($arg) done exec /usr/local/bin/afl-clang-fast ${ARGS[]}然后CC/path/to/my_afl_cc.sh ./configure ...6.2 模糊测试运行时问题问题AFL启动后速度极慢如每秒只有几次执行或者一直显示“calibrating”。排查检查-t超时设置设置得太短会导致大量进程因超时被杀死拖慢速度。先用一个正常输入手动测试运行时间。检查目标程序目标程序本身是否非常耗时是否每次启动都要初始化大量资源考虑使用持久模式Persistent Mode。持久模式修改你的目标代码使其在一个循环中反复调用要测试的函数而不是每次启动新进程。在AFL命令行中用-p参数指定循环次数。这可以将速度提升一个数量级。// 示例test_persistent.c #include stdio.h #include string.h #include unistd.h #include __AFL_FUZZ_INIT.h // AFL提供的头文件 void vulnerable_function(char *buf) { char local_buf[64]; strcpy(local_buf, buf); // ... do something ... } int main() { __AFL_FUZZ_INIT(); unsigned char *buf __AFL_FUZZ_TESTCASE_BUF; while (__AFL_LOOP(10000)) { // AFL持久循环 int len __AFL_FUZZ_TESTCASE_LEN; vulnerable_function((char*)buf); } return 0; }编译后运行afl-fuzz -i seeds -o out -p exploit -- ./test_persistent检查系统配置CPU频率调控确保CPU运行在性能模式。sudo cpupower frequency-set -g performance。核心绑定避免Fuzzer在CPU核心间跳跃可以使用taskset绑定核心如taskset -c 0 afl-fuzz ...。虚拟化环境在VMware/VirtualBox中确保为虚拟机分配了足够的CPU核心并启用VT-x/AMD-V虚拟化支持。在KVM/QEMU中性能更好。问题发现了很多崩溃但看起来都是同一个问题比如都是NULL指针解引用。排查使用afl-collect或编写脚本对崩溃进行去重。AFL本身会基于崩溃地址进行初步去重但更深层的去重需要结合栈回溯信息。可以结合gdb脚本或exploitable工具进行分类。6.3 提升模糊测试效率的进阶技巧字典Dictionary如果你知道目标程序解析的数据格式如HTTP关键词、XML标签、PNG块类型可以提供一个字典文件-x参数。AFL会在变异过程中尝试插入这些关键字极大提高命中有效语法的概率。# 创建一个简单的字典文件 http.dict # 内容如 # GET # POST # HTTP/1.1 # Host: afl-fuzz -i seeds -o out -x ./http.dict -- ./http_parser 语料库蒸馏与调度不要只运行一次就结束。定期例如每24小时用afl-cmin最小化queue/。将最小化后的语料库作为新的种子输入 (-i)重新开始一轮模糊测试。这能确保能量集中在最有效的测试用例上。结合Sanitizer在编译目标程序时可以同时启用AddressSanitizer (ASan) 或 UndefinedBehaviorSanitizer (UBSan)。这能让AFL发现更多的内存错误如use-after-free, buffer underflow和未定义行为而不仅仅是段错误。AFL_USE_ASAN1 afl-clang-fast -fsanitizeaddress,undefined -o test_asan test.c注意Sanitizer会带来性能开销约2倍并且会占用更多内存。建议在初步广覆盖测试后用ASan版本进行深度测试。并行与分布式对于大型项目在一台机器上并行多个实例 (-M,-S) 是基础。更进一步可以使用afl-network或自定义脚本将语料库在多个物理机器间同步实现分布式模糊测试集群。6.4 解读AFL状态界面理解状态界面的信息能帮你判断Fuzzer的运行健康度。cycles done当这个数字变绿意味着模糊测试器已经对当前语料库完成了至少一次完整的“轮回”可能进入了瓶颈期。此时可以考虑引入新的种子、使用-p mmopt调度策略或者检查是否有字典可以添加。stability如果这个值低于90%说明目标程序的行为存在不确定性比如使用了未初始化的内存、随机数。这会导致覆盖率反馈不准确严重影响效率。你需要设法让目标程序确定性更强。uniq crashes/hangs唯一崩溃/挂起的数量。这是你成果的核心指标。AFL是一个极其强大且灵活的工具但它的威力需要正确的配置和耐心的调优才能完全发挥。在Kali Linux这个集成了大量辅助工具的环境中你可以很方便地结合调试器gdb、逆向工具radare2, Ghidra和脚本语言Python对AFL发现的崩溃进行深入分析完成从漏洞挖掘到利用分析的完整链条。记住模糊测试不是一蹴而就的魔法而是一个需要持续观察、分析和调整的工程过程。现在你已经掌握了启动这个过程的钥匙接下来就是选择目标开始你的探索之旅了。