2577 words
13 minutes
CVE-2017-13028: TCPdump
2026-03-02
2026-03-05

CVE-2017-13028#

Description#

Compile#

Download#

Terminal window
git clone https://github.com/the-tcpdump-group/tcpdump.git && cd tcpdump
git checkout tcpdump-4.9.2

阅读 README 我们可知,要编译 TCPdump 需要先编译 libpcap 。由于 TCPdump-4.9.2 的最后一次提交是九年前的,因此对应的最匹配的 libpcap 应该是十年前的 1.8.1 版本。

Terminal window
git clone https://github.com/the-tcpdump-group/libpcap.git && cd libpcap
git checkout libpcap-1.8.1

Build#

根据 chall 的说明,我们需要开启 ASAN 来 fuzz,故配置的时候需要加入 AFL_USE_ASAN=1 变量。

Terminal window
CC=clang CXX=clang++ CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-pointer" CXXFLAGS="$CFLAGS" ./configure --enable-shared=no --prefix="$(realpath ../workshop/libpcap-debug)" --disable-bluetooth --disable-dbus
make -j`nproc` && make install
make clean
AFL_USE_ASAN=1 CC=afl-clang-lto CXX=afl-clang-lto++ ./configure --enable-shared=no --prefix="$(realpath ../workshop/libpcap-fuzz)" --disable-bluetooth --disable-dbus
AFL_USE_ASAN=1 make -j`nproc` && AFL_USE_ASAN=1 make install

接下来编译 tcpdump,遇到如下报错:

原因是它忽略了我们传入的 LDFLAGSCPPFLAGS,强制要求 libpcap 的目录和 tcpdump 在同级,且名字固定为 libpcap 。解决方法是使用 --with-system-libpcap 参数。虽然我们的 libpcap 是安装到自定义路径,而非系统级安装的,但是用了这个参数后,如果系统目录下没找到 libpcap,它就会去我们传入的环境变量里找。

然后又遇到了新的问题:

我们可以通过 config.log 查看详细报错信息:

可见报错原因是 ISO C99 之后不再支持隐式声明导致的。解决方法是通过 -Wno-error=implicit-int 告诉编译器将 implicit-int 的错误当成 warning 处理,而非 error 。

Terminal window
CC=clang \
CXX=clang++ \
CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-pointer \
-Wno-error=implicit-int" \
CXXFLAGS="$CFLAGS" \
LDFLAGS="-L$(realpath ../workshop/libpcap-debug/lib)" \
CPPFLAGS="-I$(realpath ../workshop/libpcap-debug/include)" \
./configure \
--prefix="$(realpath ../workshop/tcpdump-debug)" \
--with-system-libpcap

现在 configure 阶段是通过了,make 一下,又是一堆报错:

大概翻了一下,错误种类还不少,一个个解决吧。

首先是部分文件报 incomplete element type 'const struct tok'。这是因为编译器只看到了声明没有看到定义,不知道这个结构体的具体成员有什么,我们只需要在每个报这样错误的文件中加入完整定义即可。

先 grep 一下,可知这个 struct tok 定义在 netdissect.h 中:

那我们直接在报错的文件 l2vpn.h 中加入 #include "netdissect.h" 就好了。

接下来是有些文件报 unknown type name 'uint32_t' 这种错,直接在报错的那个文件里加入 #include <stdint.h> 即可。

对于 incomplete type 'struct in6_addr',我们只要导入 #include <netinet/in.h> 就好了。

然后是 redefinition of 'UNALIGNED' with a different type: 'struct ip6_ext' vs 'struct ip6_hdr' 这种重定义,先 grep 看看 UNALIGNED 是个啥:

可见它就是一个结构体属性宏,同时默认声明为 #define UNALIGNED __attribute__((packed)),既然是重定义,解决方法也很简单,我们在对应的文件顶部加入如下代码即可:

#ifndef UNALIGNED
#define UNALIGNED __attribute__((packed))
#endif

之后 unknown type name 'netdissect_options' 也是一样,找定义它的头文件,然后在缺失的头文件中导入即可,依然是 #include "netdissect.h"

