Netcat 0.7.1远程拒绝服务漏洞

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

这个漏洞相对于上一个比较简单

这个选项是使用telnet协议来进行telnet应答

-T, --telnet answer using TELNET negotiation

调试过程

wget https://www.exploit-db.com/apps/088def25efe04dcdd1f8369d8926ab34-netcat-0.7.1.tar.gz
tar -zxvf 088def25efe04dcdd1f8369d8926ab34-netcat-0.7.1.tar.gz
cd 088def25efe04dcdd1f8369d8926ab34-netcat-0.7.1
./configure
make

gdb运行netcat -T -lvvp 1234,运行poc

 RAX  0x21ae0
 RBX  0x2df
 RCX  0x21ae1
 RDX  0x62
 RDI  0x7fffffffd080 ◂— 0x200000004
 RSI  0x2df
 R8   0x0
 R9   0x20
 R10  0x0
 R11  0x246
 R12  0x7fffffffd080 ◂— 0x200000004
 R13  0x7fffffffc7c0 ◂— 0x6262626262626262 ('bbbbbbbb')
 R14  0x2e0
 R15  0x1
 RBP  0x400
 RSP  0x7fffffffc600 ◂— 0x0
 RIP  0x405fe4 (netcat_telnet_parse+100) ◂— mov    byte ptr [rax + 0x609520], dl
──────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────────
 ► 0x405fe4 <netcat_telnet_parse+100>    mov    byte ptr [rax + 0x609520], dl
   0x405fea <netcat_telnet_parse+106>    cmp    ecx, 1
   0x405fed <netcat_telnet_parse+109>    mov    dword ptr [rip + 0x203531], ecx <0x609524>
   0x405ff3 <netcat_telnet_parse+115>    je     netcat_telnet_parse+376 <0x4060f8>
    ↓
   0x4060f8 <netcat_telnet_parse+376>    mov    esi, r14d
   0x4060fb <netcat_telnet_parse+379>    jmp    netcat_telnet_parse+256 <0x406080>
    ↓
   0x406080 <netcat_telnet_parse+256>    add    ebx, 1
   0x406083 <netcat_telnet_parse+259>    cmp    ebp, ebx
   0x406085 <netcat_telnet_parse+261>    jg     netcat_telnet_parse+64 <0x405fc0>
    ↓
   0x405fc0 <netcat_telnet_parse+64>     movsxd rax, ebx
   0x405fc3 <netcat_telnet_parse+67>     movzx  edx, byte ptr [r13 + rax]
──────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────────────────────
In file: /media/psf/Home/MyFile/study/CVE复现/NC拒绝服务漏洞/netcat-0.7.1/src/telnet.c
    95     /* this is surely a char that will be eaten */
    96     eat_chars++;
    97 #endif
    98 
    99     /* copy the char in the IAC-code-building buffer */
 ► 100     getrq[l++] = buf[i];
   101 
   102     /* if this is the first char (IAC!) go straight to the next one */
   103     if (l == 1)
   104       continue;
   105 
──────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffc600 ◂— 0x0
01:0008│      0x7fffffffc608 ◂— 0x278228f898a31d00
02:0010│      0x7fffffffc610 —▸ 0x7fffffffd080 ◂— 0x200000004
03:0018│      0x7fffffffc618 —▸ 0x7fffffffd820 ◂— 0x0
04:0020│      0x7fffffffc620 —▸ 0x7fffffffc7c0 ◂— 0x6262626262626262 ('bbbbbbbb')
05:0028│      0x7fffffffc628 ◂— 0x4
06:0030│      0x7fffffffc630 ◂— 0x1
07:0038│      0x7fffffffc638 —▸ 0x403e18 (core_readwrite+1816) ◂— mov    eax, dword ptr [rbx + 0x3c0]
────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────
 ► f 0           405fe4 netcat_telnet_parse+100
   f 1           403e18 core_readwrite+1816
   f 2           402131 main+1889
   f 3     7ffff7a2d830 __libc_start_main+240
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Program received signal SIGSEGV (fault address 0x62b000)
pwndbg> 

