# ETHREAD
概述:
- 每个 Windows 线程在 0 环都有一个对应的
ETHREAD 结构体
- 该结构体包含了线程的所有重要信息
kd> dt _ETHREAD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| ntdll!_ETHREAD +0x000 Tcb : _KTHREAD +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 +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 +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
|
注意: EPROCESS 中有两条线程链表 —— +0x050 ThreadListHead (位于 KPROCESS 中)和 +0x190 ThreadListHead (位于 EPROCESS 中)。它们分别对应 KTHREAD+0x1b0 和 ETHREAD+0x22c 处的 ThreadListEntry 。
# +0x000 Tcb : _KTHREAD
_KTHREAD 包含了线程的调度状态、上下文信息、优先级等内核管理所需的关键数据。
kd> dt _KTHREAD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| ntdll!_KTHREAD +0x000 Header : _DISPATCHER_HEADER +0x010 MutantListHead : _LIST_ENTRY +0x018 InitialStack : Ptr32 Void +0x01c StackLimit : Ptr32 Void +0x020 Teb : Ptr32 Void +0x024 TlsArray : Ptr32 Void +0x028 KernelStack : Ptr32 Void +0x02c DebugActive : UChar +0x02d State : UChar +0x02e Alerted : [2] UChar +0x030 Iopl : UChar +0x031 NpxState : UChar +0x032 Saturation : Char +0x033 Priority : Char +0x034 ApcState : _KAPC_STATE +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 +0x06d DecrementCount : UChar +0x06e PriorityDecrement : Char +0x06f Quantum : Char +0x070 WaitBlock : [4] _KWAIT_BLOCK +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 +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 +0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE +0x140 PreviousMode : Char +0x141 EnableStackSwap : UChar +0x142 LargeStack : UChar +0x143 ResourceIndex : UChar +0x144 KernelTime : Uint4B +0x148 UserTime : Uint4B +0x14c SavedApcState : _KAPC_STATE +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
|
# 练习:使用 WinDbg 断开进程的某个线程
思考:
- 断链后,在调试器(如 OllyDbg)中还能不能找到被断开的线程?
- 断链后,进程运行过程中会不会出现异常?
# 实验步骤
一、 编译以下代码,程序包含主线程和子线程,两者分别每 5 秒打印一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <stdio.h> #include <windows.h>
DWORD WINAPI child_thread_func(LPVOID lpParam) { while (1) { printf("Child Thread\n"); Sleep(5000); } return 0; }
int main() { HANDLE child_thread;
child_thread = CreateThread( NULL, 0, child_thread_func, NULL, 0, NULL );
if (child_thread == NULL) { printf("Failed to create thread! Error: %d\n", GetLastError()); return 1; }
while (1) { printf("Main Thread\n"); Sleep(5000); }
CloseHandle(child_thread); return 0; }
|
二、 使用 OllyDbg 运行程序,查看线程列表,可以看到与任务管理器中显示的线程数相同。
运行效果:
三、 在 WinDbg 中使用 !process 0 0 找到该程序的进程。
四、 查看进程结构体。由于 KPROCESS 是 EPROCESS 的第一个成员,两者使用同一个地址。
1 2 3 4 5 6 7 8
| kd> dt _EPROCESS 85eaa4b0 nt!_EPROCESS +0x000 Pcb : _KPROCESS ... +0x174 ImageFileName : [16] "test.exe" ... +0x190 ThreadListHead : _LIST_ENTRY [ 0x8604c404 - 0x85ec724c ] ...
|
1 2 3 4 5
| kd> dt _KPROCESS 85eaa4b0 nt!_KPROCESS ... +0x050 ThreadListHead : _LIST_ENTRY [ 0x8604c388 - 0x85ec71d0 ] ...
|
五、 逐一检查线程节点,定位子线程。
通过 KPROCESS.ThreadListHead 遍历(节点位于 KTHREAD+0x1b0 ):
1 2 3 4
| kd> dt _CLIENT_ID 0x8604c388-1b0+1ec nt!_CLIENT_ID +0x000 UniqueProcess : 0x000001a4 Void +0x004 UniqueThread : 0x00000700 Void // 主线程
|
1 2 3 4
| kd> dt _CLIENT_ID 0x85ec71d0-1b0+1ec nt!_CLIENT_ID +0x000 UniqueProcess : 0x000001a4 Void +0x004 UniqueThread : 0x000006d8 Void // 子线程
|
通过 EPROCESS.ThreadListHead 遍历(节点位于 ETHREAD+0x22c ),同样可以定位到 TID 0x700 (主线程)和 0x6d8 (子线程)。
六、 将子线程从两条链表中断链。
1 2 3 4 5 6 7
| kd> ed (0x85eaa500+4) 0x8604c388 kd> ed 0x8604c388 0x85eaa500
kd> ed (0x85eaa640+4) 0x8604c404 kd> ed 0x8604c404 0x85eaa640
|
七、 恢复系统运行,可以发现任务管理器中线程数由 2 变为 1,但 OllyDbg 中仍然显示两个线程。
八、 查看程序运行情况,子线程仍在正常打印。
# 实验结论
- 任务管理器通过
ThreadListHead 链表判断线程数量,断链后线程 "消失"
- 系统调度线程并不依赖
ThreadListHead ,因此断链不影响线程的实际运行
- OllyDbg 如果在断链前已经附加到进程,它会保留之前获取的线程信息,仍能看到两个线程
补充说明:
如果先对线程进行断链,然后使用 OllyDbg 通过附加的方式调试,此时只能观察到一个线程。此外,使用 OllyDbg 暂停程序时,由于子线程不在链表中,不会被暂停,子线程将继续运行。
# KPCR(Processor Control Region,CPU 控制区)
- 当线程进入 0 环时,
FS:[0] 指向 KPCR(3 环时指向 TEB)
- 每个 CPU 都有一个 KPCR 结构体(一个核对应一个 KPCR)
- KPCR 中存储了 CPU 运行所需的重要数据:GDT、IDT 以及线程相关信息
dt _KPCR
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| nt!_KPCR +0x000 NtTib : _NT_TIB +0x01c SelfPcr : Ptr32 _KPCR +0x020 Prcb : Ptr32 _KPRCB +0x024 Irql : UChar +0x028 IRR : Uint4B +0x02c IrrActive : Uint4B +0x030 IDR : Uint4B +0x034 KdVersionBlock : Ptr32 Void +0x038 IDT : Ptr32 _KIDTENTRY +0x03c GDT : Ptr32 _KGDTENTRY +0x040 TSS : Ptr32 _KTSS +0x044 MajorVersion : Uint2B +0x046 MinorVersion : Uint2B +0x048 SetMember : Uint4B +0x04c StallScaleFactor : Uint4B +0x050 DebugActive : UChar +0x051 Number : UChar +0x052 Spare0 : UChar +0x053 SecondLevelCacheAssociativity : UChar +0x054 VdmAlert : Uint4B +0x058 KernelReserved : [14] Uint4B +0x090 SecondLevelCacheSize : Uint4B +0x094 HalReserved : [16] Uint4B +0x0d4 InterruptMode : Uint4B +0x0d8 Spare1 : UChar +0x0dc KernelReserved2 : [17] Uint4B +0x120 PrcbData : _KPRCB
|
# +0x000 NtTib : _NT_TIB
_NT_TIB (NT Thread Information Block)用于存储线程的异常处理链、TLS 和用户模式栈信息。
1 2 3 4 5 6 7 8 9 10 11 12
| nt!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void +0x00c SubSystemTib : Ptr32 Void +0x010 FiberData : Ptr32 Void +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 Void +0x018 Self : Ptr32 _NT_TIB
|
# +0x120 PrcbData : _KPRCB
_KPRCB (Kernel Processor Control Block,内核处理器控制块)用于管理每个处理器核心的状态和上下文,包含调度、中断、性能计数等信息。
dt _KPRCB(仅列出关键字段)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| nt!_KPRCB +0x000 MinorVersion : Uint2B +0x002 MajorVersion : Uint2B +0x004 CurrentThread : Ptr32 _KTHREAD +0x008 NextThread : Ptr32 _KTHREAD +0x00c IdleThread : Ptr32 _KTHREAD +0x010 Number : Char +0x014 SetMember : Uint4B +0x018 CpuType : Char +0x01c ProcessorState : _KPROCESSOR_STATE +0x4a0 NpxThread : Ptr32 _KTHREAD +0x4a4 InterruptCount : Uint4B +0x4a8 KernelTime : Uint4B +0x4ac UserTime : Uint4B +0x4fc KeContextSwitches : Uint4B +0x860 DpcListHead : _LIST_ENTRY +0x868 DpcStack : Ptr32 Void +0x88c QuantumEnd : Uint4B ...
|
其中 CurrentThread 、 NextThread 和 IdleThread 三个字段是线程调度的核心:
CurrentThread :当前正在此 CPU 上运行的线程
NextThread :已被选定、即将切换到的线程(备用线程)
IdleThread :没有就绪线程可调度时,CPU 执行的空闲线程