-
-
[原创]遍历线程调度链表辅助检测进程
-
发表于:
2009-12-6 00:15
16132
-
以前就有了,只是还是自己实现了一下,当做复习吧,大侠飘过。进程的隐藏一直是木马程序设计者不断探求的重要技术,目前Rootkit使用的主要的进程隐藏技术有:通过hook内核API来来隐藏,通过从进程链表上摘除自身来隐藏。这样对于进程普通的API列举将不会得到真实的数据,为了获取可信进程信息,就要要采用其他的方法。
众所周知,windows执行的基本单位是线程,而不是进程,所以才有从进程链表上摘除自身的进程隐藏方法,这是虽然从进程链表上摘除了自身,但不会影响操作系统的调度,所以不影响程序运行。但是不能从线程链表上摘除自身,除非程序不想获得cpu时间了。在内核里和线程调度有关的结构是TCB,在ETHREAD里的KTHREAD里,和本文有关的域有如下几个:
Typedef _KTHREAD {
……
KAPC_STATE ApcState;
LIST_ENTRY WaitListEntry
LIST_ENTRY QueueListEntry
LIST_ENTRY ThreadListEntry
……
};
从网上得来的资料说在windows xp下,windows线程分派器使用pKiDispatcherReadyListHead和pKiWaitListHead这两个结构来调度线程。它的线程分派主要利用KTHREAD中的WaitListEntry 、QueueListEntry和ThreadListEntry来完成,这三个成员变量代表了三种不同的线程状态,是三个双向链表结构,ThreadListEntry队列是处于准备状态的,其余两个处于等待状态。pKiDispatcherReadyListHead是指向一个有32个元素的LIST_ENTRY数组,Windows线程有32个优先级,所以有32个节点。每个数组元素连接一个双向链表。如图一所示
线程调度就是基于这三个链表的。如图所示
基于线程调度链表的隐藏进程检测,就是遍历这个两个链表获取线程,再通过ThreadProcess域得到进程的EPROCES。但是自己写出程序测试时发现没这么简单,在虚拟机里面pKiWaitListHead我目前测试的情况来看都是对应WaitListEntry,没有发现过QueueListEntry里面的线程,而且pKiDispatcherReadyListHead对应的都是在WaitListEntry里面的元素,相当无语。不知道是不是还有什么其他未知的数据结构,知道的大侠还请不吝赐教。首先要解决的问题是如何得到pKiDispatcherReadyListHead和pKiWaitListHead,已经有前辈总结过了,使用KiDispatcherReadyListHead的例程有:KeSetAffinityThread、KiFindReadyThread、KiReadyThread、KiSetPriorityThread、NtYieldExecution、KiScanReadyQueues、KiSwapThread。系统中用到KiWaitInListHead的例程:KeWaitForSingleObject()、 KeWaitForMultipleObject()、 KeDelayExecutionThread、 KiOutSwapKernelStacks。我自己实现的代码如下:
ULONG FindKiWaitListHead()
{ UCHAR* Addre=(UCHAR*)KeDelayExecutionThread;//这个WDK有申明
ULONG index=0; for(;index<=1000;index++)
{ if(*(unsigned short*)Addre==0x3c7)//unsigned short 就是汇编里的word
{if(*(unsigned short*)(Addre+6)==0x4389)
KdPrint(("find the KiWaitListHead address :%08X",*(ULONG*)(Addre+2))); break;
}
else
{ Addre++;
}
}
if(index>1000)
{ KdPrint(("没有找到KiWaitListHead"));
}
return *(ULONG*)(Addre+2);
}
搜索KiDispatcherReadyListHead的代码有点长,就不贴出来了,实现思路是先由导出函数KeSetAffinityThread;获得KiSetAffinityThread的地址,从这个地址往上搜找到一个CMP指令,这个指令再上面的一个DWORD就是KiDispatcherReadyListHead了。具体实现请看附件,也可以用其他的方法获得,比如NtYieldExecution,这个进去之后一个lea指令就装载了KiDispatcherReadyListHead的地址,就是这个函数地址要由SSDT获取,不知道为什么没有导出,却在ICeSword下看到,用MmGetSystemRoutineAddress没办法得到,觉得不稳妥,就没有使用这个方法了。下面就开始列进程了,但是还有一个问题就是怎么知道KiWaitListEntry链表里的的指针到底是指向WaitListEntry 还是QueueListEntry呢,应该是还有一个域来表征的,可是我对ETHREAD结构学习不深,实际也很难找到完整资料来描述其中各个域的资料,知道的还望不吝赐教。在这里使用了ServiceTable 来判断,已经知道了他们的偏移,可以知道他们的偏移之差。ServiceTable和QueueListEntry的偏移之差是0x38,和WaitListEntry的偏移之差是0x80,,通过判断这个偏移差就可以知道到底是那种情况了。再插一点,有的读者可能对ServiceTable不太清楚,这个位,在线程调用任何GUI函数之前指向的是SSDT,调用过GUI函数之后就变成shadow ssdt,具体可以参考《深入解析windows操作系统》一书的有关章节。
找到这两个结构之后就就可以遍历链表来获取进程信息了,遍历链表的代码很简单
NTSTATUS ListProcessInWaitList(PLIST_ENTRY List)
{//列举处于等待状态的进程
if(!List)
return STATUS_UNSUCCESSFUL;
PLIST_ENTRY Seek=List->Flink;
ULONG eproc;
int ret;
int Temp=0;
KSPIN_LOCK Lock;
KeInitializeSpinLock(&Lock);
KIRQL irql=KeRaiseIrqlToDpcLevel();
KeAcquireSpinLockAtDpcLevel(&Lock);
while(Seek!=List)
{
ret=IsWaitList(Seek);
if(ret==0)//Seek此时是在WaitListEntry里
{
eproc=GetProcessFormWaitList(Seek,WAITLIST);
DisplayProcess(eproc);
}
else if(ret==1)
{
eproc=GetProcessFormWaitList(Seek,QUEUE_LISTENTRY);
DisplayProcess(eproc);
}
else
{
KdPrint(("没有得到有效的数据\n"));
}
Seek=Seek->Flink;
Temp++;
}
KeReleaseSpinLockFromDpcLevel(&Lock);
KeLowerIrql(irql);
KdPrint(("total %d thread in waitList!\n",Temp));
return STATUS_SUCCESS;
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!