NT内核级进程隐藏3-线程调度链表及Hook内核函数基于线程调度链表的检测和隐藏技术1.什么是ETHREAD和KTHREAD块学习各种外挂制作技术,马上去百度搜索"魔鬼作坊"点击第一个站进入、快速成为做挂达人。
Windows2000是由执行程序线程(ETHREAD)块表示的,ETHREAD成员都是指向的系统空间,进程环境块(TEB)除外。
ETHREAD块中的第一个结构体就是内核线程(KTHREAD)块。
在KTHREAD块中包含了windows2000内核需要访问的信息。
这些信息用于执行线程的调度和同步正在运行的线程。
kd>!kthreadstruct_KTHREAD(sizeof=432)+000struct_DISPA TCHER_HEADER Header+010struct_LIST_ENTRY MutantListHead+018void*InitialStack+01c void*StackLimit+020void*Teb+024void*TlsArray+028void*KernelStack+02c byte DebugActive+02d byte State+02e byte Alerted[2]+030byte Iopl+031byte NpxState+032char Saturation+033char Priority+034struct_KAPC_STATE ApcState+034struct_LIST_ENTRY ApcListHead[2] +044struct_KPROCESS*Process+04c uint32ContextSwitches+050int32WaitStatus+054byte WaitIrql+055char WaitMode+056byte WaitNext+057byte WaitReason+058struct_KWAIT_BLOCK*WaitBlockList +05c struct_LIST_ENTRY WaitListEntry+064uint32WaitTime+068char BasePriority+069byte DecrementCount+06a char PriorityDecrement+06b char Quantum+06c struct_KWAIT_BLOCK WaitBlock[4] +0cc void*LegoData+0d0uint32KernelApcDisable+0d4uint32UserAffinity+0d8byte SystemAffinityActive+0d9byte PowerState+0da byte NpxIrql+0db byte Pad[1]+0dc void*ServiceTable+0e0struct_KQUEUE*Queue+0e4uint32ApcQueueLock+0e8struct_KTIMER Timer+110struct_LIST_ENTRY QueueListEntry+118uint32Affinity+11c byte Preempted+11d byte ProcessReadyQueue+11e byte KernelStackResident+11f byte NextProcessor+120void*CallbackStack+124void*Win32Thread+128struct_KTRAP_FRAME*TrapFrame+12c struct_KAPC_STATE*ApcStatePointer[2] +134char PreviousMode+135byte EnableStackSwap+136byte LargeStack+137byte ResourceIndex+138uint32KernelTime+13c uint32UserTime+140struct_KAPC_STATE SavedApcState+158byte Alertable+159byte ApcStateIndex+15a byte ApcQueueable+15b byte AutoAlignment+15c void*StackBase+160struct_KAPC SuspendApc+190struct_KSEMAPHORE SuspendSemaphore+1a4struct_LIST_ENTRY ThreadListEntry+1ac char FreezeCount+1ad char SuspendCount+1ae byte IdealProcessor+1af byte DisableBoost在偏移0x5c处有一个WaitListEntry成员,这个就是用来链接到线程调度链表的。
在偏移0x34处有一个ApcState成员结构,在ApcState中的Process域就是指向当前线程关联的进程的KPROCESS块,由于KPROCESS块是EPROCESS块的第一个元素,所以找到了KPROCESS块指针也就是找到了EPROCESS块的指针。
找到了EPROCESS就不用多少了,就可以取得当前线程的进程的名字,ID号等。
2.线程调度在windows系统中,线程调度主要分成三条主要的调度链表。
分别是KiWaitInListHead,KiWaitOutListhead,KiDispatcherReadyListHead,分别是两条阻塞链,一条就绪链表,当线程获得CPU执行的时候,系统分配一个时间片给线程,当发生一次时钟中断就从分配的时间片上减去一个时钟中断的值,如果这个值小于零了也就是时间片用完了,那么这个线程根据其优先级载入到相应的就绪队列末尾。
KiDispatcherReadyListHead是一个数组链的头部,在windows2000中它包含有32个队列,分别对应线程的32个优先级。
如果线程因为同步,或者是对外设请求,那么阻塞线程,让出CPU的所有权,加如到阻塞队列里面去。
CPU从就绪队列里面,按照优先权的前后,重新调度新的线程的执行。
当阻塞队列里面的线程获得所需求的资源,或者是同步完成就又重新加到就绪队列里面等待执行。
3.通过线程调度链表进行隐藏进程的检测void DisplayList(PLIST_ENTRY ListHead){PLIST_ENTRY List=ListHead->Flink;if(List==ListHead){//DbgPrint("return\n");return;}PLIST_ENTRY NextList=List;while(NextList!=ListHead){PKTHREAD Thread=ONTAINING_RECORD(NextList,KTHREAD,WaitListEntry); PKPROCESS Process=Thread->ApcState.Process;PEPROCESS pEprocess=(PEPROCESS)Process;DbgPrint("ImageFileName=%s\n",pEprocess->ImageFileName);NextList=NextList->Flink;}}以上是对一条链进行进程枚举。
所以我们必须找到KiWaitInListHead,KiWaitOutListhead,KiDispatcherReadyListHead的地址,由于他们都没有被ntoskrnl.exe导出来,所以只有通过硬编码的办法给他们赋值。
通过内核调试器,能找到(windows2000sp4):PLIST_ENTRY KiWaitInListHead=(PLIST_ENTRY)0x80482258;PLIST_ENTRY KiDispatcherReadyListHead=(PLIST_ENTRY)0x804822e0;PLIST_ENTRY KiWaitOutListhead=(PLIST_ENTRY)0x80482808;遍历所有的线程调度链表。
for(i=0;i<32;i++){DisplayList(KiDispatcherReadyListHead+i);}DisplayList(KiWaitInListHead);DisplayList(KiWaitOutListhead);通过上面的那一小段核心代码就能把删除活动进程链表的隐藏进程给查出来。
也可以改写一个友好一点的驱动,加入IOCTL,得到的进程信息把打印在DbgView中把它返回给Ring3的应用程序,然后应用程序对返回的数据进行处理,和Ring3级由PSAPI得到的进程对比,然后判断是不是有隐藏的进程。
4.绕过内核调度链表隐藏进程。
Xfocus上SoBeIt提出了绕过内核调度链表进程检测。
详情可以参见原文: /articles/200404/693.html由于现在的基于线程调度的检测系统都是通过内核调试器得硬编码来枚举所有的调度线程的,所以我们完全可以自己创造一个那三个调度链表头,然后把原链表头从链中断开,把自己的申请的链表头接上去。
由于线程调度的时候会用到KiFindReadyThread等内核API,在KiFindReadyThread里面又会去访问KiDispatcherReadyListHead,所以我完全可以把KiFindReadyThread中那段访问KiDispatcherReadyListHead的机器码修改了,把原KiDispatcherReadyListHead的地址改成我们新申请的头。
kd>u KiFindReadyThread+0x48nt!KiFindReadyThread+0x48:804313db8d34d5e0224880lea esi,[nt!KiDispatcherReadyListHead(804822e0)+edx*8]很明显我们可以在机器码中看到e0224880,由于它是在内存中以byte序列显示的转换成DWORD就是804822e0就是我们KiDispatcherReadyListHead的地址。
所以我们要做的就是把[804313db+3]赋值成我们自己申请的一个链头。
使其系统以后对原链表头的操作变化成对我们自己申请的链表头的操作。
同理用到那三个链表头的还有一些内核API,所以必须找到他们在机器码中含有原表头地址信息的具体地址然后把它全部替换掉。
不然系统调度就会出错.系统中用到KiWaitInListHead的例程:KeWaitForSingleObject、KeWaitForMultipleObject、KeDelayExecutionThread、KiOutSwapKernelStacks。