简介
线程所属的父进程 _EPROCESS
结构体中的 ThreadListHead
成员是当前进程中所有线程的双向链表头,该成员有两个,分别在 0x50
和 0x190
处。我们可以通过该线程链表头进行线程遍历,也可以通过直接遍历 _ETHREAD
结构体中的 ThreadListEntry
成员实现遍历进程中的所有线程,ThreadListEntry
分别位于 0x1b0
和 0x22c
处。
我们可以通过 _ETHREAD
的 ThreadListEntry
断链实现线程隐藏。需要注意的是,ThreadListEntry
中的地址不能直接置为0,而是因指向自己,否则会触发蓝屏。
关于 _EPRICESS
的结构体,可参考
_EPROCESS断链 —— 实现进程内核隐藏
_ETHREAD
_ETHREAD
的结构如下:
kd> dt _ETHREAD
nt!_ETHREAD
+0x000 Tcb : _KTHREAD //“可等待”对象,比如Mutex互斥体、Event事件等(WaitForSingleObject)
/*
kd> dt _KTHREAD
nt!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY
+0x018 InitialStack : Ptr32 Void // 记录R0栈顶初始地址, 用于线程切换
+0x01c StackLimit : Ptr32 Void // 记录R0栈界限
+0x020 Teb : Ptr32 Void // R3 _TEB, Thread Environment Block, 线程环境块
// fs[0]: R3 表示_TEB; R0表示 KPCR
+0x024 TlsArray : Ptr32 Void
+0x028 KernelStack : Ptr32 Void // 记录R0栈顶, 用于线程切换时, 填充TSS的ESP0
+0x02c DebugActive : UChar // 当前线程是否处于调试状态
// 如果值为-1 不能使用调试寄存器:Dr0 - Dr7
+0x02d State : UChar // 当前线程的状态, 就绪、阻塞
+0x02e Alerted : [2] UChar
+0x030 Iopl : UChar
+0x031 NpxState : UChar
+0x032 Saturation : Char
+0x033 Priority : Char
+0x034 ApcState : _KAPC_STATE // APC 相关
+0x04c ContextSwitches : Uint4B
+0x050 IdleSwapBlock : UChar
+0x051 Spare0 : [3] UChar
+0x054 WaitStatus : Int4B
+0x058 WaitIrql : UChar
+0x059 WaitMode : Char
+0x05a WaitNext : UChar
+0x05b WaitReason : UChar
+0x05c WaitBlockList : Ptr32 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY // 等待链表
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY // 就绪链表
+0x068 WaitTime : Uint4B
+0x06c BasePriority : Char // 其初始值是所属进程的BasePriority值,
// 以后可以通过KeSetBasePriorityThread()函数重新设定
+0x06d DecrementCount : UChar
+0x06e PriorityDecrement : Char
+0x06f Quantum : Char
+0x070 WaitBlock : [4] _KWAIT_BLOCK // 等待哪个对象(WaitForSingleObject)
+0x0d0 LegoData : Ptr32 Void
+0x0d4 KernelApcDisable : Uint4B
+0x0d8 UserAffinity : Uint4B
+0x0dc SystemAffinityActive : UChar
+0x0dd PowerState : UChar
+0x0de NpxIrql : UChar
+0x0df InitialNode : UChar
+0x0e0 ServiceTable : Ptr32 Void //系统服务表
+0x0e4 Queue : Ptr32 _KQUEUE
+0x0e8 ApcQueueLock : Uint4B // APC相关
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY
+0x120 SoftAffinity : Uint4B
+0x124 Affinity : Uint4B
+0x128 Preempted : UChar
+0x129 ProcessReadyQueue : UChar
+0x12a KernelStackResident : UChar
+0x12b NextProcessor : UChar
+0x12c CallbackStack : Ptr32 Void
+0x130 Win32Thread : Ptr32 Void
+0x134 TrapFrame : Ptr32 _KTRAP_FRAME // 进0环时保存环境
+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE // APC 相关
+0x140 PreviousMode : Char //先前模式,记录先前是0环,还是3环
+0x141 EnableStackSwap : UChar
+0x142 LargeStack : UChar
+0x143 ResourceIndex : UChar
+0x144 KernelTime : Uint4B
+0x148 UserTime : Uint4B
+0x14c SavedApcState : _KAPC_STATE // APC 相关
+0x164 Alertable : UChar
+0x165 ApcStateIndex : UChar
+0x166 ApcQueueable : UChar
+0x167 AutoAlignment : UChar
+0x168 StackBase : Ptr32 Void
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY // 双向链表, 记录进程中的所有模块
+0x1b8 FreezeCount : Char
+0x1b9 SuspendCount : Char
+0x1ba IdealProcessor : UChar
+0x1bb DisableBoost : UChar
*/
+0x1c0 CreateTime : _LARGE_INTEGER
+0x1c0 NestedFaultCount : Pos 0, 2 Bits
+0x1c0 ApcNeeded : Pos 2, 1 Bit
+0x1c8 ExitTime : _LARGE_INTEGER
+0x1c8 LpcReplyChain : _LIST_ENTRY
+0x1c8 KeyedWaitChain : _LIST_ENTRY
+0x1d0 ExitStatus : Int4B
+0x1d0 OfsChain : Ptr32 Void
+0x1d4 PostBlockList : _LIST_ENTRY
+0x1dc TerminationPort : Ptr32 _TERMINATION_PORT
+0x1dc ReaperLink : Ptr32 _ETHREAD
+0x1dc KeyedWaitValue : Ptr32 Void
+0x1e0 ActiveTimerListLock : Uint4B
+0x1e4 ActiveTimerListHead : _LIST_ENTRY
+0x1ec Cid : _CLIENT_ID
/*
kd> dt _CLIENT_ID
nt!_CLIENT_ID
+0x000 UniqueProcess : Ptr32 Void // PID
+0x004 UniqueThread : Ptr32 Void // ThreadID
*/
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : Ptr32 Void
+0x208 LpcWaitingOnPort : Ptr32 Void
+0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
+0x210 IrpList : _LIST_ENTRY
+0x218 TopLevelIrp : Uint4B
+0x21c DeviceToVerify : Ptr32 _DEVICE_OBJECT
+0x220 ThreadsProcess : Ptr32 _EPROCESS // 指向父进程的 _EPROCESS 结构体
+0x224 StartAddress : Ptr32 Void
+0x228 Win32StartAddress : Ptr32 Void
+0x228 LpcReceivedMessageId : Uint4B
+0x22c ThreadListEntry : _LIST_ENTRY // 双向链表, 记录进程中的所有模块
+0x234 RundownProtect : _EX_RUNDOWN_REF
+0x238 ThreadLock : _EX_PUSH_LOCK
+0x23c LpcReplyMessageId : Uint4B
+0x240 ReadClusterSize : Uint4B
+0x244 GrantedAccess : Uint4B
+0x248 CrossThreadFlags : Uint4B
+0x248 Terminated : Pos 0, 1 Bit
+0x248 DeadThread : Pos 1, 1 Bit
+0x248 HideFromDebugger : Pos 2, 1 Bit
+0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
+0x248 SystemThread : Pos 4, 1 Bit
+0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
+0x248 BreakOnTermination : Pos 6, 1 Bit
+0x248 SkipCreationMsg : Pos 7, 1 Bit
+0x248 SkipTerminationMsg : Pos 8, 1 Bit
+0x24c SameThreadPassiveFlags : Uint4B
+0x24c ActiveExWorker : Pos 0, 1 Bit
+0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
+0x24c MemoryMaker : Pos 2, 1 Bit
+0x250 SameThreadApcFlags : Uint4B
+0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
+0x250 LpcExitThreadCalled : Pos 1, 1 Bit
+0x250 AddressSpaceOwner : Pos 2, 1 Bit
+0x254 ForwardClusterOnly : UChar
+0x255 DisablePageFaultClustering : UChar
Code
NTSTATUS _NtThreadHide(ULONG uPid)
{
PEPROCESS pEPROCESS;
LPSTR lpImageFileName;
__asm
{
mov eax, fs: [0x124]
mov eax, [eax + 0x220]
mov pEPROCESS, eax
}
PLIST_ENTRY pHeader = (PCHAR)pEPROCESS + 0x88;
PLIST_ENTRY pCur = pHeader;
do
{
if (*(PLONG)((ULONG)pCur - 4) == uPid)
{
PLIST_ENTRY pEThreadHeader1, pEThreadHeader2, pEThreadCur, pEThreadNext;
ULONG uSizeOfLIST_ENTRY = sizeof(LIST_ENTRY);
lpImageFileName = (PCHAR)pCur + 0xec;
DbgPrint("该进程名称: %s\n", lpImageFileName);
pEThreadHeader1 = (PCHAR)pCur - 0x88 + 0x50; // 模块链表头
pEThreadHeader2 = (PCHAR)pCur - 0x88 + 0x190; // 模块链表头
pEThreadCur = pEThreadHeader1;
do
{
pEThreadNext = pEThreadCur->Blink;
pEThreadCur->Flink = pEThreadCur;
pEThreadCur->Blink = pEThreadCur;
pEThreadCur = pEThreadNext;
} while (pEThreadHeader1 == pEThreadCur);
pEThreadCur = pEThreadHeader2;
do
{
pEThreadNext = pEThreadCur->Blink;
pEThreadCur->Flink = pEThreadCur; // 指向自己, 不能直接置为0, 否则会触发蓝屏
pEThreadCur->Blink = pEThreadCur;
pEThreadCur = pEThreadNext;
} while (pEThreadHeader1 == pEThreadCur);
DbgPrint("线程隐藏成功!\n");
return STATUS_SUCCESS;
}
pCur = pCur->Blink;
} while (pCur != pHeader);
return STATUS_ABANDONED;
}
测试进程的 PID
为 980,有一个线程。
当执行 _EtHReAD
断链后,线程数变为了0,说明成功实现了对线程的隐藏。