When penetration testing, different categories of shellcode can be used. Ultimately, shellcode can be broken down into two main categories, local and remote. Within each category, there are various types of shellcode that exist and that perform different functions. In this section, we will explore these various types of shellcode. Keep in mind that this is not a complete list as new types of shellcode are constantly being developed. Let's explore the various types of shellcode that exist, starting with local shellcode and moving on to remote shellcode.
Local shellcode
Local shellcode is run on the target computer and does not perform any network activities. This type of shellcode can be used to escalate privileges, execute a payload, spawn a shell, or break out of a jailed shell. Let's examine some examples of local shellcode.
execve shellcode
execve
is a syscall that is used within Linux systems to execute a program on the local system. It is commonly used for privilege escalation when executing a shell. In the first example of shellcode at the beginning of this book, you saw a sample of the execve
system call being used within shellcode.
You can learn more about execve
by looking at the man page for the system call.
By executing the man execve
command on Linux, you will be presented with a full write-up about it:
NAME
execve - execute program
SYNOPSIS
#include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
DESCRIPTION
execve() executes the program pointed to by filename. filename must be either a binary executable, or a script starting with a line of the form..
..snip..
Generally, execve
is used in conjunction with the following:
filename
: A pointer to a string specifying the path to a binary
argv[]
: An array of command-line variables
envp[]
: An array of environment variables
Right at the beginning of this chapter, an example of execve
was shown. Here is a recap of the command specifically related to execve
:
execve("/bin/sh", args, NULL);
As per the man page, execve
can be used to execute a program. Since this syscall is able to execute either an executable or a script, it's commonly used in shellcode.
Buffer overflow
Buffer overflow attacks result from an exploited vulnerability locally. A buffer in relation to memory is an area used by a running program. This location is a temporary location that has temporary data stored by an application. A buffer overflow happens when the length of the input data exceeds (overflows) the limit of the buffer. This overflow causes the program to write data outside of its buffer allocation, perhaps in other sections of memory. This process causes the program to crash. The program crashing is not dangerous in itself, but let's assume the program is written with a binary such as setuid
.
The setuid
binary ultimately allows a program to run under a special privileged permission, the permission of a user or system/root privilege. So, moving back to the program, if you are able to cause a buffer overflow, ultimately you can make it execute a payload that executes a system call to spawn a reverse shell.
Egg hunter
When it comes to writing shellcode used to exploit a program, one of the challenges that is faced is the limited space. That limited space may hamper what you are trying to execute and ultimately cause your execution to fail. Consider a basic shellcode with the primary purpose of providing a reverse shell. Depending on what you use to generate it, you may end up with a size of 32 bytes or more. Now, what if the target program does not have that amount of free space within its allocated buffer? Well, that simple shellcode will not work.
This is where egg hunting comes into play. The main purpose of egg hunting is to search the memory for a specified egg that is defined when crafting the egg hunter shellcode. This egg is a location in memory that is a unique string, also referred to as a tag. Once this egg is found, the shellcode located directly after the egg will be executed.
In Chapter 4, Developing Shellcode for Windows, we will cover egg hunting in more detail with some examples.
Shellcode reflective DLL injection
Shellcode reflective DLL injection (sRDI) is a mechanism that allows you to turn a DLL into position-free shellcode that can subsequently be injected using your preferred shellcode injection and execution method.
To understand how this technique works, let's look back at some history. DLL injection involves the use of a malicious DLL file that was read from the disk and loaded into a target process. While it worked a few years back, the problem with this technique is that anti-virus manufacturers caught on to it and started to flag these types of files, not to mention the security improvements made by operating system vendors over time. That being said, you may have the ability to use a completely new DLL that has not been seen before and still have the chance of success with a normal DLL injection – but we can assume that this opportunity is unlikely.
Around 2009, we began to see a reflective DLL injection that made use of something called a ReflectiveLoader from the malicious DLL. When injected, this DLL would then drop a thread and work its way back to locate the DLL and map it automatically. Ultimately, DLLMain would be called, and your code would be running in memory.
In 2015, we saw a reflective DLL injection that allowed a function to be called after DLLMain and allowed the passing of user arguments. This is made possible by the use of shellcode and a bootstrap placed before the call of the ReflectiveLoader. This allowed you to load a DLL, call the entry point, and pass data to another exported function.
If you would like to look at some public references for this technique, you can take a look at the sRDI published at https://github.com/monoxgas/sRDI.
Remote shellcode
Remote shellcode runs on another computer through a network or via remote connectivity. Remote shellcodes make use of TCP/IP connections in order to provide access to the target machine shell. Shellcodes of this type are categorized based on how they are set up. For example, you have a bindshell if the shellcode binds to a certain port on the target computer. If the shellcode used establishes a connection back to you, then you have a reverse shell.
Bindshell
A bindshell does exactly what its name implies. It binds the shell to a specific port or socket. In essence, the target machine works as a server waiting for a connection on a specific port. Once a connection is established, a shell is provided. This technique is not really used much, as most targets have a firewall in place to block incoming connections. That being said, there is still a chance of discovering an endpoint that has a firewall rule allowing connections to it.
An example bindshell written in C looks like this:
#include <stdio.h>
...snip..
int main ()
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(4444);
addr.sin_addr.s_addr = INADDR_ANY;
...snip..
{
...snip..
}
execve("/bin/sh", NULL, NULL);
return 0;
}
In the preceding example, the use of AF_NET
is used to create an IPv4 socket. We then have the port defined by addr.sin_port
and at the end, we have execve
, which is used to spawn the shell.
Download and execute
This type of shellcode is slightly different from the rest in that it does not spawn a shell. Instead, it is used to download and execute something. This can be a malicious program, a payload, or malware, among others.
In environments today, web filtering products have a number of enhancements to block potentially malicious traffic. Even newer web browsers have these enhancements, such as SmartScreen on Microsoft Edge. These features present a number of issues when trying to get a target to perform a drive-by download or the execution of shellcode that makes use of visibly malicious patterns.
However, even with these advancements in detection, it is still possible to get shellcode to download and execute something, such as by making use of urlmon.dll
and one of its APIs called URLDownloadToFileA
, for example.