枚举进程(CreateToolhelp32Snapshot函数剖析)
之前写过一些枚举进程的代码,这两天正在回顾之前学习的代码,看到了CreateToolhelp32Snapshot函数,就顺便来看看它的具体实现吧。
实现枚举进程信息的这个过程看起来是比较easy的,只不过是一些Windows的API函数罢了:
- CreateToolhelp32Snapshot
- Process32First
- Process32Next
就利用这三个函数,便可实现当前系统进程的枚举工作,但是这三个函数的具体实现,还是有待考究。
先从CreateToolhelp32Snapshot说起:
其传递两个参数,第一参数是一个宏,说明我们需要拍摄快照的类型,我们在此处是枚举进程信息,自然就传递TH32CS_SNAPPROCESS。第二个参数是进程ID,我们一般传入0,传入0之后会发生什么呢?原来是默认当前进程。
if(th32ProcessID == 0)
{
th32ProcessID = GetCurrentProcessId();
}
接下来,会在当前进程中申请一块内存空间,然后调用NtQuerySystemInformation进行进程信息查询,对查询到的信息在参数列表中进行返回。
for(;;)
{
(*ProcThrdInfoSize) += 0x10000;
//在当前进程家申请内存,可读可写,并且提交物理内存
Status = NtAllocateVirtualMemory(NtCurrentProcess(),
ProcThrdInfo,
0,
ProcThrdInfoSize,
MEM_COMMIT,
PAGE_READWRITE);
if(!NT_SUCCESS(Status))
{
break;
}
//查询进程信息 并且作为参数返回
Status = NtQuerySystemInformation(SystemProcessInformation,
*ProcThrdInfo,
*ProcThrdInfoSize,
NULL);
if(Status == STATUS_INFO_LENGTH_MISMATCH)
{
//如果长度不够,则释放内存,进入for循环再增加0x10000重新申请
NtFreeVirtualMemory(NtCurrentProcess(),
ProcThrdInfo,
ProcThrdInfoSize,
MEM_RELEASE);
*ProcThrdInfo = NULL;
}
else
{
break;
}
}
接下来创建一个Section对象,并进行虚拟内存映射,将返回的进程信息初始化到Section对象所在的内存,返回Section句柄
...
//创建Section对象,可读可写,内存提交
Status = NtCreateSection(&hSection,
SECTION_ALL_ACCESS,
&ObjectAttributes,
&SSize,
PAGE_READWRITE,
SEC_COMMIT,
NULL);
...
//在当前进程进行映射,返回映射出来的地址赋值给一个PTH32SNAPSHOT指针
Status = NtMapViewOfSection(hSection,
NtCurrentProcess(),
(PVOID*)&Snapshot,
0,
0,
&SOffset,
&ViewSize,
ViewShare,
0,
PAGE_READWRITE);
......
//将之前查询出来的进程信息放到映射出来的地址中
ProcessListEntry = (LPPROCESSENTRY32W)OffsetToPtr(Snapshot, DataOffset);
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)ProcThrdInfo;
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)ProcessInfo + ProcOffset);
ProcessListEntry->dwSize = sizeof(PROCESSENTRY32W);
ProcessListEntry->cntUsage = 0; /* no longer used */
ProcessListEntry->th32ProcessID = (ULONG_PTR)ProcessInfo->UniqueProcessId;
ProcessListEntry->th32DefaultHeapID = 0; /* no longer used */
ProcessListEntry->th32ModuleID = 0; /* no longer used */
ProcessListEntry->cntThreads = ProcessInfo->NumberOfThreads;
ProcessListEntry->th32ParentProcessID = (ULONG_PTR)ProcessInfo->InheritedFromUniqueProcessId;
....//上面是部分赋值语句
//写完我们要写的数据,结束映射
Status = NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
if(NT_SUCCESS(Status))
{
//返回Section对象的句柄,以便后续使用
*SectionHandle = hSection;
}
至此代码大体实现已经完成,接下来会对一些不再需要的资源进行回收。
Process32First与Process32Next的实现基本一致:
//内部照样对section对象的句柄进行内存映射,读出内存中的内容
Status = NtMapViewOfSection(hSnapshot,
NtCurrentProcess(),
(PVOID*)&Snapshot,
0,
0,
&SOffset,
&ViewSize,
ViewShare,
0,
PAGE_READWRITE);
//对里面的内容进行解析
if(Snapshot->ProcessListCount > 0)
{
LPPROCESSENTRY32W Entries = (LPPROCESSENTRY32W)OffsetToPtr(Snapshot, Snapshot->ProcessListOffset);
//进行结构体内容拷贝
Snapshot->ProcessListIndex = 1;
RtlCopyMemory(lppe, &Entries[0], sizeof(PROCESSENTRY32W));
Ret = TRUE;
}
//释放内存映射
NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
Process32Next只不过是利用索引开始进行遍历,拷贝各个进程的信息,在这里就不做赘述,最重要的三个函数整体实现过程就是这样,我相信在此基础之上更能理解快照的含义。
附简单测试代码:
/*
typedef struct tagPROCESSENTRY32
{
DWORD dwSize; //该结构体字节数,在调用Process32First前必须初始化为sizeof(PROCESSENTRY32)
DWORD cntUsage; //0 很久不用了
DWORD th32ProcessID; //当前进程Id
ULONG_PTR th32DefaultHeapID; //0 很久不用了
DWORD th32ModuleID; //0 很久不用了
DWORD cntThreads; //进程启动的执行线程数
DWORD th32ParentProcessID; //父进程Id
LONG pcPriClassBase; //进程创建线程的基本优先级
DWORD dwFlags; //0 保留值 未使用
TCHAR szExeFile[MAX_PATH]; //进程名
} PROCESSENTRY32;
*/
void _tmain(int argc, _TCHAR **argv, _TCHAR **envp)
{
_tsetlocale(LC_ALL, _T("chs"));
TCHAR* v1 = (TCHAR*)(_T("进程枚举:"));
_tprintf(_T("%s\n"), v1);
KtEnumSystemProcess();
_tprintf(_T("Input AnyKey to Exit\r\n"));
getchar();
}
VOID KtEnumSystemProcess()
{
HANDLE SnapHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (SnapHandle == INVALID_HANDLE_VALUE)
{
return;
}
//PROCESSENTRY32 必须这样初始化
PROCESSENTRY32 ProcessEntry = { sizeof(PROCESSENTRY32) };
BOOL IsOk = Process32First(SnapHandle, &ProcessEntry);
//NtMapViewOfSection再次映射Section对象到虚拟内存中
//进行读操作
//与Process32First基本一致,通过索引对PTH32SNAPSHOT中的进程列表一一提取
for (; IsOk; IsOk = Process32Next(SnapHandle, &ProcessEntry))
{
_tprintf(_T("ProcessName:%s "), ProcessEntry.szExeFile);
_tprintf(_T("ThreadPriority:%d "), ProcessEntry.pcPriClassBase);
_tprintf(_T("ProcessID:%d \r\n"), ProcessEntry.th32ProcessID);
_tprintf(_T("ParentID:%d \r\n"), ProcessEntry.th32ParentProcessID);
}
}
拍摄快照这个函数有一个弊端就是不能实时更新系统中的进程,如果我在拍摄好快照之后,也就是Section对象的句柄返回之后,将某个进程关闭,它还是会枚举出被我关闭的这个进程。同理,当我在拍摄快照之后,再启动一个进程,也不会枚举出进程的信息。
注:讲解代码过程中的源代码来自开源代码ReactOS
今日自勉:夜色之浓,莫过于黎明前的黑暗。