可以发现程序访问了无效的地址0x62b000,bt回溯,

pwndbg> bt
#0  netcat_telnet_parse (ncsock=ncsock@entry=0x7fffffffd080) at telnet.c:100
#1  0x0000000000403e18 in core_readwrite (nc_main=nc_main@entry=0x7fffffffd080, nc_slave=nc_slave@entry=0x7fffffffd820) at core.c:823
#2  0x0000000000402131 in main (argc=argc@entry=4, argv=argv@entry=0x7fffffffdd08) at netcat.c:499
#3  0x00007ffff7a2d830 in __libc_start_main (main=0x4019d0 <main>, argc=4, argv=0x7fffffffdd08, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdcf8) at ../csu/libc-start.c:291
#4  0x0000000000402889 in _start ()

报错出现在netcat_telnet_parse函数中,从源码中找到这个函数
telnet.c:74:

void netcat_telnet_parse(nc_sock_t *ncsock)
{
  static unsigned char getrq[4];
  static int l = 0;
  unsigned char putrq[4], *buf = ncsock->recvq.pos;
  int i, *size = &ncsock->recvq.len, eat_chars = 0, ref_size = *size;
  debug_v(("netcat_telnet_parse(ncsock=%p)", (void *)ncsock));

  /* if the socket object is NULL, assume a reset command */
  if (ncsock == NULL) {
    l = 0;
    return;
  }

  /* loop all chars of the string */
  for (i = 0; i < ref_size; i++) {
    /* if we found IAC char OR we are fetching a IAC code string process it */
    if ((buf[i] != TELNET_IAC) && (l == 0))
      continue;

#ifndef USE_OLD_TELNET
    /* this is surely a char that will be eaten */
    eat_chars++;
#endif

    /* copy the char in the IAC-code-building buffer */
    getrq[l++] = buf[i];

    /* if this is the first char (IAC!) go straight to the next one */
    if (l == 1)
      continue;

    /* identify the IAC code. The effect is resolved here. If the char needs
       further data the subsection just needs to leave the index 'l' set. */
    switch (getrq[1]) {
    case TELNET_SE:
    case TELNET_NOP:
      goto do_eat_chars;
    case TELNET_DM:
    case TELNET_BRK:
    case TELNET_IP:
    case TELNET_AO:
    case TELNET_AYT:
    case TELNET_EC:
    case TELNET_EL:
    case TELNET_GA:
    case TELNET_SB:
      goto do_eat_chars;
    case TELNET_WILL:
    case TELNET_WONT:
      if (l < 3) /* need more data */
        continue;

      /* refuse this option */
      putrq[0] = 0xFF;
      putrq[1] = TELNET_DONT;
      putrq[2] = getrq[2];
      /* FIXME: the rfc seems not clean about what to do if the sending queue
         is not empty.  Since it's the simplest solution, just override the
         queue for now, but this must change in future. */
      write(ncsock->fd, putrq, 3);      /* FIXME: handle failures */
      goto do_eat_chars;
    case TELNET_DO:
    case TELNET_DONT:
      if (l < 3) /* need more data */
        continue;

      /* refuse this option */
      putrq[0] = 0xFF;
      putrq[1] = TELNET_WONT;
      putrq[2] = getrq[2];
      write(ncsock->fd, putrq, 3);
      goto do_eat_chars;
    case TELNET_IAC:
#ifndef USE_OLD_TELNET
      /* insert a byte 255 in the buffer.  Note that we don't know in which
         position we are, but there must be at least 1 eaten char where we
         can park our data byte.  This effect is senseless if using the old
         telnet codes parsing policy. */
      buf[i - --eat_chars] = 0xFF;
#endif
      goto do_eat_chars;
    default:
      /* FIXME: how to handle the unknown code? */
      break;
    }
    continue;

 do_eat_chars:
    /* ... */
    l = 0;

#ifndef USE_OLD_TELNET
    if (eat_chars > 0) {
      unsigned char *from, *to;

      debug(("(telnet) ate %d chars\n", eat_chars));

      /* move the index to the overlapper character */
      i++;

      /* if this is the end of the string, memmove() does not care of a null
         size, it simply does nothing. */
      from = &buf[i];
      to = &buf[i - eat_chars];
      memmove(to, from, ref_size - i);

      /* fix the index.  since the loop will auto-increment the index we need
         to put it one char before.  this means that it can become negative
         but it isn't a big problem since it is signed. */
      i -= eat_chars + 1;
      ref_size -= eat_chars;
      eat_chars = 0;
    }
#endif
  }

  /* we are at the end of the buffer. all we have to do now is updating the
     authoritative buffer size.  In case that there is a broken-down telnet
     code, the do_eat_chars section is not executed, thus there may be some
     pending chars that needs to be removed.  This is handled here in an easy
     way: since they are at the end of the buffer, just cut them playing with
     the buffer length. */

#ifdef USE_OLD_TELNET
  assert(eat_chars == 0);
#endif

  *size = ref_size - eat_chars;
}

