资讯动态

AFL模糊测试原理与C程序插桩实战指南

发布时间:2026/9/29 19:33:58 来源:尧图企业网站定制
1. 这不是“跑个工具就完事”的教程AFL挖洞的本质是理解程序行为你搜“AFL 漏洞挖掘”十有八九会看到一堆“三步安装、五步 fuzz、一键出 crash”的速成帖。我试过也写过——但后来删了。因为那根本不是在教人挖洞是在教人按回车。真正卡住你的从来不是afl-fuzz -i in -o out -- ./target这行命令而是你按下回车后屏幕停在cycles done: 0不动时心里那个问号它到底在等什么为什么我的hello world程序跑不起来为什么fuzzer_stats里execs_done一直是 0为什么crashes目录空空如也而hangs却在疯狂增长AFL 不是黑盒扫描器它是基于覆盖率反馈的灰盒模糊测试引擎。关键词是“灰盒”——它不需要源码所以能测闭源二进制但必须能插桩instrument或通过其他方式感知程序内部路径变化。它不靠规则匹配也不靠语法树分析它靠的是让程序反复执行、微调输入、观察哪次执行走到了新的代码分支。这个“新分支”就是它判定“有价值”的信号。所以当你看到paths_found: 12那不是 AFL 发现了 12 个漏洞而是它找到了 12 条不同的代码执行路径。漏洞只是其中某条路径因输入异常而崩溃的副产品。这直接决定了整个流程的成败逻辑AFL 的核心目标是最大化路径覆盖率漏洞发现是路径探索过程中的自然结果。如果你的程序本身结构简单、分支极少比如一个只读输入、只打印的printf程序或者它对输入极其敏感比如一读到非法字符就exit(1)AFL 根本没机会深入它连第一层 if 判断都过不去。这就是为什么网上那些“Hello World”示例总失败——它太干净没有“可探索”的空间。真正的靶子得是带解析逻辑的一个简易的 JSON 解析器、一个自定义协议的包解析函数、甚至是一个带strcmp和memcpy的字符串处理小工具。它们有判断、有循环、有内存操作——这才是 AFL 能施展拳脚的土壤。我带过的实习生里80% 的人卡在第一步编译。他们用gcc hello.c -o hello编译完afl-fuzz一跑就报错No instrumentation detected。这不是 AFL 的 bug是你没告诉它“请在我程序里埋点”。AFL 的插桩instrumentation不是魔法它需要你用afl-gcc或afl-clang替换掉系统默认的编译器让编译器在生成的二进制里自动插入大量用于统计基本块basic block执行次数的探针代码。这些探针就像在程序每一条可能的执行路径上装了计数器AFL 主进程通过共享内存实时读取这些计数器的变化从而知道“这次输入让程序走了一条从未走过的路”。没有插桩AFL 就像蒙着眼睛开车它根本不知道自己开到了哪里。所以这篇内容不叫“手把手教你安装 AFL”它叫“手把手带你理解 AFL 如何与 C 程序对话”。从afl-gcc编译那一刻起你就在和程序的底层执行流建立连接。后面所有的in目录、out目录、fuzzer_stats文件都是这个连接产生的数据快照。搞懂这个连接怎么建立、怎么维持、怎么解读它的反馈你才能把 AFL 从一个“跑起来就不管”的黑盒变成一个你随时能诊断、能调优、能读懂它“语言”的伙伴。下面我们就从最基础的环境准备开始但每一步都会告诉你“为什么非得这样”。2. 环境准备与 AFL 安装不是复制粘贴而是理解依赖链2.1 为什么必须用 Ubuntu/DebianWindows 和 macOS 的真实困境你可能会想“我 Mac 上有 Homebrewbrew install afl不就完了”或者“我 Win10 有 WSL装个 Ubuntu 子系统不也一样”——理论上可以但实操中90% 的坑都源于环境差异。AFL 的核心设计哲学是“极致轻量、深度绑定 Linux 内核特性”。它重度依赖ptrace系统调用进行进程控制依赖fork()的写时复制Copy-on-Write机制实现超高速的子进程克隆还依赖/proc/pid/maps文件来精确获取目标进程的内存布局。这些在原生 Linux 上是开箱即用的稳定接口。而在 macOS 上ptrace的权限模型完全不同fork()的行为也有细微差别AFL 的afl-forkserver组件负责管理 fuzz 子进程经常启动失败或行为异常。WSL 虽然好很多但它本质是 Windows 内核上的兼容层ptrace性能损耗大fork()的写时复制效率远低于原生 Linux导致 fuzz 速度下降 30%-50%且afl-showmap等调试工具偶尔会读取不到完整的内存映射信息。我实测过一个简单的base64解码器在原生 Ubuntu 22.04 上平均 1200 exec/sec在 WSL2 上只有 750 exec/sec而在 macOS 上afl-fuzz启动后几秒就报Fork server crashed。所以我的建议非常明确如果你是第一次接触 AFL务必使用原生的 Ubuntu 20.04 或 22.04 LTS 系统。不要图省事用虚拟机VMware/VirtualBox跑一个最小化安装的 Ubuntu Server。原因很简单AFL 对 CPU 和内存非常敏感。虚拟机增加了额外的指令翻译和内存虚拟化开销exec/sec数值会打七折。一个在物理机上能跑 1000 exec/sec 的程序在虚拟机里可能只有 300-400。而 fuzz 效率直接决定了你能在单位时间内探索多少路径。时间就是路径路径就是漏洞概率。因此我推荐的最低配置是一台闲置的旧笔记本或者一块单独的 SSD直接装双系统。物理机上的 Ubuntu Server是 AFL 最舒适、最高效的工作环境。2.2 从源码编译 AFL绕过包管理器的“黑盒”陷阱Ubuntu 的apt install afl看似方便但它安装的是 Debian 官方仓库打包的版本通常滞后于 AFL 官方 GitHub 的最新 release。更重要的是包管理器安装的 AFL其afl-gcc和afl-clang工具链往往链接的是系统默认的gcc和clang而这些编译器版本可能与 AFL 插桩代码存在兼容性问题。我遇到过最典型的情况是apt install afl后afl-gcc test.c -o test编译成功但afl-fuzz一运行就 segmentation fault。查日志发现是 AFL 的插桩代码试图访问一个在新版gcc中已被废弃的寄存器上下文结构。因此我坚持从官方源码编译。这多花不了 5 分钟却能规避 95% 的“安装即失败”问题。步骤如下# 1. 更新系统并安装编译依赖 sudo apt update sudo apt upgrade -y sudo apt install -y build-essential llvm-dev libclang-dev cmake python3-dev # 2. 克隆官方仓库注意不是 fork是 lcamtuf 的原 repo git clone https://github.com/AFLplusplus/AFLplusplus.git cd AFLplusplus # 3. 编译关键指定 LLVM 版本确保兼容性 make clean make distrib # 这会编译所有组件包括 afl-gcc, afl-clang, afl-fuzz 等提示make distrib是 AFL 的推荐编译方式它会自动检测系统环境并选择最优的编译选项。它比老版 AFL 的make all更健壮尤其对clang的支持更完善。编译完成后所有可执行文件都在当前目录下无需make install。你可以直接./afl-fuzz运行或者将当前目录加入PATH。2.3 验证安装不只是afl-fuzz -h而是看它是否“活”了很多人验证安装就是敲afl-fuzz -h看到帮助信息就认为成功了。这远远不够。真正的验证是让它和一个最简单的、已知会崩溃的程序“对话”。我们不用hello world而用 AFL 自带的经典测试用例test-instr.c// test-instr.c #include stdio.h #include stdlib.h #include string.h int main(int argc, char** argv) { if (argc ! 2) return 1; char* input argv[1]; // 一个经典的栈溢出点无长度检查的 strcpy char buf[16]; strcpy(buf, input); // 当 input 长度 15 时必然溢出 printf(Input length: %zu\n, strlen(input)); return 0; }编译并验证# 用 afl-gcc 编译这是关键 ./afl-gcc test-instr.c -o test-instr # 创建输入语料库 mkdir in out echo hello in/seed # 运行 fuzz加 -d 参数跳过 dumb mode更快看到结果 ./afl-fuzz -i in -o out -d -- ./test-instr 如果一切正常几秒钟内out/fuzzer_stats文件就会开始更新out/crashes/目录下会出现以id:000000,sig:11,src:000000,time:...命名的崩溃文件。用cat out/crashes/id:000000,sig:11,src:000000,time:...查看内容你应该能看到一串超长的A字符AAAAAAAAAAAA...。这就是 AFL 找到的导致strcpy溢出的输入。注意是 AFL 的占位符表示将生成的测试用例作为命令行参数传给目标程序。./test-instr 等价于./test-instr generated_input。如果这里报错No instrumentation detected说明afl-gcc没生效回去检查编译步骤。这一步验证确认了 AFL 的核心工作流插桩编译 → 输入种子 → 反馈驱动变异 → 崩溃捕获。它不是一个静态工具而是一个动态的、与目标程序共生的“生命体”。只有亲眼看到它产出第一个crash你才算真正把它“唤醒”了。3. 目标程序准备与插桩编译让 C 程序“开口说话”3.1 选靶心什么样的 C 程序才值得 AFL 去 fuzz网上很多教程拿ls、cat甚至gcc本身当靶子这完全是误导。AFL 的设计初衷是 fuzz那些接受外部输入、并对其进行复杂解析的程序。ls的输入是文件路径它几乎不做任何“解析”只是调用stat()系统调用cat的输入是文件内容但它只是原样输出没有状态机、没有语法树、没有复杂的条件分支。AFL 对这类程序的 fuzz99% 的时间都在生成无效路径比如cat /dev/null/xxx无法触发深层逻辑。真正的好靶子必须满足三个硬性条件有明确的输入源最好是命令行参数argv[1]、标准输入stdin或文件读取fread()/fscanf()。网络 socket 输入虽然可行但需要额外的afl-net支持对新手过于复杂。有丰富的控制流程序内部必须有大量的if/else、switch/case、for/while循环。这些结构是 AFL 路径探索的“路标”。一个只有线性执行的程序AFL 几乎无法获得任何反馈。有潜在的不安全操作比如strcpy、strcat、sprintf、memcpy未校验长度、malloc未检查返回值、atoi未处理异常输入等。这些是漏洞的温床也是 AFL 最容易触发崩溃的地方。基于此我为你准备了一个“教学级”靶子一个极简的 Base64 解码器。它完美符合以上三点// base64_decode.c #include stdio.h #include stdlib.h #include string.h #include ctype.h // Base64 字符表 static const char b64_table[65] ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/; // 查找字符在表中的索引 static int b64_idx(char c) { for (int i 0; i 64; i) { if (b64_table[i] c) return i; } return -1; // 无效字符 } // 解码函数无错误检查故意留坑 void decode_base64(const char* input, char* output) { int len strlen(input); int out_len 0; // Base64 编码要求长度是 4 的倍数这里不做校验 for (int i 0; i len; i 4) { // 提取 4 个字符 char c0 input[i], c1 input[i1], c2 input[i2], c3 input[i3]; // 查找索引这里会崩溃如果 i3 超出字符串长度 int idx0 b64_idx(c0), idx1 b64_idx(c1), idx2 b64_idx(c2), idx3 b64_idx(c3); // 组合 4 个 6-bit 为 3 个 8-bit 字节 output[out_len] (idx0 2) | (idx1 4); output[out_len] ((idx1 0xf) 4) | (idx2 2); output[out_len] ((idx2 0x3) 6) | idx3; } output[out_len] \0; } int main(int argc, char** argv) { if (argc ! 2) { fprintf(stderr, Usage: %s base64_string\n, argv[0]); return 1; } char* input argv[1]; // 分配足够大的输出缓冲区但这里没做长度校验 char* output malloc(strlen(input) * 3 / 4 1); if (!output) { perror(malloc); return 1; } decode_base64(input, output); printf(Decoded: %s\n, output); free(output); return 0; }这个程序的“坑”非常明显decode_base64函数里input[i3]的访问完全没有边界检查。当 AFL 生成一个长度不是 4 的倍数的输入比如ABi0时i33就越界了直接触发SIGSEGV。同时malloc的大小计算strlen(input) * 3 / 4 1在input极长时可能导致整数溢出分配一个极小的内存后续output[out_len]就会写到堆外。这就是 AFL 的“猎场”。3.2 插桩编译afl-gcc与afl-clang的抉择编译这个靶子你有两个选择afl-gcc和afl-clang。它们的核心区别在于插桩的粒度和性能。afl-gcc使用的是GCC 的__builtin_trap()插桩。它会在每个基本块Basic Block的入口处插入一条trap指令。AFL 的 forkserver 通过捕获SIGTRAP信号来计数。这种方式兼容性最好几乎所有 GCC 版本都支持但性能开销略大每次进入基本块都要触发一次信号。afl-clang使用的是LLVM 的SanitizerCoverage插桩。它通过编译器前端在 IR 层面注入计数代码运行时直接修改内存中的计数器无需信号中断。性能比afl-gcc高 15%-20%且能提供更精细的覆盖率如边缘覆盖率 Edge Coverage但对clang版本有要求推荐clang-12或更高。对于初学者我强烈推荐afl-gcc。原因有二一是它足够稳定几乎不会出错二是它的“慢”恰恰让你更容易观察 AFL 的工作节奏。当你看到execs/sec在 500-800 之间波动时你就知道AFL 正在稳健地工作。而afl-clang动辄 1200 exec/sec新手反而会怀疑“它是不是卡住了怎么没 crash”因为太快了反馈不直观。编译命令# 使用 afl-gcc 编译-O3 优化是必须的 ./afl-gcc -O3 -g -o base64_decode base64_decode.c # 验证插桩是否成功检查二进制里是否有 afl 的符号 nm base64_decode | grep __afl # 应该能看到类似 __afl_area_ptr, __afl_prev_loc 等符号注意-O3优化至关重要。没有优化GCC 会生成大量冗余的、无意义的基本块比如为每一个局部变量赋值都生成一个块AFL 的覆盖率会被这些“噪音”淹没无法聚焦到真正的逻辑分支上。-g参数保留调试符号方便后续用gdb分析 crash。3.3 种子语料库Corpus不是随便扔几个文件而是构建“知识地图”-i in参数指定的输入目录绝不是放一个hello.txt就完事。这个目录里的每一个文件都是 AFL 的“起点”和“老师”。AFL 的初始变异全部基于这些种子文件。好的种子能让 AFL 在几分钟内就找到深层路径差的种子会让它在浅层循环里浪费数小时。构建种子库有三个黄金法则合法性优先种子必须是目标程序能成功解析的合法输入。对于我们的 Base64 解码器种子应该是有效的 Base64 字符串比如SGVsbG8Hello、V29ybGQWorld、MTIzNA1234。你可以用 Python 快速生成import base64 for s in [Hello, World, 1234, A, AA, AAA]: with open(fin/{s}.b64, w) as f: f.write(base64.b64encode(s.encode()).decode() \n)多样性覆盖种子要覆盖各种长度和结构。Base64 字符串长度必须是 4 的倍数所以种子应包含长度为 4、8、12、16 的字符串。同时要包含填充字符因为的位置会影响解码逻辑。最小化原则每个种子文件只包含一个输入且内容尽可能短。AFL 的变异算法bitflip, arith, interest, dictionary对短输入的变异效率最高。一个 100KB 的base64.txt文件不如 10 个 10 字节的.b64文件有效。最终你的in/目录应该像这样$ ls -l in/ -rw-r--r-- 1 user user 12 Jan 1 10:00 Hello.b64 -rw-r--r-- 1 user user 12 Jan 1 10:00 World.b64 -rw-r--r-- 1 user user 12 Jan 1 10:00 1234.b64 -rw-r--r-- 1 user user 8 Jan 1 10:00 A.b64 -rw-r--r-- 1 user user 8 Jan 1 10:00 AA.b64 -rw-r--r-- 1 user user 8 Jan 1 10:00 AAA.b64实操心得我曾经用一个 5MB 的 PNG 图片作为种子去 fuzz 一个 PNG 解析器结果 AFL 跑了 24 小时paths_found还是 1。后来换成 10 个不同尺寸、不同色深的 1KB PNG 小图30 分钟就找到了 47 条路径。种子的质量直接决定了 AFL 的“认知起点”。4. AFL 实战运行与核心参数详解读懂它的“心跳”4.1 启动 fuzz从afl-fuzz命令到终端界面的每一行现在万事俱备。让我们启动真正的 fuzz./afl-fuzz -i in -o out -t 5000 -m 100 -d -- ./base64_decode 这条命令的每一个参数都对应着 AFL 的一个核心决策-i in输入种子目录。AFL 会先读取这里的所有文件作为初始语料。-o out输出目录。AFL 会在这里创建queue/待测输入队列、crashes/崩溃用例、hangs/挂起用例、fuzzer_stats统计文件等子目录。-t 5000超时时间milliseconds。这是最关键的参数之一。它告诉 AFL如果目标程序执行超过 5 秒还没退出就强制kill -9。为什么需要它因为 AFL 的核心是“快速迭代”。一个挂起hang的程序会阻塞整个 fuzz 流程。-t就是给每个测试用例设的“倒计时”。对于 Base64 解码器5000ms5秒绰绰有余。但对于一个需要网络请求的程序你可能需要-t 20000。-m 100内存限制MB。AFL 会为每个 fuzz 子进程设置ulimit -v 100000100MB 虚拟内存。这是为了防止目标程序因内存泄漏或无限循环而耗尽系统资源。100MB 对大多数 CLI 工具足够了。如果程序本身很大比如一个数据库可以设为-m 500或-m none禁用限制不推荐。-d禁用 dumb mode。AFL 启动时默认会先用“dumb”模式即不使用反馈纯随机变异跑一小段时间以快速建立初始语料。但对于已知有插桩的程序这个阶段是多余的-d可以跳过让 AFL 立即进入高效的反馈驱动模式。--分隔符表示之后的参数是传递给目标程序的。./base64_decode 目标程序及其参数。是占位符AFL 会将其替换为当前测试用例的文件路径当使用文件输入时或直接作为命令行参数当使用时AFL 会将测试用例内容作为argv[1]传入。启动后你会看到一个彩色的 TUI文本用户界面afl-fuzz 3.10a by lcamtufgoogle.com [] Loaded 6 seeds from in. [*] No auto-generated dictionary tokens to reuse. [] Using exploration-based strategy. [] Using SHM test case delivery. [*] Target map size: 65536 bytes. [*] Attempting dry run with the initial seed... [] All test cases processed. [*] Creating hard links for all input files... [] Here are some useful stats: Started at : Sun Jan 1 10:00:00 2024 Last new path : 0 days, 0:00:05 Last unique crash : none Last unique hang : none Summary stats Fuzzers alive : 1 Total run time : 0 days, 0:00:05 Total execs : 1200 Cumulative speed : 240 execs/sec (avg) Pending paths : 6 favored, 0 variable Pending total path : 6 Saved crashes : 0 Saved hangs : 0这个界面就是 AFL 的“心跳监测仪”。你需要关注的不是上面的“Started at”而是下面的动态指标Total execs总共执行了多少次测试用例。这是衡量 fuzz 进度的绝对指标。Cumulative speed平均每秒执行次数。这是衡量 fuzz 效率的核心指标。如果它长期低于 100说明你的程序太重或者-t设得太小频繁超时。Pending paths还有多少路径等待被探索。6 favored表示有 6 个“优选”路径它们是 AFL 认为最有潜力产生新路径的种子。Saved crashes已保存的崩溃数量。一旦这个数字从0变成1你就成功了4.2fuzzer_stats文件AFL 的“日记本”读懂它的隐含信息AFL 的 TUI 界面是实时的但out/fuzzer_stats文件才是它的“永久日记”。它每秒更新一次记录着所有关键指标。打开它你会看到类似这样的内容start_time : 1704103200 last_update : 1704103260 fuzzer_pid : 12345 cycles_done : 0 execs_done : 1200 execs_per_sec : 240.00 paths_total : 6 paths_found : 6 paths_imported : 0 max_depth : 1 cur_path : 0 pending_favs : 6 pending_total : 6 stability : 100.00% havoc_cycles : 0其中stability稳定性这个指标常常被忽略但它极其重要。它表示在最近一段时间内AFL 发现的新路径占总执行次数的比例。100.00%意味着AFL 还在吃“新鲜饭”所有执行都在探索已知路径没有发现新路径。这通常是好事说明初始种子质量高AFL 还在消化。但如果stability长期比如 30 分钟保持在100.00%而paths_found不再增长那就意味着你的程序已经没有更多可探索的路径了或者 AFL 的变异策略碰到了瓶颈。这时你需要干预增加-x字典如果程序有特定的语法比如 Base64 只能用A-Z a-z 0-9 / 提供一个字典dict.txt让 AFL 的dictionary变异更精准。调整-p策略-p explore默认偏向探索新路径-p exploit则偏向在已知崩溃点附近深度挖掘。重启并增加-L-L 10000可以延长 AFL 的“学习期”让它花更多时间在初始种子上做更细致的变异。实操心得我曾 fuzz 一个 JSON 解析器stability卡在100.00%2 小时不动。后来发现是因为所有种子都是{}、[]这种极简结构。我手动添加了几个嵌套很深的 JSON如{a:{b:{c:1}}}stability瞬间降到95%paths_found开始飙升。AFL 不是 AI它需要你给它“提示”。4.3queue/目录AFL 的“进化树”理解它的筛选逻辑out/queue/目录下的每一个文件都是 AFL 认为“有价值”的输入。它们不是随机生成的而是经过严格筛选的“进化节点”。文件名格式为id:000000,orig:Hello.b64其中id:000000这是该输入在 AFL 内部的唯一 ID。orig:Hello.b64表示这个输入是从种子Hello.b64变异而来的。AFL 的筛选逻辑是一个新输入只有在它触发了程序之前从未走过的代码路径时才会被放入queue/。这就是“路径导向”的核心。queue/里的文件就是 AFL 探索出的“新大陆”的坐标。你可以用afl-showmap工具查看任意一个queue文件触发了哪些路径./afl-showmap -o .test_map -- ./base64_decode SGVsbG8 cat .test_map | wc -l # 输出 12表示触发了 12 个基本块 ./afl-showmap -o .test_map -- ./base64_decode SGVsbG8K cat .test_map | wc -l # 输出 15多了 3 个说明这是一个“新路径”queue/目录的结构就是 AFL 的“进化树”。根节点是你的种子枝叶是各种变异bitflip、arith、splice。AFL 会不断从queue/中挑选“最有前途”的节点favored对其进行更激进的变异以期找到更深的路径。理解这一点你就明白为什么 AFL 的queue/目录会越来越大——它不是在堆积垃圾而是在构建一张越来越精细的程序行为地图。5. 常见错误排查与独家避坑指南那些文档里不会写的真相5.1 错误No instrumentation detected插桩失败的 5 种真实原因这是新手遇到的第一座大山。AFL 报这个错意思是它在目标二进制里找不到自己插进去的“探针”。原因绝不仅仅是“没用afl-gcc编译”。现象真实原因解决方案afl-gcc test.c -o test后报错test.c里包含了#include sys/...等系统头文件而afl-gcc的头文件路径没配全编译时显式指定头文件路径./afl-gcc -I/usr/include -I/usr/include/x86_64-linux-gnu test.c -o testafl-clang编译后报错系统clang版本过低10不支持 AFL 的插桩 APIsudo apt install clang-12然后用CCclang-12 ./afl-clang ...afl-fuzz启动时报错目标程序是setuid或setgid的如/bin/pingLinux 内核禁止ptrace跟踪sudo setcap cap_sys_ptraceep /path/to/afl-fuzz或用sudo运行 AFLafl-fuzz运行几秒后报错目标程序启动时stdout/stderr被重定向或关闭AFL 的 forkserver 无法通信在目标程序开头加freopen(/dev/tty, w, stdout); freopen(/dev/tty, w, stderr);afl-fuzz在dumb mode阶段报错dumb mode下 AFL 不依赖插桩但会尝试fork()如果系统ulimit -u最大进程数太小会失败ulimit -u 65535独家技巧最快速的诊断法是用readelf -S ./target_binary | grep afl。如果输出里有.afl_area、.afl_prev_loc等 section说明插桩成功。如果没有那就是编译环节出了问题和 AFL 本身无关。5.2 错误Fork server crashed进程控制失效的底层根源这个错误意味着 AFL 的forkserver子进程启动失败。forkserver是 AFL 的心脏它负责高效地fork()出无数个目标程序的副本。它崩溃整个 fuzz 就瘫痪。最常见的原因是目标程序在main()之前就崩溃了。比如全局构造函数里调用了malloc而malloc初始化失败或者LD_PRELOAD加载了一个有问题的 so 库。AFL 的forkserver是在main()之前启动的它需要目标程序能顺利走到main()。诊断方法# 让 AFL 用 gdb 启动目标看在哪崩溃 ./afl-fuzz -d -g -o out -i in -- ./base64_decode # 这会启动 gdb你输入 run就能

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