AFL++实战其三

练习3 - TCPdump

版本:TCPdump 4.9.2

漏洞编号:CVE-2017-13028

CVE-2017-13028 是一个可通过 BOOTP 数据包(Bootstrap 协议)触发的边界外读取漏洞。

越界读取是指程序读取数据超过预期缓冲区的结束点或开始时发生的漏洞。

因此,它允许远程攻击者实施拒绝服务,或可能从进程内存中获取潜在敏感信息。


下载TCPdump

1
2
mkdir $HOME/fuzz_main/
cd $HOME/fuzz_main/

下载 tcpdump 4.9.2:

1
2
wget https://github.com/the-tcpdump-group/tcpdump/archive/refs/tags/tcpdump-4.9.2.tar.gz
tar -xzvf tcpdump-4.9.2.tar.gz

我们还需要下载 libpcap,这是其跨平台库:

1
2
wget https://github.com/the-tcpdump-group/libpcap/archive/refs/tags/libpcap-1.8.0.tar.gz
tar -xzvf libpcap-1.8.0.tar.gz

将 libpcap-libpcap-1.8.0 重命名为 libpcap-1.8.0,因为这样编译 tcpdump 时其才能找到库(有点傻):

1
mv libpcap-libpcap-1.8.0/ libpcap-1.8.0

ASan

Asan是一种适用与C/C++的快速内存错误检测器。

它由编译器仪器模块和运行时库组成。该工具能够发现堆、堆栈和全局对象的越界访问,以及“释放后使用”、“双重释放”和内存泄漏的漏洞。

通过在原先内存前后放置禁区来检查是否越界访问。


通过Asan构建你的源码

1
2
3
4
cd $HOME/fuzz_main/libpcap-1.8.0/
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzz_main/install/"
AFL_USE_ASAN=1 make
1
2
3
4
cd $HOME/fuzz_main/tcpdump-tcpdump-4.9.2/
AFL_USE_ASAN=1 CC=afl-clang-lto ./configure --prefix="$HOME/fuzz_main/install/" CFLAGS="-Wno-error=implicit-int"
AFL_USE_ASAN=1 make
AFL_USE_ASAN=1 make install

使用 afl-clang-lto 编译时如果不加 CFLAGS=”-Wno-error=implicit-int” 则会报64位输出匹配错误,加上就能正常通过了。


开始fuzz

语料库我们取用 tcpdump 项目中 tests 的示例文件即可:

1
afl-fuzz -m none -i $HOME/fuzz_main/tcpdump-tcpdump-4.9.2/tests/ -o ./out -s 123 -- $HOME/fuzz_main/install/sbin/tcpdump -vvvvXX -ee -nn -r @@

启用Asan需要大量内存,这里使用 -m none 选项表示不限制内存用量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  AFL ++4.41a {default} (/home/zlsf/fuzz_main/install/sbin/tcpdump) [explore]
┌─ process timing ────────────────────────────────────┬─ overall results ────┐
│ run time : 0 days, 7 hrs, 52 min, 56 sec │ cycles done : 0 │
│ last new find : 0 days, 0 hrs, 0 min, 24 sec │ corpus count : 11.2k │
│last saved crash : 0 days, 1 hrs, 41 min, 26 sec │saved crashes : 1 │
│ last saved hang : none seen yet │ saved hangs : 0 │
├─ cycle progress ─────────────────────┬─ map coverage┴──────────────────────┤
│ now processing : 10.9k.1 (98.0%) │ map density : 0.78% / 19.95% │
│ runs timed out : 0 (0.00%) │ count coverage : 3.26 bits/tuple │
├─ stage progress ─────────────────────┼─ findings in depth ─────────────────┤
│ now trying : inference │ favored items : 609 (5.45%) │
│ stage execs : 874/513 (170.37%) │ new edges on : 408 (3.65%) │
│ total execs : 6.35M │ total crashes : 3 (1 saved) │
exec speed : 0.00/sec (zzzz...) │ total tmouts : 11.1k (0 saved) │
├─ fuzzing strategy yields ────────────┴─────────────┬─ item geometry ───────┤
│ bit flips : 7/1.83M, 3/1.83M, 2/1.83M │ levels : 16 │
│ byte flips : 1/228k, 1/228k, 0/228k │ pending : 9385 │
│ arithmetics : 36/16.0M, 0/32.0M, 0/32.0M │ pend fav : 112 │
│ known ints : 4/2.06M, 4/8.68M, 0/12.8M │ own finds : 10.4k │
│ dictionary : 4/35.7M, 0/35.7M, 0/0, 0/0 │ imported : 0 │
│havoc/splice : 1177/1.07M, 0/0 │ stability : 100.00% │
│py/custom/rq : unused, unused, unused, unused ├───────────────────────┘
│ trim/eff : 14.53%/678k, 99.99% │ [cpu000: 75%]
└─ strategy: explore ────────── state: in progress ──┘^C

