51、进程与线程逆向:进程遍历、线程遍历、进程隐藏、进程保护

进程和线程,是逆向工程里绕不开的两座山。你分析一个恶意软件,它总得跑起来吧?跑起来就得有进程,有线程。我这些年跟各种样本打交道,说白了,大部分时间都在跟这两样东西较劲。

今天咱们就聊聊,怎么在逆向视角下,把进程和线程看个通透。包括怎么遍历它们、怎么藏起来、又怎么保护自己不被干掉。

进程遍历:找到目标的第一步

你想分析一个进程,首先得找到它。Windows 下遍历进程的方法很多,但我个人最常用的是 CreateToolhelp32Snapshot 这套 API。

为什么?因为它简单、稳定,而且兼容性好。我在分析一个老旧的 XP 系统样本时,用其他 API 经常翻车,但这套基本没出过问题。

核心思路就三步:

  1. 拍一张快照(CreateToolhelp32Snapshot)
  2. 遍历进程列表(Process32First / Process32Next)
  3. 读取每个进程的信息(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 技术 在内核层拦截遍历请求

我个人觉得,对于大多数逆向场景,傀儡进程是最实用的。你不需要碰内核,风险低,而且效果不错。

我的经验: 做傀儡进程时,记得把原进程的线程全部挂起再恢复,否则容易崩溃。我曾经图省事没做这一步,结果目标进程直接蓝屏,调试器都来不及保存数据。

进程保护:别让人轻易干掉你

反过来,如果你写了一个分析工具,或者一个调试器,你肯定不希望它被恶意进程结束掉。进程保护就是干这个的。

我见过最粗暴的方式是双进程守护:两个进程互相监控,一个挂了另一个立马拉起来。这招在恶意软件里很常见,我分析过一个远控木马,就是用的这个套路。

更高级一点的是内核回调保护:注册进程创建回调,一旦检测到结束请求,直接拦截。

注意: 进程保护技术如果写得不好,很容易被反杀。比如双进程守护,如果攻击者同时结束两个进程,你的保护就形同虚设。我建议加上随机延迟启动,增加同时命中的难度。

知识体系总览

下面这张图,是我梳理的进程与线程逆向的核心知识结构。你可以把它当作一个地图,遇到具体问题时,知道该往哪个方向走。

进程与线程逆向 进程遍历 CreateToolhelp32Snapshot Process32First/Next PID / 父进程 / 路径 线程遍历 TH32CS_SNAPTHREAD Thread32First/Next 按进程PID过滤 进程隐藏 DKOM / Hook 傀儡进程 Rootkit 进程保护 双进程守护 内核回调 句柄权限控制 核心:理解系统机制,才能攻防自如

实战中的避坑指南

最后,分享几个我踩过的坑:

  • 遍历时权限不够: 有些系统进程(如 csrss.exe)需要调试权限才能访问。我建议在程序启动时用 AdjustTokenPrivileges 提权。
  • 32位和64位进程混搭: 32位工具无法遍历64位进程的模块信息。如果你需要做跨位数分析,记得编译成64位版本。
  • 线程同步问题: 遍历线程时,目标进程可能正在创建或销毁线程。我一般会连续遍历两次,对比结果,取交集。

嗯,今天就聊到这儿。进程和线程这块内容,说深可以很深,说浅也可以很浅。关键是你得在实际调试中多动手,光看文章是学不会的。

公众号:蓝海资料掘金营,微信deep3321