这样一路 patch 下来,就解决的差不多了,直接编译;

Terminal window
make clean && make -j`nproc` && make install

至于这个奇怪的 libpcap 1.9.0,是因为它 VERSION 文件里写的 1.9.0,但 release tag 打的确实是 1.8.1,不重要。

总结:修这种头文件风暴,有的时候就像打地鼠一样,修好一个蹦出来 19 个都是常有的事……边修边骂好吧(

然后编译一份用来 fuzz 的:

Terminal window
make clean
AFL_USE_ASAN=1 \
CC=afl-clang-lto \
CXX=afl-clang-lto++ \
CFLAGS="-Wno-error=implicit-int" \
CXXFLAGS="$CFLAGS" \
LDFLAGS="-L$(realpath ../workshop/libpcap-fuzz/lib)" \
CPPFLAGS="-I$(realpath ../workshop/libpcap-fuzz/include)" \
./configure \
--prefix="$(realpath ../workshop/tcpdump-fuzz)" \
--with-system-libpcap
AFL_USE_ASAN=1 make -j`nproc` && AFL_USE_ASAN=1 make install

Samples#

要准备报文样本不容易,也很容易。 不容易在,我们不可能自己去用它抓包弄点报文,也不确定 AI 能不能生成(应该可以),容易在,tcpdump 提供的测试用例里有各种各样的报文:

这里我直接用它提供的测试报文作为初始语料库。

此外,既然都是抓包工具,那大名鼎鼎的 wireshark 是不是也提供了这种测试报文?

确实有,不过由于 tcpdump 的测试报文库已经很丰富了,如果我 fuzz 不出来再去用 wireshark 的。

Fuzzing#

开始之前先确定一下怎么用,随便传一个测试数据包看看:

执行需要很长时间,所以我们 fuzz 的时候需要手动添加 -t 1000+ 将超时时间增加到一秒钟,+ 表示让 afl++ 根据平均时间动态缩放这个 timeout 值,但是始终保持在我们设定的上限之内,AFL_TMPDIR=/dev/shm 是为了保护我们的硬盘寿命,而 -m none 则是因为 ASAN 会占用大量内存,虽然有 OOM 的风险,但是建议开启。当然也有其它解决方案,比如:Notes for using ASAN with afl-fuzz

Terminal window
ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:symbolize=0" AFL_TMPDIR=/dev/shm afl-fuzz -i corpus -o outs -s 1337 -t 1000+ -m none -- ./tcpdump-fuzz/sbin/tcpdump -vvvvXX -ee -nn -r @@

fuzzer 开始工作,我们也该去打游戏了,让它在后台慢慢跑吧~

结果跑了半天毛都没出,受不了了,直接看官方题解,官方题解用的是 1.8.0,虽然 1.8.1 显然是更接近的版本,不懂啊,不懂,但是不妨碍我试试……也有可能版本号是以 VERSION 文件中定义的为准?或许是这个原因吧,不管了……

把之前的都删了重头来过,这里我只写编译 libpcap 1.8.0 的指令,其它的不变。

configure 后 make 依旧遇到很多报错,这次我不打算禁用 dbus 和 bluetooth 了,手动 patch 一下好了。

查看报错信息,发现都是因为不知道 pcap_tpcap_if_t 导致的,并且 grep 发现它们定义在同一个文件中,那我们在每一个报错的文件头加入 #include "pcap.h" 就行。

Terminal window
CC=clang CXX=clang++ CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-pointer" CXXFLAGS="$CFLAGS" ./configure --enable-shared=no --prefix="$(realpath ../workshop/libpcap-debug)"
make -j`nproc` && make install
make clean
AFL_USE_ASAN=1 CC=afl-clang-lto CXX=afl-clang-lto++ ./configure --enable-shared=no --prefix="$(realpath ../workshop/libpcap-fuzz)"
AFL_USE_ASAN=1 make -j`nproc` && AFL_USE_ASAN=1 make install

编译完 libpcap 后开开心心去编译 tcpdump,本以为会很顺遂,然后一 make:

我还能说什么呢?还是得把 dbus 禁用啊,还得再重新编译一遍 libpcap,我……草……

还能咋办,加上 --disable-dbus 再来一遍呗!/骂骂咧咧

结果呢?结果我发现只关 dbus 不够,还要解决一下 libusb 和 canusb……

Terminal window
AFL_USE_ASAN=1 CC=afl-clang-lto CXX=afl-clang-lto++ ./configure --enable-shared=no --prefix="$(realpath ../workshop/libpcap-fuzz)" --disable-dbus --disable-usb --disable-canusb

现在总算是解决了,让它慢慢跑去吧~祈祷可以跑出点 crashes 来。

难过了,早上起来一看,11 个小时,毛都没出,然后又过了一个早八,依旧无果。非但无果,还又发现了一些新的路径……合着我 fuzz 一晚上一直在探路啊。

后来,我发现了俩个傻逼……

解决方法是除了 configure 的时候需要 AFL_USE_ASAN=1 外,make 的时候也要。

现在这样才算是编译进去了,之前不显示 Compiled with AddressSanitizer/CLang.……

然后继续挂机,看看这次能不能出点货来。

历经两个小时,终于出现 crash 了!

Analysis#

分析之前先把 debug 版本编译出来:

Terminal window
CC=clang \
CXX=clang++ \
CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-pointer \
-fsanitize=address" \
CXXFLAGS="$CFLAGS" \
./configure \
--enable-shared=no \
--prefix="$(realpath ../workshop/libpcap-debug)" \
--disable-dbus
--disable-usb \
--disable-canusb
CC=clang \
CXX=clang++ \
CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-pointer \
-Wno-error=implicit-int \
-fsanitize=address" \
CXXFLAGS="$CFLAGS" \
LDFLAGS="-L$(realpath ../workshop/libpcap-debug/lib)" \
CPPFLAGS="-I$(realpath ../workshop/libpcap-debug/include)" \
./configure \
--prefix="$(realpath ../workshop/tcpdump-debug)" \
--with-system-libpcap

然后看看第一个 crash:

很遗憾,并不是我们要复现的那个 CVE,继续挂机……

又是跑了一晚上加一个早八的时间,一晚上跑出来六百多个重复的 crashes,早上关掉去重后继续跑,跑出来十个:

但是用 gdb 这么一查,并没有我们要复现的那个 CVE 的 crash……

检测脚本如下:

#!/usr/bin/env bash
TARGET="./tcpdump-debug/sbin/tcpdump"
CRASH_DIR="./outs_v3/default/crashes"
OUTPUT_LOG="crash_reports.log"
>"$OUTPUT_LOG"
echo "[+] Analysing crashes in $CRASH_DIR..."
export ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:symbolize=1"
for crash_file in "$CRASH_DIR"/id:*; do
[ -e "$crash_file" ] || continue
echo "--------------------------------------------------" >>"$OUTPUT_LOG"
echo "File: $(basename "$crash_file")" >>"$OUTPUT_LOG"
gdb --batch --quiet \
--ex "run" \
--ex "bt" \
--ex "quit" \
--args "$TARGET" -vvvvXX -ee -nn -r "$crash_file" >>"$OUTPUT_LOG" 2>&1
echo -e "\n" >>"$OUTPUT_LOG"
done
echo "[+] Done! Result in: $OUTPUT_LOG"

有点烦,直接让 AI 再生成几个样本一起加进去:

#!/usr/bin/env python3
import os
import binascii
from scapy.all import *
# 加载扩展协议
load_contrib("bgp")
load_contrib("ospf")
# 创建语料目录
CORPUS_DIR = "corpus"
if not os.path.exists(CORPUS_DIR):
os.makedirs(CORPUS_DIR)
def save_pcap(name, pkts):
wrpcap(os.path.join(CORPUS_DIR, f"{name}.pcap"), pkts)
def generate_corpus():
print("正在生成初始语料...")
# 1. 基础以太网 + IPv4 + TCP (带有各种标志)
save_pcap("tcp_flags", [
Ether()/IP(dst="1.2.3.4")/TCP(dport=80, flags="S"),
Ether()/IP(dst="1.2.3.4")/TCP(dport=80, flags="PA"),
Ether()/IP(dst="1.2.3.4")/TCP(dport=80, flags="F"),
Ether()/IP(dst="1.2.3.4")/TCP(dport=80, flags="R"),
])
# 2. UDP + DNS 查询
save_pcap("dns_query", [
Ether()/IP(dst="8.8.8.8")/UDP(dport=53)/DNS(rd=1, qd=DNSQR(qname="www.google.com"))
])
# 3. ICMP (Ping, Unreachable)
save_pcap("icmp", [
Ether()/IP(dst="1.2.3.4")/ICMP(type=8), # Echo Request
Ether()/IP(dst="1.2.3.4")/ICMP(type=3, code=3), # Port Unreachable
])
# 4. IPv6 基础数据包
save_pcap("ipv6_base", [
Ether()/IPv6(dst="2001:4860:4860::8888")/TCP(dport=443),
Ether()/IPv6(dst="2001:4860:4860::8888")/ICMPv6EchoRequest(),
])
# 5. ARP 请求与应答
save_pcap("arp", [
Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.1"),
Ether()/ARP(op=2, psrc="192.168.1.1", hwsrc="00:11:22:33:44:55")
])
# 6. HTTP 请求 (简单应用层负载)
http_payload = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
save_pcap("http_get", [
Ether()/IP(dst="1.2.3.4")/TCP(dport=80, flags="PA")/Raw(load=http_payload)
])
# 7. 带有选项的 IP/TCP (测试解析器对选项的处理)
save_pcap("options", [
Ether()/IP(dst="1.2.3.4", options=[IPOption(b'\x83\x03\x10')])/TCP(dport=80),
Ether()/IP(dst="1.2.3.4")/TCP(dport=80, options=[('MSS', 1460), ('NOP', None), ('WScale', 7)])
])
# 8. 分片包 (Fragmentation)
payload = "A" * 100
pkts = fragment(IP(dst="1.2.3.4")/UDP(dport=123)/payload, fragsize=40)
save_pcap("fragments", pkts)
# 9. BOOTP & DHCP (丰富样本)
# 9.1 基础 BOOTP 请求与应答
save_pcap("bootp_base", [
Ether(dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0", dst="255.255.255.255")/UDP(sport=68, dport=67)/BOOTP(op=1, chaddr="00:11:22:33:44:55"),
Ether(src="00:11:22:33:44:55")/IP(src="192.168.1.1", dst="192.168.1.100")/UDP(sport=67, dport=68)/BOOTP(op=2, yiaddr="192.168.1.100", siaddr="192.168.1.1", chaddr="00:11:22:33:44:55")
])
# 9.2 DHCP 完整交互 (Discover, Offer, Request, Ack)
chaddr = "00:de:ad:be:ef:00"
transaction_id = 0x12345678
save_pcap("dhcp_full_flow", [
# Discover
Ether(dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0", dst="255.255.255.255")/UDP(sport=68, dport=67)/BOOTP(xid=transaction_id, chaddr=chaddr)/DHCP(options=[("message-type", "discover"), "end"]),
# Offer
Ether(dst=chaddr)/IP(src="192.168.1.1", dst="192.168.1.100")/UDP(sport=67, dport=68)/BOOTP(op=2, xid=transaction_id, yiaddr="192.168.1.100", siaddr="192.168.1.1", chaddr=chaddr)/DHCP(options=[("message-type", "offer"), ("server_id", "192.168.1.1"), ("lease_time", 86400), "end"]),
# Request
Ether(dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0", dst="255.255.255.255")/UDP(sport=68, dport=67)/BOOTP(xid=transaction_id, chaddr=chaddr)/DHCP(options=[("message-type", "request"), ("requested_addr", "192.168.1.100"), ("server_id", "192.168.1.1"), "end"]),
# Ack
Ether(dst=chaddr)/IP(src="192.168.1.1", dst="192.168.1.100")/UDP(sport=67, dport=68)/BOOTP(op=2, xid=transaction_id, yiaddr="192.168.1.100", siaddr="192.168.1.1", chaddr=chaddr)/DHCP(options=[("message-type", "ack"), ("server_id", "192.168.1.1"), ("lease_time", 86400), "end"])
])
# 9.3 带有复杂选项的 DHCP (测试解析器健壮性)
save_pcap("dhcp_options", [
Ether()/IP(src="0.0.0.0", dst="255.255.255.255")/UDP(sport=68, dport=67)/BOOTP(chaddr=chaddr)/DHCP(options=[
("message-type", "discover"),
("hostname", "fuzz-target-node"),
("param_req_list", [1, 3, 6, 12, 15, 28, 42]),
("vendor_class_id", b"MSFT 5.0"),
("client_id", b"\x01" + binascii.unhexlify(chaddr.replace(':',''))),
"end"
])
])
# 10. BGP (历史上漏洞极多)
# 模拟一个 BGP Keepalive 和 Open 消息
save_pcap("bgp", [
Ether()/IP(dst="1.1.1.1")/TCP(sport=179, dport=179)/BGPHeader(type=4), # Keepalive
Ether()/IP(dst="1.1.1.1")/TCP(sport=179, dport=179)/BGPHeader(type=1)/BGPOpen(my_as=65000, hold_time=180) # Open
])
# 11. SNMP (复杂编码,易出洞)
save_pcap("snmp", [
Ether()/IP(dst="1.2.3.4")/UDP(sport=161, dport=161)/SNMP(community="public", PDU=SNMPget(varbindlist=[SNMPvarbind(oid=ASN1_OID("1.3.6.1.2.1.1.1.0"))]))
])
# 12. OSPF (路由协议解析器很复杂)
save_pcap("ospf", [
Ether()/IP(dst="224.0.0.5")/OSPF_Hdr(type=1)/OSPF_Hello(router="1.1.1.1", mask="255.255.255.0")
])
# 13. 畸形数据包 (Fuzz 核心:故意破坏长度字段)
# 构造一个 IP 长度字段远大于实际数据的包
malformed_ip = IP(len=100, dst="1.2.3.4")/TCP(dport=80)
save_pcap("malformed_len", [Ether()/malformed_ip])
# 14. 802.1Q VLAN 嵌套
save_pcap("vlan", [
Ether()/Dot1Q(vlan=10)/Dot1Q(vlan=20)/IP()/TCP()
])
print(f"语料生成完成,保存在 {CORPUS_DIR} 目录下。")
if __name__ == "__main__":
generate_corpus()

并且我加入 AFL_LLVM_CMPLOG=1 编译了 CMPLOG 的版本,然后使用下面的指令重新 fuzz 。此外,这次用两个线程,我就不信这次还跑不出来/mad

Terminal window
mkdir -p /dev/shm/asan
mkdir -p /dev/shm/asan_cmplog
AFL_TMPDIR=/dev/shm/asan \
afl-fuzz -i clean_seeds_v4 \
-o outs_v4 \
-s 1337 \
-m none \
-M asan \
-- ./tcpdump-fuzz/sbin/tcpdump -vvvvXX -ee -nn -r @@
AFL_TMPDIR=/dev/shm/asan_cmplog \
afl-fuzz -i clean_seeds_v4 \
-o outs_v4 \
-m none \
-c ./tcpdump-fuzz-cmplog/sbin/tcpdump \
-S asan_cmplog \
-- ./tcpdump-fuzz-cmplog/sbin/tcpdump -vvvvXX -ee -nn -r @@

传下去,我放弃了。

哥们 fuzz 了三天出了一堆别的 CVE,就是没有能和 CVE-2017-13028 对上的……反正这个 chall 主要是教我们使用 ASAN 的,我们已经学会了,那就到此为止吧!

btw ASAN 的 backtrace 还是很帅的(

CVE-2017-13028: TCPdump
https://cubeyond.net/posts/fuzz/tcpdump-cve-2017-13028/
Author
CuB3y0nd
Published at
2026-03-02
License
CC BY-NC-SA 4.0