基于线程调度链表的检测和隐藏技术
来源:百度文库 编辑:神马文学网 时间:2024/04/19 10:49:31
基于线程调度链表的检测和隐藏技术
1.
Windows2000是由执行程序线程(ETHREAD)块表示的,ETHREAD成员都是指向的系统空
间,进程环境块(TEB)除外。ETHREAD块中的第一个结构体就是内核线程(KTHREAD)块。在KTHREAD块中包含了windows2000内核需要访问的信息。这些信息用于执行线程的调度和同步正在运行的线程。
kd> !kthread
struct
+000 struct
+010 struct
+018 void
+01c void
+020 void
+024 void
+028 void
+02c byte
+02d byte
+02e byte
+030 byte
+031 byte
+032 char
+033 char
+034 struct
+034
+044
+04c uint32
+050 int32
+054 byte
+055 char
+056 byte
+057 byte
+058 struct
+05c struct
+064 uint32
+068 char
+069 byte
+06a char
+06b char
+06c struct
+0cc void
+0d0 uint32
+0d4 uint32
+0d8 byte
+0d9 byte
+0da byte
+0db byte
+0dc void
+0e0 struct
+0e4 uint32
+0e8 struct
+110 struct
+118 uint32
+11c byte
+11d byte
+11e byte
+11f byte
+120 void
+124 void
+128 struct
+12c struct
+134 char
+135 byte
+136 byte
+137 byte
+138 uint32
+13c uint32
+140 struct
+158 byte
+159 byte
+15a byte
+15b byte
+15c void
+160 struct
+190 struct
+1a4 struct
+1ac char
+1ad char
+1ae byte
+1af byte
在偏移0x5c处有一个WaitListEntry成员,这个就是用来链接到线程调度链表的。在偏移0x34处有一个ApcState成员结构,在ApcState中的Process域就是指向当前线程关联的进程的KPROCESS块,由于KPROCESS块是EPROCESS块的第一个元素,所以找到了KPROCESS块指针也就是找到了EPROCESS块的指针。找到了EPROCESS就不用多少了,就可以取得当前线程的进程的名字,ID号等。
2.
在windows系统中,线程调度主要分成三条主要的调度链表。分别是KiWaitInListHead,
KiWaitOutListhead,KiDispatcherReadyListHea
3.
void DisplayList(PLIST_ENTRY ListHead)
{
}
以上是对一条链进行进程枚举。所以我们必须找到KiWaitInListHead,KiWaitOutListhead,KiDispatcherReadyListHea
PLIST_ENTRY KiWaitInListHead =
PLIST_ENTRY KiDispatcherReadyListHea
PLIST_ENTRY KiWaitOutListhead =
遍历所有的线程调度链表。
for ( i =0; i<32 ;i++ )
{
}
DisplayList(KiWaitInListHead);
DisplayList(KiWaitOutListhead);
通过上面的那一小段核心代码就能把删除活动进程链表的隐藏进程给查出来。也可以改写一个友好一点的驱动,加入IOCTL,得到的进程信息把打印在DbgView中把它返回给Ring3的应用程序,然后应用程序对返回的数据进行处理,和Ring3级由PSAPI得到的进程对比,然后判断是不是有隐藏的进程。4.
Xfocus上SoBeIt提出了绕过内核调度链表进程检测。详情可以参见原文:
http://www.xfocus.net/articles/200404/693.html
由于现在的基于线程调度的检测系统都是通过内核调试器得硬编码来枚举所有的调度线程的,所以我们完全可以自己创造一个那三个调度链表头,然后把原链表头从链中断开,把自己的申请的链表头接上去。由于线程调度的时候会用到KiFindReadyThread等内核API,在KiFindReadyThread里面又会去访问KiDispatcherReadyListHead,所以我完全可以把KiFindReadyThread中那段访问KiDispatcherReadyListHea
kd> u KiFindReadyThread+0x48
nt!KiFindReadyThread+0x48:
804313db 8d34d5e0224880 lea esi,[nt!KiDispatcherReadyListHea
很明显我们可以在机器码中看到e0224880,由于它是在内存中以byte序列显示的转换成DWORD就是804822e0就是我们KiDispatcherReadyListHead的地址。所以我们要做的就是把[804313db+3]赋值成我们自己申请的一个链头。使其系统以后对原链表头的操作变化成对我们自己申请的链表头的操作。同理用到那三个链表头的还有一些内核API,所以必须找到他们在机器码中含有原表头地址信息的具体地址然后把它全部替换掉。不然系统调度就会出错.系统中用到KiWaitInListHead的例程:KeWaitForSingleObject、 KeWaitForMultipleObject、 KeDelayExecutionThread、 KiOutSwapKernelStacks。用到KiWaitOutListHead的例程和KiWaitInListHead的一样。使用KiDispatcherReadyListHea
申请新的表头空间:
pNewKiWaitInListHead = (PLIST_ENTRY)ExAllocatePool \
pNewKiWaitOutListHead = (PLIST_ENTRY)ExAllocatePool \
pNewKiDispatcherReadyLis
下面仅仅以pNewKiWaitInListHead头为例,其他的表头都是一样的操作。
新调度链表的表头替换:
InitializeListHead(pNewKiWaitInListHead);
把原来的系统链表头摘除,把新的接上去:
pFirstEntry = pKiWaitInListHead->Flink;
pLastEntry = pKiWaitInListHead->Blink;
pNewKiWaitInListHead->Flink = pFirstEntry;
pNewKiWaitInListHead->Blink = pLastEntry;
pFirstEntry->Blink = pNewKiWaitInListHead;
pLastEntry->Flink = pNewKiWaitInListHead;
剩下的就是在原来的线程调度链表上做文章了使其基于线程调度检测系统看不出什么异端.
for(;;)
{
{
pETHREAD = (PETHREAD)(((PCHAR)pEntry)-0x5c);
pEPROCESS = (PEPROCESS)(pETHREAD->Tcb.ApcState.Process);
pFakeETHREAD = ExAllocatePool(PagedPool,sizeof(FAKE_ETHREAD));
}
...休息一段时间
}
首先每过一小段时间就把原来的线程调度链表清空,然后遍历当前的线程调度链,判断链中的每一个KPROCESS块是不是要属于要隐藏的进程线程,如果是就跳过,不是就自己构造一个ETHREAD块把当前的信息拷贝过去,然后把自己构造的ETHREAD块加入到原来的调度链表中。为什么要自己构造一个ETHREAD?其原因主要有2个,其一为了使检测系统看起来更可信,如果仅仅清空原来的线程调度链表那么检测系统将查不出来任何的线程和进程信息,
很明显,这无疑不打自招的说,系统里面已经有东西了。其二,如果把自己构造的ETHREAD块挂接在原调度链表中,检测系统会访问挂在原来调度链表上的ETHREAD块里面的成员,如果不自己构造一个和真实ETHREAD块重要信息一样的块,那么检测系统很有可能出现非法访问,然后就boom兰屏了。
5.
一般情况下我们是通过内核调试器得到那三条链表的内核地址,然后进行枚举。这就给隐藏者留下了机会,如上面所示。但是我们完全可以把上面那种隐藏进程检测出来。我们也通过在内核函数中取得硬编码的办法来分别取得他们的链表头的地址。如上面我们已经看见了 KiFindReadyThread+0x48+3出就是KiDispatcherReadyListHead的地址,如果用上面的绕过内核调度链表检测办法同时也去要修改KiFindReadyThread+0x48+3的值为新链表的头部地址。所以我们的检测系统完全可以从KiFindReadyThread+0x48+3(0x804313de)去取得KiDispatcherReadyListHea