CVE-2017-11543

  • 影响程序:tcpdump 4.9.0

  • 漏洞类型:非法地址访问

  • 产生原因:忽略处理意外输入

漏洞分析

首先介绍pcap文件格式

img

文件头,总共 24 个字节:

1
2
3
4
5
6
7
8
9
struct pcap_file_header {
bpf_u_int32 magic;
u_short version_major;
u_short version_minor;
bpf_int32 thiszone; /* gmt to local correction */
bpf_u_int32 sigfigs; /* accuracy of timestamps */
bpf_u_int32 snaplen; /* max length saved portion of each pkt */
bpf_u_int32 linktype; /* data link type (LINKTYPE_*) */
};
  • magic:标识位:4 字节,这个标识位的值是 16 进制的 0xa1b2c3d4

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

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

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

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

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

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

数据包头,总共 16 个字节:

1
2
3
4
5
6
struct pcap_pkthdr {
time_t tv_sec; /* seconds (XXX should be time_t) */
bpf_u_int32 tv_usec; /* and microseconds */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
  • ts:时间戳:8 字节,4字节表示秒数,4字节表示微秒数
  • caplen:当前数据区长度:4 字节,表示所抓获的数据包保存在 pcap 文件中的实际长度
  • len:离线数据长度:4 字节

查看包结构 xxd -g1 slip-bad-direction.pcap

1
2
3
4
5
00000000: d4 c3 b2 a1 02 00 04 00 00 00 00 00 00 00 00 00  ................
00000010: 00 00 04 00 08 00 00 00 f6 b5 a5 58 f8 bd 07 00 ...........X....
00000020: 27 00 00 00 36 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 '...6...........
00000030: e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 ca 00 ................
00000040: 00 52 54 00 12 35 02 08 00 27 bd c8 2e 08 00 .RT..5...'.....

可见其链路层类型为 08,即 SLIP(Serial Line Internet Protocol) 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 |
. .
. .
. .

direction 字段指示发送或接收

