How Exploits Work
In cybersecurity, the term “exploit” is used to represent a piece of code, data or sequence of commands that takes advantage of a vulnerability. This code is usually broken up into three distinct sections, consisting of:
- The exploit used to gain access
- The shellcode injected directly after the exploit
- In most cases, a payload that actually carries out the malicious intent of the attacker.
When used together, these three parts make up what we refer to as “an exploit”, which can be delivered locally or remotely against a vulnerable application. This little code you see here is actually Log4j – which experts have called the “single biggest vulnerability of our time”.
In this blog, we’ll break down the anatomy of an exploit – and how each of these parts works together to gain unauthorized access into a system. We’ll also review a multi-defense strategy that can be used to defend against common exploits.
The term “exploit” is broadly used to explain an attack against a vulnerability. But more specifically, it starts with a piece of code that is intended to do something that was not originally intended by the developers. In other words, the first step is to break or crash the vulnerable software. This is the first and most important step of compromising a machine because it opens the door that ultimately leads to unauthorized access.
A popular exploit that we mentioned earlier is the recent Log4j which Tenable referred to as “the single biggest, most critical vulnerability of the last decade”. Log4j, or Log4Shell might be the worst exploit we have ever seen due to several factors, including how easy it is to execute and the widespread use of Apache web servers.
In this vulnerability, an attacker leverages the Java Naming and Directory Interface to perform a request for a malicious resource. The attacker places a JNDI command to download a StealthLoader trojan. The trojan installs crypto mining software on the victim machine
In this example, the first part of the string is the exploit command targeting the vulnerable server. The next section in purple is the shellcode that runs after the exploit, telling the machine what to do next. In this case, it instructs the victim to download the trojan using PowerShell. We’ll talk more about shellcodes in the next sections.
This particular exploit may be referred to as “remote execution”, and it comes from not properly sanitizing the input validation from the Java application. In this case, the actual exploit was the “jndi” command you see on the screen, which requests a malicious resource.
Many other kinds of exploits exist depending on the intended purpose of the attack and the delivery mechanism, whether it’s remote, local or client based. Some of the other common types of exploits include:
- Buffer overflows
- Remote code execution
- Cross-site scripting
- Denial of Service
- SQL Injections
- and many more
One of the most popular exploit types is the buffer overflow. This type of attack varies greatly based on many factors, but ultimately its goal is to introduce a system or kernel crash by overflowing the memory buffer reserved for the application. Once the application or system crashes, the goal is to insert malicious data in the memory buffer where the application was residing. From there, a common technique is to attempt to jump into a more privileged place in memory – often called “jumping the stack”.
Buffer Overflow Example
Let’s assume an application requested your username as text input into the application. The application has reserved 8 bytes for storing the user name inputted by the user. An attacker that knows the input variable can only hold 8 bytes of memory will attempt to overflow the buffer by inputting 10 bytes. These extra 2 bytes can contain real code that can alter the program’s execution. When the vulnerable application attempts to save this 10-byte value, the two extra bytes are actually overriding another portion of memory. This overridden part of memory now has the two bytes of malicious code that attempt to jump to another portion of memory, or run other kinds of shellcode.
It’s important to note that buffer overflow is one of many types of exploits. The exploit itself is just the means by which an attacker gets unauthorized access into a vulnerable system. In the buffer overflow example, the exploit inserted two bytes of code into memory which can be used to execute the second part of the attack – which is the shellcode. Vulnerabilities can exist anywhere, from applications and operating systems to hardware and even personnel.
The goal is for the exploit to cause unintended behavior in the application or system so that they can execute their desired code. The most common type of code delivered after the exploit is called “shellcode”.
Shellcode is typically written in assembly language, and it involves carefully crafted instructions that tell the machine what to do at a specific point of execution. Once a vulnerable system has been successfully exploited via buffer overflow, or some other type of attack, the shellcode contains instructions on what to do next.
Going back to our Log4j exploit, you’ll notice that the code has two parts: the first part is the exploit, which, as we said earlier, was the JNDI remote execution. The second part is the PowerShell command which translated from Base64 – means “download this malicious file from text bin.net.”
The first part is the exploit, the second part is the shell command we want to run on the victim machine. Together, both parts are what makes up a successful exploit.
The shellcode can be any type of payload that contains the malicious code you want to execute on the victim machine. Log4j was a rare example of a relatively simple type of exploit that had major ramifications. In this case, the shellcode is simple to understand because we’re sending a remote command to be executed. In more advanced exploits, things are rarely that easy. In fact, even in the simplest of buffer overflows examples, you rarely get your overflow code run in the section of the memory stack you want it to run the proper shellcode. This is where jumping the stack techniques attempt to use pointers to redirect your code into more privileged memory sections.
While the shellcode can be considered a type of payload, this part of the exploit is usually about doing just enough to compromise the machine. Often, another type of payload is introduced as the 3rd and possibly final step that sets up an attacker with a more persistent entry to the vulnerable application.
After a successful exploit, an attacker may opt to include another type of payload, which includes malware to carry out a specific task, reverse shell or a RAT – that can be used to gain more access later. If we take a look again at our Log4j exploit, we see the shellcode which triggers the download of the bit mining trojan. In this example, the trojan being downloaded is the additional payload and final part of the successful exploit because it accomplishes the goal for this attack – which is to run bit mining code on all infected systems.
Several frameworks exist for the kind of payload we want to run on the victim once the exploit has gained us access. Meterpreter is one kind of payload that itself includes a suite of tools an attacker can use on the compromised machine. With the Meterpreter payload injected after the exploit, an attacker can do things such as:
- Retrieve hash dumps to reveal the SAM database of the remote system
- Escalate privileges
- Turn and use webcam features
- Drop into the shell prompt
- Automatically cover up their tracks, which removes logs that may have triggered the compromise
Meterpreter is a powerful payload that is included in Metasploit, and must be selected after selecting the exploit you want to run to gain initial access.
Several free and commercial tools exist to protect against most of the techniques discussed in this blog. But like all things in security, a multi-tiered defense approach is necessary.
It starts with securing the coding of the application or software – and making sure proper security practices are in place at the time of development. Microsoft, OWASP and several other resources exist to provide developers with best practices to use – such as input validation and data sanitization.
Beyond secure coding, we need to protect the OS on which the software or application is running. Techniques like DEP and ASLR can help mitigate buffer overflow attacks. There are also CIS benchmarks , configuration baselines and best practices for securely configuring operating systems and network infrastructure.
Beyond the OS, we need to protect at the network layer. And that means following the principles of Zero Trust and least privileged. Locking down unnecessary ports and other best practices should be applied here. Of course, when you’re dealing with any type of software – it’s critical to maintain a patch management policy to make sure that applications and operating systems are up to date. This is perhaps the best ROI on protecting against attacks, yet it’s also the one most overlooked.
Sitting at the network and system is the IDS or IPS system. IPS are specifically designed to look for known exploit signatures. The keyword there is “known”, – which means it won’t do anything in the way of protecting against zero-days which are not yet publicly known. Even still, a good IPS device is a nice safety net to help protect you in between the time a vulnerability is known and the time you can actually patch your software.