隐藏系统线程
线程内核对象KTHREAD的ThreadListEntry链接了属于同一个进程的所有线程内核对象。应用层通过ZwQueryInformationThread和进程快照枚举线程就是枚举的这个链表,将此链表进行断链后就可以隐藏线程。
上述隐藏手段只是一种欺骗手段,无法做到真正的隐藏。对抗方法是可以根据PspCidTable全局句柄表找到系统中所有的线程。所有还需要将PspCidTable全局句柄表擦除,之后将ETHREAD的StartAddress、Cid.UniqueThread、Cid.UniqueProcess、Win32StartAddress都擦除即可。
检测断链后的系统线程
因为线程要想运行一定会被操作系统调度执行,所以可以通过检测当前所有处理器正在运行的线程来检测是否存在隐藏的线程。
!running
可以查看系统所有正在运行的线程
!ready
可以查看系统所有处于就绪队列的线程
这些正在运行的线程和处于就绪队列中的线程信息都保存在各个处理器对应的KPRC结构中
0: kd> dt _KPCR
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : Ptr64 _KGDTENTRY64
+0x008 TssBase : Ptr64 _KTSS64
+0x010 UserRsp : Uint8B
+0x018 Self : Ptr64 _KPCR
+0x020 CurrentPrcb : Ptr64 _KPRCB
+0x028 LockArray : Ptr64 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : Ptr64 Void
+0x038 IdtBase : Ptr64 _KIDTENTRY64
+0x040 Unused : [2] Uint8B
+0x050 Irql : UChar
+0x051 SecondLevelCacheAssociativity : UChar
+0x052 ObsoleteNumber : UChar
+0x053 Fill0 : UChar
+0x054 Unused0 : [3] Uint4B
+0x060 MajorVersion : Uint2B
+0x062 MinorVersion : Uint2B
+0x064 StallScaleFactor : Uint4B
+0x068 Unused1 : [3] Ptr64 Void
+0x080 KernelReserved : [15] Uint4B
+0x0bc SecondLevelCacheSize : Uint4B
+0x0c0 HalReserved : [16] Uint4B
+0x100 Unused2 : Uint4B
+0x108 KdVersionBlock : Ptr64 Void
+0x110 Unused3 : Ptr64 Void
+0x118 PcrAlign1 : [24] Uint4B
+0x180 Prcb : _KPRCB
_KPCR中内嵌了一个_KPRCB结构,此结构的CurrentThread,NextThread,DispatcherReadyListHead,SharedReadyQueue 分别存放了正在运行的,即将运行的,就绪的线程。
0: kd> dt _KPRCB
nt!_KPRCB
+0x000 MxCsr : Uint4B
+0x004 LegacyNumber : UChar
+0x005 ReservedMustBeZero : UChar
+0x006 InterruptRequest : UChar
+0x007 IdleHalt : UChar
+0x008 CurrentThread : Ptr64 _KTHREAD //当前线程(Running)
+0x010 NextThread : Ptr64 _KTHREAD //下一个要运行的线程
+0x018 IdleThread : Ptr64 _KTHREAD //空闲线程
.....
+0x2d08 DeferredReadyListHead : _SINGLE_LIST_ENTRY //延迟就绪
+0x7c00 WaitListHead : _LIST_ENTRY //等待
+0x7c10 WaitLock : Uint8B
+0x7c18 ReadySummary : Uint4B
+0x7c1c AffinitizedSelectionMask : Int4B
+0x7c20 QueueIndex : Uint4B
+0x7c24 PrcbPad75 : [2] Uint4B
+0x7c2c DpcWatchdogSequenceNumber : Uint4B
+0x7c30 TimerExpirationDpc : _KDPC
+0x7c70 ScbQueue : _RTL_RB_TREE
+0x7c80 DispatcherReadyListHead : [32] _LIST_ENTRY //就绪
.....
+0x8440 SharedReadyQueueMask : Uint8B
+0x8448 SharedReadyQueue : Ptr64 _KSHARED_READY_QUEUE //共享就绪
设置DPC延迟过程调用
通过向所有的处理器设置DPC延迟调用来获取这些信息并判断是否存在隐藏的线程。
PKPCR p_current_kpcr;
p_current_kpcr = GetCurrentKpcr();
if (!MmIsAddressValid(p_current_kpcr)) {
return;
}
//CurrentThread
PVOID current_kprcb = p_current_kpcr->CurrentPrcb;
_KTHREAD* p_kthread = NULL;
p_kthread = *(_KTHREAD**)((UCHAR*)current_kprcb + CurrentThread);
if (MmIsAddressValid(p_kthread)) {
DbgPrint(" irql = %d,count=%d ,number = %d, tid: %p\r\n", KeGetCurrentIrql(), ++count, KeGetCurrentProcessorNumber(), PsGetThreadId(p_kthread));
}
//NextThread
p_kthread = *(_KTHREAD**)((UCHAR*)current_kprcb + NextThread);
if (MmIsAddressValid(p_kthread)) {
DbgPrint(" irql = %d,count=%d ,number = %d, tid: %p\r\n", KeGetCurrentIrql(), ++count, KeGetCurrentProcessorNumber(), PsGetThreadId(p_kthread));
}
//enum DispatcherReadyListHead
for (int i = 0; i < 32; i++) {
LIST_ENTRY* ready_list_head = (LIST_ENTRY*)((UCHAR*)current_kprcb + DispatcherReadyListHead) + i;
LIST_ENTRY* ready_list_entry = ready_list_head->Flink;
while (ready_list_entry != ready_list_head) {
p_kthread = (PKTHREAD)((UCHAR*)ready_list_entry - WaitListEntry);
if (MmIsAddressValid(p_kthread) ) {
DbgPrint(" irql = %d,count=%d ,number = %d, tid: %p\r\n", KeGetCurrentIrql(), ++count, KeGetCurrentProcessorNumber(), PsGetThreadId(p_kthread));
}
ready_list_entry = ready_list_entry->Flink;
}
}
//SharedReadyQueue
for (int i = 0; i < 32; i++) {
PVOID share_ready_queue = *(PVOID*)((UCHAR*)current_kprcb + SharedReadyQueue);
LIST_ENTRY* ready_list_head = (LIST_ENTRY*)((UCHAR*)share_ready_queue + 0x10) + i;
LIST_ENTRY* ready_list_entry = ready_list_head->Flink;
while (ready_list_entry != ready_list_head) {
p_kthread = (PKTHREAD)((UCHAR*)ready_list_entry - WaitListEntry);
if (MmIsAddressValid(p_kthread)) {
DbgPrint(" irql = %d,count=%d ,number = %d, tid: %p\r\n", KeGetCurrentIrql(), ++count, KeGetCurrentProcessorNumber(), PsGetThreadId(p_kthread));
}
ready_list_entry = ready_list_entry->Flink;
}
}
设置NMI回调
NMI是一种不可屏蔽的中断,CPU在收到此中断时必须要调用对应的中断处理程序。通过设置NMI回调并定期向每一个CPU发送NMI中断可以检测检测当前CPU核心的线程信息。
测试
可以发现被断链隐藏的系统线程,通过进一步的堆栈回溯可以判断线程是否合法。
参考:
https://bbs.pediy.com/thread-273980.htm