进程的遍历有多种方法。在应用程序里可以使用CreateToolHelp32SnapShot函数先做个进程快照(snapshot),然后通过返回的数据进行遍历。在内核编程里可以通过内核数据结构来实现。
内核结构EPROCESS(执行体进程块 executive process)是一个不透明(opaque)的结构体,大概意思就是这个结构不对外公开,但是还是可以通过WinDbg获取它的各成员名和类型。在Win7 32上,eprocess结构体在偏移为0xb8处成员是ActiveProcessLinks,从字面意思就可以看出来是关于活动进程的链表。内核中常常使用循环双链表将相同类型的对象(object)串在一起,便于管理。在了解循环双链表之后就可以通过该链表来遍历进程。
lkd> dt nt!_list_entry
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
list_entry使用时应注意,当该表项是头节点时,Flink指第一个,Blink指最后一个;当该表项是中间节点时,Flink指向下一个,Blink指向前一个。
lkd> dt nt!_eprocess
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER
+0x0a8 ExitTime : _LARGE_INTEGER
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : Ptr32 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY 这个结构里保存了两个eprocess结构里,ActiveProcessLinks的地址,有张类似的图,可以对照看一看。
....(后面还有一大块没copy_paste)
实现遍历的主要代码:
ULONG AddrOfTheCurrentProcess=0,AddrOfTheFirstProcess=0;
AddrOfTheFirstProcess=AddrOfTheCurrentProcess=(ULONG)IoGetCurrentProcess();
do
{
KdPrint(("ImageFileName:%s\n",(char *)(AddrOfTheCurrentProcess+0x16c)));//打印进程名
AddrOfTheCurrentProcess=(ULONG)(*(ULONG *)(AddrOfTheCurrentProcess+0xb8)-0xb8);//得到下一个进程的地址
}while(AddrOfTheCurrentProcess!=AddrOfTheFirstProcess);
IoGetCurrentProcess()返回一个类型为Eprocess的指针,该指针指向当前进程的eprocess结构,并用ULONG转换一下。eprocess结构的0x16c偏移出时ImageFileName,一个长度为16的字符串数组,用来存放进程名(如果名字过长怎么办呢?还没发现怎么办:))。接下来就是从当前进程的地址到下一个进程的地址转换操作:(ULONG )(AddrOfTheCurrentProcess+0xb8),得到下一个eprocess里ActiveProcessLinks地址,然后再减去该成员的偏移就是结构体的首地址。(ULONG )(AddrOfTheCurrentProcess+0xb8)看上去不对劲呀?怎么感觉前面的(ULONG )抵消呢?来看看,假如AddrOfTheCurrentProcess+0xb8=0x00400100,该地址,保存另一个对象的地址。先把它变成指针(实际上都是地址好吧!),(ULONG )就得到想要的地址。可能没讲那么明白,可以去编一编,看看对不对,加深理解。
关于遍历的结果,因为是在内核第一个列出的是System,最后一个打印的乱码。实际上在命令行下使用tasklist命令得到的结果第一个是System Idle Process,它好像没有名字(悲哀呀)。