首页
社区
课程
招聘
[原创]遍历线程调度链表辅助检测进程
发表于: 2009-12-6 00:15 16015

[原创]遍历线程调度链表辅助检测进程

2009-12-6 00:15
16015
以前就有了,只是还是自己实现了一下,当做复习吧,大侠飘过。进程的隐藏一直是木马程序设计者不断探求的重要技术,目前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;
}

下面一个主要函数是列举处于KiDispatcherReadyListHead队列里的线程从而得到进程。其主要代码为:
for(int i=0;i<32;i++)
	{ 
		for( Seek=ListTemp[i].Flink;Seek!=&ListTemp[i];Seek=Seek->Flink)
		{    Temp++;
			ret=IsWaitList(Seek);
			if(ret==0)
			{
			eproc=GetProcessFormWaitList(Seek,WAITLIST);
			if(eproc==0)
			{
				KdPrint(("获取EPROCESS地址失败\n"));
				continue;
			}
			DisplayProcess(eproc);
			}
			else if(ret==1)
			{
			eproc=GetProcessFormWaitList(Seek,QUEUE_LISTENTRY);
			if(eproc==0)
			{
				KdPrint(("获取EPROCESS地址失败\n"));
				continue;
			}
			DisplayProcess(eproc);
			}
			else
			{eproc=GetProcessFormWaitList(Seek,THREAD_LISTENTRY);
			if(eproc==0)
			{
				KdPrint(("获取EPROCESS地址失败\n"));
				continue;
			}
			//DisplayProcess(eproc);
			}	}  }

现在可以测试一下检测效果了,测试之后就会发现,理论是这样没错,却怎么也检测不到所有线程。不知道为什么,估计原因是线程有不止两种状态,还有转换,备用等状态。测试检测隐藏进程时发现,如果打开一个记事本,一段时间不动它就检测不到了,还有一个更怪的现象是Smss进程我一次都没有检测到过。N次试验后发现,只要进程一段时间没有执行任何代码,比如:就像打开记事本却什么也不做,这种方法就检测不到这个进程了。不过只要进程在近段时间执行过代码,那么不管你怎么隐藏,都会被检测到,后来发现ruk也使用了这种检测方法,但也只是作为一种辅助手段,估计原因就在于此。

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
免费 8
支持
分享
最新回复 (3)
雪    币: 245
活跃值: (11)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
http://www.lunwentianxia.com/product.free.10012963.7/

你写的这个论文?
2009-12-6 01:51
0
雪    币: 239
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
这么好的贴子竟然没人顶。。太奇怪了。
2010-8-13 16:02
0
雪    币: 83
活跃值: (1082)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
4
如果开启dfss 还可以 遍历cpu对应的dfss的cpu配额排序链表  cpu配额等待块链表  +28h就是线程了
2019-11-7 06:44
0
游客
登录 | 注册 方可回帖
返回
//