  • 0:表示本机接收的包
  • 1:表示本机发送的包

在这里 direction 是 0xE7

漏洞程序代码

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
static void
sliplink_print(netdissect_options *ndo,
register const u_char *p, register const struct ip *ip,
register u_int length)
{
int dir;
u_int hlen;

dir = p[SLX_DIR];
ND_PRINT((ndo, dir == SLIPDIR_IN ? "I " : "O "));

if (ndo->ndo_nflag) {
/* XXX just dump the header */
register int i;

for (i = SLX_CHDR; i < SLX_CHDR + CHDR_LEN - 1; ++i)
ND_PRINT((ndo, "%02x.", p[i]));
ND_PRINT((ndo, "%02x: ", p[SLX_CHDR + CHDR_LEN - 1]));
return;
}
switch (p[SLX_CHDR] & 0xf0) {

case TYPE_IP:
ND_PRINT((ndo, "ip %d: ", length + SLIP_HDRLEN));
break;

case TYPE_UNCOMPRESSED_TCP:
/*
* The connection id is stored in the IP protocol field.
* Get it from the link layer since sl_uncompress_tcp()
* has restored the IP header copy to IPPROTO_TCP.
*/
lastconn = ((const struct ip *)&p[SLX_CHDR])->ip_p;
hlen = IP_HL(ip);
hlen += TH_OFF((const struct tcphdr *)&((const int *)ip)[hlen]);
lastlen[dir][lastconn] = length - (hlen << 2);
ND_PRINT((ndo, "utcp %d: ", lastconn));
break;

default:
if (p[SLX_CHDR] & TYPE_COMPRESSED_TCP) {
compressed_sl_print(ndo, &p[SLX_CHDR], ip,
length, dir);
ND_PRINT((ndo, ": "));
} else
ND_PRINT((ndo, "slip-%d!: ", p[SLX_CHDR]));
}
}

gdb调试, 在sliplink_print 函数处下断点,并运行

1
2
3
gdb ./tcpdump-tcpdump-4.9.0/tcpdump -q
b sliplink_print
r -e -r ./cves/slip-bad-direction.pcap
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
63
64
65
66
67
68
69
70
71
Breakpoint 1, sliplink_print (length=0xe7e7e726, ip=0x61d000000090, p=0x61d000000080 '\347' <repeats 22 times>, <incomplete sequence \312>, ndo=0x7fffffffcff0) at ./print-sl.c:133
133 dir = p[SLX_DIR];

[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x7
$rbx : 0x0061d000000080 → 0xe7e7e7e7e7e7e7e7
$rcx : 0x0
$rdx : 0x000000e7e7e736 → 0x0000000000000000
$rsp : 0x007fffffffcbf0 → 0xe7e7e72600000000
$rbp : 0x27
$rsi : 0x1
$rdi : 0x007fffffffcff4 → 0x0000000000000001
$rip : 0x0055555577d518 → <sl_if_print+312> mov rax, rbx
$r8 : 0x0
$r9 : 0x7bdf8
$r10 : 0x007ffff7949db2 → 0x0000000000010101
$r11 : 0x202
$r12 : 0x0061d000000090 → 0x00cae7e7e7e7e7e7
$r13 : 0x007fffffffd070 → 0x0061d0000000a7 → 0xbebebebebebebebe
$r14 : 0x007fffffffccc0 → 0xe7e7e73600000027 ("'"?)
$r15 : 0x007fffffffcff0 → 0x00000100000000 → 0x0000000000000000
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffcbf0│+0x0000: 0xe7e7e72600000000 ← $rsp
0x007fffffffcbf8│+0x0008: 0x0000000000000000
0x007fffffffcc00│+0x0010: 0x01df03ec038a01b9
0x007fffffffcc08│+0x0018: 0x0346017501130320
0x007fffffffcc10│+0x0020: 0x02de00ed008b02b8
0x007fffffffcc18│+0x0028: 0x007fffffffcff0 → 0x00000100000000 → 0x0000000000000000
0x007fffffffcc20│+0x0030: 0x007fffffffccb0 → 0x0000000058a5b5f6
0x007fffffffcc28│+0x0038: 0x0061d000000080 → 0xe7e7e7e7e7e7e7e7
────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x55555577d509 <sl_if_print+297> call QWORD PTR [r15+0x98]
0x55555577d510 <sl_if_print+304> jmp 0x55555577d69f <sl_if_print+703>
0x55555577d515 <sl_if_print+309> nop DWORD PTR [rax]
→ 0x55555577d518 <sl_if_print+312> mov rax, rbx
0x55555577d51b <sl_if_print+315> mov rcx, rbx
0x55555577d51e <sl_if_print+318> shr rax, 0x3
0x55555577d522 <sl_if_print+322> and ecx, 0x7
0x55555577d525 <sl_if_print+325> movzx eax, BYTE PTR [rax+0x7fff8000]
0x55555577d52c <sl_if_print+332> cmp al, cl
────────────────────────────────────────────────────────────────────────────────────────────────── source:./print-sl.c+133 ────
128 register u_int length)
129 {
130 int dir;
131 u_int hlen;
132
→ 133 dir = p[SLX_DIR];
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;
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x55555577d518 → sliplink_print(length=0xe7e7e726, ip=0x61d000000090, p=0x61d000000080 '\347' <repeats 22 times>, <incomplete sequence \312>, ndo=0x7fffffffcff0)
[#1] 0x55555577d518 → sl_if_print(ndo=0x7fffffffcff0, h=<optimized out>, p=0x61d000000080 '\347' <repeats 22 times>, <incomplete sequence \312>)
[#2] 0x555555689b29 → pretty_print_packet(ndo=0x7fffffffcff0, h=0x7fffffffccb0, sp=0x61d000000080 '\347' <repeats 22 times>, <incomplete sequence \312>, packets_captured=<optimized out>)
[#3] 0x55555567faaf → print_packet(user=<optimized out>, h=<optimized out>, sp=<optimized out>)
[#4] 0x7ffff7f7f90a → cmp ebp, r13d
[#5] 0x7ffff7f6acef → pcap_loop()
[#6] 0x55555567a073 → main(argc=<optimized out>, argv=<optimized out>)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

gef➤ x/39b 0x0061d000000080
0x61d000000080: 0xe7 0xe7 0xe7 0xe7 0xe7 0xe7 0xe7 0xe7
0x61d000000088: 0xe7 0xe7 0xe7 0xe7 0xe7 0xe7 0xe7 0xe7
0x61d000000090: 0xe7 0xe7 0xe7 0xe7 0xe7 0xe7 0xca 0x0
0x61d000000098: 0x0 0x52 0x54 0x0 0x12 0x35 0x2 0x8

可见地址0x61d000000080存放着从pcap文件中读取出的39个Byte

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
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0
$rbx : 0x0061d000000080 → 0xe7e7e7e7e7e7e7e7
$rcx : 0x0
$rdx : 0x000000e7e7e736 → 0x0000000000000000
$rsp : 0x007fffffffcbf0 → 0xe7e7e72600000000
$rbp : 0x27
$rsi : 0x1
$rdi : 0x007fffffffcff4 → 0x0000000000000001
$rip : 0x0055555577d543 → <sl_if_print+355> mov rax, r13
$r8 : 0x0
$r9 : 0x7bdf8
$r10 : 0x007ffff7949db2 → 0x0000000000010101
$r11 : 0x202
$r12 : 0x0061d000000090 → 0x00cae7e7e7e7e7e7
$r13 : 0x007fffffffd088 → 0x005555556894a0 → <ndo_printf+0> push r13
$r14 : 0xe7
$r15 : 0x007fffffffcff0 → 0x00000100000000 → 0x0000000000000000
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffcbf0│+0x0000: 0xe7e7e72600000000 ← $rsp
0x007fffffffcbf8│+0x0008: 0x0000000000000000
0x007fffffffcc00│+0x0010: 0x01df03ec038a01b9
0x007fffffffcc08│+0x0018: 0x0346017501130320
0x007fffffffcc10│+0x0020: 0x02de00ed008b02b8
0x007fffffffcc18│+0x0028: 0x007fffffffcff0 → 0x00000100000000 → 0x0000000000000000
0x007fffffffcc20│+0x0030: 0x007fffffffccb0 → 0x0000000058a5b5f6
0x007fffffffcc28│+0x0038: 0x0061d000000080 → 0xe7e7e7e7e7e7e7e7
────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x55555577d532 <sl_if_print+338> jne 0x55555577d997 <sl_if_print+1463>
0x55555577d538 <sl_if_print+344> lea r13, [r15+0x98]
0x55555577d53f <sl_if_print+351> movzx r14d, BYTE PTR [rbx]
→ 0x55555577d543 <sl_if_print+355> mov rax, r13
0x55555577d546 <sl_if_print+358> shr rax, 0x3
0x55555577d54a <sl_if_print+362> cmp BYTE PTR [rax+0x7fff8000], 0x0
0x55555577d551 <sl_if_print+369> jne 0x55555577dfb3 <sl_if_print+3027>
0x55555577d557 <sl_if_print+375> test r14d, r14d
0x55555577d55a <sl_if_print+378> lea rax, [rip+0xc76bf] # 0x555555844c20
────────────────────────────────────────────────────────────────────────────────────────────────── source:./print-sl.c+134 ────
129 {
130 int dir;
131 u_int hlen;
132
133 dir = p[SLX_DIR];
→ 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

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

然后程序将 dir==0xe7 与 SLIPDIR_IN==0 作比较,不相等,于是错误地把 dir 当成 SLIPDIR_OUT==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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0xe7e7
$rbx : 0x0061d000000080 → 0xe7e7e7e7e7e7e7e7
$rcx : 0x7
$rdx : 0x00555555ac6d20 → 0x0000000000000000
$rsp : 0x007fffffffcbf0 → 0xe7e7e72600000000
$rbp : 0x27
$rsi : 0x000000e7e7e6de → 0x0000000000000000
$rdi : 0x555555b00cbc
$rip : 0x0055555577d931 → <sl_if_print+1361> mov DWORD PTR [rdx+rax*4], esi
$r8 : 0xe7
$r9 : 0x0061d000000081 → 0xe7e7e7e7e7e7e7e7
$r10 : 0x0061d000000089 → 0xe7e7e7e7e7e7e7e7
$r11 : 0x0
$r12 : 0x0061d000000090 → 0x00cae7e7e7e7e7e7
$r13 : 0x007fffffffd088 → 0x005555556894a0 → <ndo_printf+0> push r13
$r14 : 0x000ffffffffa11 → 0x0000000000000000
$r15 : 0x007fffffffcff0 → 0x00000100000000 → 0x0000000000000000
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffcbf0│+0x0000: 0xe7e7e72600000000 ← $rsp
0x007fffffffcbf8│+0x0008: 0x0061d000000089 → 0xe7e7e7e7e7e7e7e7
0x007fffffffcc00│+0x0010: 0x0061d000000081 → 0xe7e7e7e7e7e7e7e7
0x007fffffffcc08│+0x0018: 0x0061d000000081 → 0xe7e7e7e7e7e7e7e7
0x007fffffffcc10│+0x0020: 0x02de00ed008b02b8
0x007fffffffcc18│+0x0028: 0x007fffffffcff0 → 0x00000100000000 → 0x0000000000000000
0x007fffffffcc20│+0x0030: 0x007fffffffccb0 → 0x0000000058a5b5f6
0x007fffffffcc28│+0x0038: 0x0061d000000080 → 0xe7e7e7e7e7e7e7e7
────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x55555577d922 <sl_if_print+1346> shr r14, 0x3
0x55555577d926 <sl_if_print+1350> add rax, r8
0x55555577d929 <sl_if_print+1353> cmp BYTE PTR [r14+0x7fff8000], 0x0
→ 0x55555577d931 <sl_if_print+1361> mov DWORD PTR [rdx+rax*4], esi
0x55555577d934 <sl_if_print+1364> jne 0x55555577e016 <sl_if_print+3126>
0x55555577d93a <sl_if_print+1370> mov rcx, r10
────────────────────────────────────────────────────────────────────────────────────────────────── source:./print-sl.c+253 ────
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 }

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
gef➤  c
Continuing.
AddressSanitizer:DEADLYSIGNAL
=================================================================
==122141==ERROR: AddressSanitizer: SEGV on unknown address 0x555555b00cbc (pc 0x55555577d931 bp 0x000000000027 sp 0x7fffffffcbf0 T0)
==122141==The signal is caused by a WRITE memory access.
#0 0x55555577d931 in compressed_sl_print print-sl.c:253
#1 0x55555577d931 in sliplink_print print-sl.c:166
#2 0x55555577d931 in sl_if_print print-sl.c:77
#3 0x555555689b28 in pretty_print_packet print.c:339
#4 0x55555567faae in print_packet tcpdump.c:2501
#5 0x7ffff7f7f909 (/lib/x86_64-linux-gnu/libpcap.so.0.8+0x24909)
#6 0x7ffff7f6acee in pcap_loop (/lib/x86_64-linux-gnu/libpcap.so.0.8+0xfcee)
#7 0x55555567a072 in main tcpdump.c:2004
#8 0x7ffff7646189 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#9 0x7ffff7646244 in __libc_start_main_impl ../csu/libc-start.c:381
#10 0x55555567d810 in _start (/usr/fuzz_pro/lib_fuzzer/tcpdump-4.9.0/tcpdump+0x129810)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV print-sl.c:253 in compressed_sl_print
==122141==ABORTING
[Inferior 1 (process 122141) exited with code 01]
gef➤ x/5x 0x555555b00cbc
0x555555b00cbc: Cannot access memory at address 0x555555b00cbc

漏洞修复

加了switch判断 不合法的输入将dir置为-1 , 后续代码遇到-1另外异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void
sliplink_print(netdissect_options *ndo, register const u_char *p, register const struct ip *ip, register u_int length)
int dir;
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;
+ }
2023-04-23

⬆︎TOP