基于EPROCESS结构中双向链表的进程检测方法
文/图 武汉大学计算机学院信息安全专业 段俊锋
本文介绍了Windows下进程的EPROCESS结构,并针对该结构中的两个双向链表,给出了一种检测隐藏进程的方法。
EPROCESS结构中的双向链表
对于Windows下的每一个进程,系统都会给它分配一个excutive process (EPROCESS) block。该结构包含和指向一系列其他相关的数据结构,比如每个进程的所有线程信息。EPROCESS及其所有相关数据结构都位于系统空间,只有Process environment block(peb)是位于进程地址空间,因为它包含由用户模式代码所决定的信息。具体如图1所示。
图1
在Windbg下,我们可以通过dt命令获取EPROCESS的具体格式。
lkd> dt _EPROCESS
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
……
在这个结构中有我们关心的两个数据,一个是偏移0x88处的ActiveProcessLinks,另一个是偏移0xc4处的ObjectTable。
顾名思义,ActiveProcessLinks是活动进程链表,从图1中我们可以看出,它的作用就是把一个个的EPROCESS结构链接在一起。它是一个_LIST_ENTRY的结构类型:
lkd> dt _LIST_ENTRY
nt!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
Flink是指向当前节点之后的指针,Blink是指向当前节点之前的指针,_LIST_ENTRY是一个双向链表。活动进程可以通过这个链表来遍历,如图2所示。
图2
与ActiveProcessLinks不同的是,ObjectTable并不是一个双向链表,它指向的是进程的句柄表。而系统内所有这些句柄表结构都是通过_LIST_ENTRY类型的双向链表链接起来的,类似于ActiveProcessLinks将进程链接起来的方式。ObjectTable是一个_HANDLE_TABLE结构,具体如下:
lkd> dt _HANDLE_TABLE
nt!_HANDLE_TABLE
+0x000 TableCode : Uint4B
+0x004 QuotaProcess : Ptr32 _EPROCESS
+0x008 UniqueProcessId : Ptr32 Void
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY
……
偏移0x1c处就是一个类型为_LIST_ENTRY的HandleTableList,即句柄表链。活动进程也可以通过这个链表遍历,如图3所示。
图3
需要注意注意的是,ObjectTable结构体内偏移0x008处的UniqueProcessId就是当前EPROCESS对应进程的ID号。同时,ObjectTable结构体内偏移0x004处有一个指针QuotaProcess,指向了当前进程的EPROCESS结构(Sytem进程比较特殊,对应的指针为空)。因而,我们不仅能够得到进程的ID号,而且可以获得进程的其他信息。
编写进程检测的驱动程序
上面我们已经弄清楚了EPROCESS结构中的双向链表,接下来看如何编写驱动程序。
对于偏移0x088处的ActiveProcessLinks,我们先通过函数PsGetCurrentProcess获得任意进程的EPROCESS的地址,该结构偏移0x084处存放进程ID的指针,读取该指针处的ID作为循环的终止判断条件。到EPROCESS的偏移0x088处ActiveProcessLinks中读取当前进程之后的进程的ActiveProcessLinks指针,通过该指针计算其EPROCESS地址。如此循环,即可以获得所有活动进程的信息。
plist_active_procs=(PLIST_ENTRY)(eproc+FLINKOFFSET);
eproc=(ULONG)plist_active_procs->Flink;
eproc=eproc-FLINKOFFSET;
current_PID=*((int *)(eproc+PIDOFFSET));
if(current_PID<0)
//读取到System Process Idle时,这个值是一个异常的负数,因而作置零处理
current_PID=0;
DbgPrint("process id %4d,address %8x ",current_PID,eproc);
对于偏移0x0c4 处的ObjectTable,依然是先通过函数PsGetCurrentProcess获得任意进程的EPROCESS地址,进而计算得到ObjectTable的地址。记录当前的ObjectTable地址作为循环终止判断条件。到ObjectTable中偏移0x008处获得进程ID,偏移0x004处QuotaProcess读取EPROCESS地址。经过循环,即可获得所有进程的信息。
do {
pid=*(PULONG)((ULONG)HandleTableList+0x08-0x1c);
if(pid!=4)
addr=*(PULONG)((ULONG)HandleTableList+0x04-0x1c);
else
addr=0x0;
DbgPrint("process id %4d,address %8x ",pid,addr);
if(pid!=4)
addr=*(PULONG)((ULONG)HandleTableList+0x08-0x1c);
HandleTableList=HandleTableList->Flink;
} while(start_list!=HandleTableList);
由于在Windows XP下System的ObjectTable偏移0x004处QuotaProcess为null,是一个异常值,不可读,因而我将其直接置零。另外,通过这个方法无法读取System Process Idle的地址,运行时显示为0。
小结
Windows系统中的EPROCESS结构是一个非常重要的结构,与系统进程、线程管理密切相关。本文为了便于说明,只给出了Windows XP SP2下的具体实现,在Windows 2000和Windows 2003下这个方法是可行的,但我们只需在驱动中判断系统版本,然后给出不同的系统偏移量即可。同时,在EPROCESS结构中还存在着其他重要的进程特征,通过分析该结构可以找到其他进程检测的方法
补充:综合编程 , 其他综合 ,