7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞

下载文件

漏洞描述

tcpdump 是 Linux 上一个强大的网络数据采集分析工具,其 4.9.0 版本的 sliplink_print 函数(位于 print-sl.c)中存在一个栈溢出漏洞,原因是程序在进行内存存取的操作前未对一些值做判断,导致操作了非法的内存地址。攻击者可以利用这个漏洞触发拒绝服务,甚至任意代码执行。

这个漏洞是发现者用 AFL 做 fuzz 时发现的。

漏洞复现

推荐使用的环境
备注

操作系统

Ubuntu 16.04

体系结构:32 位

调试器

gdb-peda

版本号:7.11.1

漏洞软件

tcpdump

版本号:4.9.0

为了编译 tcpdump,我们需要安装 dev 版本的 libpcap:

$ sudo apt-get install libpcap-dev
$ dpkg -l libpcap-dev
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name                Version        Architecture   Description
+++-===================-==============-==============-============================================
ii  libpcap-dev         1.7.4-2        all            development library for libpcap (transitiona

下载安装有漏洞的 tcpdump 4.9.0:

执行 configure 会生成相应的 Makefile,然后 make install 就可以了,但是这里我们修改下 Makefile,给 gcc 加上参数 -fsanitize=address,以开启内存检测功能:

最后:

使用下面的 poc 即可成功地触发漏洞产生 Segment Fault:

漏洞分析

首先介绍一下 pcap 包的文件格式,文件头是这样一个结构体,总共 24 个字节:

  • magic:标识位:4 字节,这个标识位的值是 16 进制的 0xa1b2c3d4

  • major:主版本号:2 字节,默认值为 0x2

  • minor:副版本号:2 字节,默认值为 0x04

  • thiszone:区域时间:4 字节,实际上并未使用,因此被设置为 0

  • sigfigs:精确时间戳:4 字节,实际上并未使用,因此被设置为 0

  • snaplen:数据包最大长度:4 字节,该值设置所抓获的数据包的最大长度

  • linktype:链路层类型:4 字节,数据包的链路层包头决定了链路层的类型

接下来是数据包头,总共 16 个字节:

  • ts:时间戳:8 字节,4字节表示秒数,4字节表示微秒数

  • caplen:当前数据区长度:4 字节,表示所抓获的数据包保存在 pcap 文件中的实际长度

  • len:离线数据长度:4 字节,如果文件中保存的不是完整数据包,可能比 caplen 大

我们从 tcpdump 的测试集中找到这样一个测试用例,整个包是这样的:

所以其链路层类型为 08,即 SLIP(Serial Line Internet Protocol)。通常一个 SLIP 的包结构如下:

  • direction 字段指示发送或接收

    • 0:表示本机接收的包

    • 1:表示本机发送的包

在这里 direction 是 0xe7,并且由于 packet type 被设置了,所以 payload 是一个压缩的 TCP/IP 包,它的 packet type 和 compression information 共同构成了压缩的 TCP/IP 数据报,其结构如下:

sliplink_print 函数处下断点:

参数 p=0xb65ba800 位置处存放着从 pcap 中解析出来的 data,总共 39 个字节。

然后语句 dir = p[SLX_DIR] 从 data 中取出第一个字节作为 dir,即 0xe7

然后程序将 dir==0xe7SLIPDIR_IN==0 作比较,肯定不相等,于是错误地把 dir 当成 SLIPDIR_OUT==1 处理了:

继续往下执行,终于在执行到语句 lastlen[dir][lastconn] = length - (hlen << 2); 的时候挂掉了,它访问了一个不合法的地址:

说一下 compressed_sl_print 的参数:

  • dir=0xe7 是 direction

  • length=0xe7e7e726 是长度,由包头的 len 计算得到

  • ip=0xb65ba810 指向 data

  • chdr=0xb65ba801 指向压缩的 TCP/IP 头

  • ndo=0xbfffdb90 是其他一些选项

lastlen[dir][lastconn] = length - (hlen << 2); 语句中:

  • lastlen:被定义为 static u_int lastlen[2][256];

  • hlen 是未压缩的 TCP/IP 头的长度

  • length - hlen 是 data 的总数

于是这里传入的 dir==0xe7,超出了 lastlen 定义的范围,发生错误。

回溯一下栈调用情况:

问题发生的原因是 sliplink_print 函数的 ND_PRINT((ndo, dir == SLIPDIR_IN ? "I " : "O ")); 没有考虑到 dir 既不是 0 也不是 1 的情况,错误地把它当做一个发送的数据包处理,然后调用了 compressed_sl_print 函数,导致非法内存地址访问。

漏洞程序代码如下:

漏洞修复

在最新的 tcpdump 中已经修复了该漏洞,当发现 direction 是错误的值时,直接返回:

具体代码的修改如下所示,文件 print-sl.c 用于打印 CSLIP(Compressed Serial Line Internet Protocol),即压缩的 SLIP:

commit:CVE-2017-11543/Make sure the SLIP direction octet is valid.

参考资料

Last updated

Was this helpful?