+++ Testing aborted by user +++

[!] Stopped during the first cycle, results may be incomplete.
(For info on resuming, see /usr/local/share/doc/afl/README.md)
[*] Writing ./out/default/fastresume.bin ...
[+] fastresume.bin successfully written with 9453487 bytes.
[+] We're done here. Have a nice day!

今天的 Fuzz 格外的漫长啊,花了我接近八个小时,只出现了一个独特错误。

不过经过验证调用链,这个崩溃正好是我们需要的CVE-2017-13028 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
==2586765==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5120000002d9 at pc 0x5555557d5b1c bp 0x7fffffffd9e0 sp 0x7fffffffd188
READ of size 4 at 0x5120000002d9 thread T0
#0 0x5555557d5b1b in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long) crtstuff.c
#1 0x5555557d5ff0 in memcmp (/home/zlsf/fuzz_main/install/sbin/tcpdump+0x281ff0) (BuildId: b2a754f7d67207d8)
#2 0x555555937998 in bootp_print /home/zlsf/fuzz_main/tcpdump-tcpdump-4.9.2/./print-bootp.c:382:6
#3 0x5555559e8a6d in ip_print_demux /home/zlsf/fuzz_main/tcpdump-tcpdump-4.9.2/./print-ip.c:402:3
#4 0x5555559f05e0 in ip_print /home/zlsf/fuzz_main/tcpdump-tcpdump-4.9.2/./print-ip.c:673:3
#5 0x55555598879d in ethertype_print /home/zlsf/fuzz_main/tcpdump-tcpdump-4.9.2/./print-ether.c:333:10
#6 0x555555986621 in ether_print /home/zlsf/fuzz_main/tcpdump-tcpdump-4.9.2/./print-ether.c:236:7
#7 0x5555558ac2ef in pretty_print_packet /home/zlsf/fuzz_main/tcpdump-tcpdump-4.9.2/./print.c:332:18
#8 0x5555558ac2ef in print_packet /home/zlsf/fuzz_main/tcpdump-tcpdump-4.9.2/./tcpdump.c:2497:2
#9 0x555555dcba7c in pcap_offline_read /home/zlsf/fuzz_main/libpcap-1.8.0/./savefile.c:507:4
#10 0x5555558a24ba in pcap_loop /home/zlsf/fuzz_main/libpcap-1.8.0/./pcap.c:875:8
#11 0x5555558a24ba in main /home/zlsf/fuzz_main/tcpdump-tcpdump-4.9.2/./tcpdump.c:2000:12
#12 0x7ffff762a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#13 0x7ffff762a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#14 0x5555557b93c4 in _start (/home/zlsf/fuzz_main/install/sbin/tcpdump+0x2653c4) (BuildId: b2a754f7d67207d8)

其中我们清晰的看见函数调用链经过了bootp_print这个函数。

源码分析

print-bootp.c:

1
2
3
4
5
6
7
8
9
10
11
12
void
bootp_print(netdissect_options *ndo,
register const u_char *cp, u_int length)
{
register const struct bootp *bp;
static const u_char vm_cmu[4] = VM_CMU;
static const u_char vm_rfc1048[4] = VM_RFC1048;
// ...
if (memcmp((const char *)bp->bp_vend, vm_rfc1048,
sizeof(uint32_t)) == 0)
rfc1048_print(ndo, bp->bp_vend); // print-bootp.c:382
// ...

这段逻辑是为了解析BOOTP包的厂商而设计的。

这里进行了内存比较,vm_rfc1048是固定4字节大小的特征值。但是bp->bp_vend是我们可控的,在我的崩溃文件输入下,它只有3个字节。多读取了一个字节。

但是这个漏洞我感觉远远达不到实施Dos或读取敏感信息的程度就是了。

修复

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 	if (memcmp((const char *)bp->bp_vend, vm_rfc1048,
sizeof(uint32_t)) == 0)
rfc1048_print(ndo, bp->bp_vend);
-- else if (memcmp((const char *)bp->bp_vend, vm_cmu,
sizeof(uint32_t)) == 0)
cmu_print(ndo, bp->bp_vend);

++ if (strnlen((const char *)bp->bp_vend, 4) == 4 && memcmp((const char *)bp->bp_vend, vm_rfc1048,
sizeof(uint32_t)) == 0)
rfc1048_print(ndo, bp->bp_vend);
++ else if (strnlen((const char *)bp->bp_vend, 4) == 4 && memcmp((const char *)bp->bp_vend, vm_cmu,
sizeof(uint32_t)) == 0)
cmu_print(ndo, bp->bp_vend);

官方的修复方法也是检查长度,不过长度不符合就是直接退出。四舍五入和我的修复差不多。


AFL++实战其三
https://zlsf-zl.github.io/2026/04/07/AFL-实战其三/
作者
ZLSF
发布于
2026年4月7日
许可协议