根据源码可以发现变量getrql都是静态变量,而把l赋值为零的只有两处,第一处是在telnet.c:84,当ncsock为空的时候,把l赋值为零。另一处在telnet.c:164,只有当进入到上面switch语句里的分支时,才会goto跳转到do_eat_chars,所以我们只需发送的字符绕过switch里的case字符即可,但是第一个字符需要特定的来构造为\xff

在这个for循环中,ref_size就是接受到字符的个数,l初始值是零,如果buf[i]!=TELNET_IAC#define TELNET_IAC 255 /* Data Byte 255. */),那么就会跳过下面的代码重新循环,所以payload中的第一个字符需要是\xff,然后再发很多个字符,直到程序crash

for (i = 0; i < ref_size; i++) {
    if ((buf[i] != TELNET_IAC) && (l == 0))
      continue;
    ……

netcat_telnet_parse下断点,查看参数nc_sock_t *ncsock的内容

pwndbg> p *ncsock 
$1 = {
  fd = 4, 
  domain = 2, 
  timeout = 0, 
  proto = NETCAT_PROTO_TCP, 
  local_host = {
    name = '\000' <repeats 255 times>, 
    addrs = {'\000' <repeats 15 times>, '\000' <repeats 15 times>, '\000' <repeats 15 times>, '\000' <repeats 15 times>, '\000' <repeats 15 times>, '\000' <repeats 15 times>}, 
    iaddrs = {{
        s_addr = 0
      }, {
        s_addr = 0
      }, {
        s_addr = 0
      }, {
        s_addr = 0
      }, {
        s_addr = 0
      }, {
        s_addr = 0
      }}
  }, 
  host = {
    name = '\000' <repeats 255 times>, 
    addrs = {'\000' <repeats 15 times>, '\000' <repeats 15 times>, '\000' <repeats 15 times>, '\000' <repeats 15 times>, '\000' <repeats 15 times>, '\000' <repeats 15 times>}, 
    iaddrs = {{
        s_addr = 0
      }, {
        s_addr = 0
      }, {
        s_addr = 0
      }, {
        s_addr = 0
      }, {
        s_addr = 0
      }, {
        s_addr = 0
      }}
  }, 
  local_port = {
    name = '\000' <repeats 63 times>, 
    ascnum = "1234\000\000\000", 
    num = 1234, 
    netnum = 53764
  }, 
  port = {
    name = '\000' <repeats 63 times>, 
    ascnum = "\000\000\000\000\000\000\000", 
    num = 0, 
    netnum = 0
  }, 
  sendq = {
    head = 0x0, 
    pos = 0x0, 
    len = 0
  }, 
  recvq = {
    head = 0x0, 
    pos = 0x7fffffffc7c0 "\377", 
    len = 1
  }
}

这里面包含着连接信息,具体的元素含义可以在netcat.h里面查看定义的结构体

参考

NETCAT【NC】 0.7.1 远程拒绝服务漏洞

添加新评论