51、进程与线程逆向:进程遍历、线程遍历、进程隐藏、进程保护
进程和线程,是逆向工程里绕不开的两座山。你分析一个恶意软件,它总得跑起来吧?跑起来就得有进程,有线程。我这些年跟各种样本打交道,说白了,大部分时间都在跟这两样东西较劲。
今天咱们就聊聊,怎么在逆向视角下,把进程和线程看个通透。包括怎么遍历它们、怎么藏起来、又怎么保护自己不被干掉。
进程遍历:找到目标的第一步
你想分析一个进程,首先得找到它。Windows 下遍历进程的方法很多,但我个人最常用的是 CreateToolhelp32Snapshot 这套 API。
为什么?因为它简单、稳定,而且兼容性好。我在分析一个老旧的 XP 系统样本时,用其他 API 经常翻车,但这套基本没出过问题。
核心思路就三步:
- 拍一张快照(CreateToolhelp32Snapshot)
- 遍历进程列表(Process32First / Process32Next)
- 读取每个进程的信息(PID、父进程、路径等)
给你看个典型的代码骨架:
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return;
PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
if (Process32First(hSnapshot, &pe)) {
do {
// pe.th32ProcessID 就是进程PID
// pe.szExeFile 是进程名
// pe.th32ParentProcessID 是父进程PID
printf("PID: %d, Name: %s\n", pe.th32ProcessID, pe.szExeFile);
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
嗯,这里要注意一点:快照句柄用完一定要关闭。我曾经见过一个新手写的工具,跑了几分钟内存暴涨,就是因为忘了 CloseHandle。
线程遍历:比进程多一层麻烦
线程遍历的思路和进程类似,也是用 CreateToolhelp32Snapshot,只不过参数换成 TH32CS_SNAPTHREAD。
但有个坑——线程遍历返回的是系统所有线程,你得自己过滤出属于目标进程的线程。我记得有一次调试一个注入器,发现目标进程多了几个可疑线程,就是用这个方法揪出来的。
HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
THREADENTRY32 te = { sizeof(THREADENTRY32) };
if (Thread32First(hThreadSnap, &te)) {
do {
if (te.th32OwnerProcessID == targetPID) {
// 这就是目标进程里的线程
printf("Thread ID: %d\n", te.th32ThreadID);
}
} while (Thread32Next(hThreadSnap, &te));
}
你想想看,如果一个进程有几十个线程,但大部分都是系统线程池的,真正干活的就两三个。怎么区分?我一般会结合线程的起始地址来判断,这个后面再说。
进程隐藏:让对手看不见你
做逆向的,有时候也需要藏一下自己的分析工具。进程隐藏,说白了就是让遍历 API 找不到你。
常见的手法有这么几种:
| 手法 | 原理 | 难度 |
|---|---|---|
| DKOM(直接内核对象操作) | 修改 EPROCESS 结构的链表指针 | 高 |
| 用户态 Hook | Hook NtQuerySystemInformation | 中 |
| 傀儡进程 | 把代码注入到合法进程里跑 | 中 |
| Rootkit 技术 | 在内核层拦截遍历请求 | 高 |
我个人觉得,对于大多数逆向场景,傀儡进程是最实用的。你不需要碰内核,风险低,而且效果不错。
进程保护:别让人轻易干掉你
反过来,如果你写了一个分析工具,或者一个调试器,你肯定不希望它被恶意进程结束掉。进程保护就是干这个的。
我见过最粗暴的方式是双进程守护:两个进程互相监控,一个挂了另一个立马拉起来。这招在恶意软件里很常见,我分析过一个远控木马,就是用的这个套路。
更高级一点的是内核回调保护:注册进程创建回调,一旦检测到结束请求,直接拦截。
知识体系总览
下面这张图,是我梳理的进程与线程逆向的核心知识结构。你可以把它当作一个地图,遇到具体问题时,知道该往哪个方向走。
实战中的避坑指南
最后,分享几个我踩过的坑:
- 遍历时权限不够: 有些系统进程(如 csrss.exe)需要调试权限才能访问。我建议在程序启动时用 AdjustTokenPrivileges 提权。
- 32位和64位进程混搭: 32位工具无法遍历64位进程的模块信息。如果你需要做跨位数分析,记得编译成64位版本。
- 线程同步问题: 遍历线程时,目标进程可能正在创建或销毁线程。我一般会连续遍历两次,对比结果,取交集。
嗯,今天就聊到这儿。进程和线程这块内容,说深可以很深,说浅也可以很浅。关键是你得在实际调试中多动手,光看文章是学不会的。
公众号:蓝海资料掘金营,微信deep3321