Terminate/Suspend/ResumeThread函数分析

ʕ •ᴥ•ʔ ɔ:

线程只能自杀,不能他杀。”他杀”只不过是“他”在目标线程上挂一个APC,然后让目标线程在适当的时候去执行这个APC。

练习:

从KiDeliverApc开始将0环和返回三环的栈画出来.(想学好调试这道题必做)

1、分析TerminateThread/SuspendThread/ResumeThread是如何实现的(从3环开始分析).
控制别的线程,就是通过APC实现的
2、自己编写代码向某个线程插入一个用户APC.
使用现成的API就可以 体会什么是APC
3、自己分析APC的插入过程

1 TerminateThread分析

TerminateThread函数:终止一个线程。

1
2
3
4
BOOL __stdcall TerminateThread(
[in, out] HANDLE hThread, //要终止线程的句柄。
[in] DWORD dwExitCode //线程的退出代码。
);

dwExitCode:使用GetExitCodeThread函数检索线程的退出值。

函数调用过程:

1
2
TerminateThread --> NtTerminateThread(102h) --> NtTerminateThread --> PspTerminateThreadByPointer
-->PspExitThread/KeInitializeApc

1.1 NtTerminateThread

NtTerminateThread函数:将指定的线程终止(This function causes the specified thread to terminate.)。

1
2
3
4
NTSTATUS __stdcall NtTerminateThread(
IN HANDLE ThreadHandle OPTIONAL, //要结束线程的句柄
IN NTSTATUS ExitStatus //线程的退出代码
)

逆向分析函数流程如下:

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
if(ThreadHandle == NULL)
{
if(EPROCESS.ActiveThreads != 1) //ActiveThreads:记录了当前进程有多少活动线程。当该值减为0时,所有的线程将退出,于是进程也退出。
{
PspTerminateThreadByPointer(Thread, ExitStatus); //ActiveThreads > 1,Kill当前线程(KPCR)
}else //Handle == 0,KTHREAD.ApcState.Process.ActiveThreads == 1时直接返回,不结束自己
{
return 0xC00000DB; //STATUS_CANT_TERMINATE_SELF,ActiveThreads == 1,说明当前线程是该进程最后一个线程了,不结束自己
}
}

if(ThreadHandle != NULL)
{
if(ThreadHandle == -2) //结束当前线程
{
PspTerminateThreadByPointer(Thread, ExitStatus);
}

//要结束的不是当前线程
ObReferenceObjectByHandle(); //获取要结束线程对象地址
if(Object != CurrentThread) //要结束的线程与当前线程 不属于同一个进程
{
PspTerminateThreadByPointer(Thread, ExitStatus);
ObfDereferenceObject(Thread);
}else //要结束的线程与当前线程 属于同一个进程
{
ObfDereferenceObject(Thread) //减少指定对象的对象引用计数(不同于句柄引用计数),并在计数变为零时进行清理工作
PspTerminateThreadByPointer(Thread, ExitStatus);
}

}

逆向分析如下:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
PAGE:004FBB8F ; ---------------------------------------------------------------------------
PAGE:004FBB92 align 8
PAGE:004FBB98
PAGE:004FBB98 ; =============== S U B R O U T I N E =======================================
PAGE:004FBB98
PAGE:004FBB98 ; Attributes: bp-based frame
PAGE:004FBB98
PAGE:004FBB98 ; NTSTATUS __stdcall NtTerminateThread(HANDLE ThreadHandle, NTSTATUS ExitStatus)
PAGE:004FBB98 _NtTerminateThread@8 proc near //DATA XREF: .text:0042D858↑o
PAGE:004FBB98
PAGE:004FBB98 AccessMode = byte ptr -4
PAGE:004FBB98 ThreadHandle = dword ptr 8
PAGE:004FBB98 ExitStatus = dword ptr 0Ch
PAGE:004FBB98
PAGE:004FBB98 mov edi, edi
PAGE:004FBB9A push ebp
PAGE:004FBB9B mov ebp, esp
PAGE:004FBB9D push ecx
PAGE:004FBB9E push ebx
PAGE:004FBB9F push esi
PAGE:004FBBA0 push edi
PAGE:004FBBA1 xor edi, edi
PAGE:004FBBA3 mov eax, large fs:124h
PAGE:004FBBA9 cmp [ebp+ThreadHandle], edi
PAGE:004FBBAC mov esi, eax
PAGE:004FBBAE jnz short loc_4FBBC3 //--
PAGE:004FBBAE //ThreadHandle != 0
PAGE:004FBBAE //判断 ThreadHandle == -2?
PAGE:004FBBAE ;
PAGE:004FBBAE //ThreadHandle句柄值:
PAGE:004FBBAE //1、 0:
PAGE:004FBBAE // - ActiveThreads != 1,结束当前线程
PAGE:004FBBAE // - ActiveThreads == 1,直接返回,也就是不能结束当前进程的最后一个线程
PAGE:004FBBAE //2、 -1 代表当前进程
PAGE:004FBBAE //3、 -2 代表当前线程
PAGE:004FBBAE //4、 为4倍数的正数:句柄表中的索引
PAGE:004FBBAE //5、 负数:其值的绝对值为System进程中的句柄值,ObpKernelHandleTable,仅限于内核模式的函数可以引用。
PAGE:004FBBAE //--
PAGE:004FBBB0 mov eax, [esi+_KTHREAD.ApcState.Process] //--
PAGE:004FBBB0 //ThreadHandle == NULL,传入句柄值为NULL时有如下两种处理方式:
PAGE:004FBBB0 //1. ActiveThreads != 1,结束当前线程
PAGE:004FBBB0 //2. ActiveThreads == 1,直接返回,也就是不能结束当前进程的最后一个线程
PAGE:004FBBB0 ;
PAGE:004FBBB0 //eax:如果线程已经挂靠,则为养父母
PAGE:004FBBB0 // 如果没有挂靠,则为亲生父母(创建者进程)
PAGE:004FBBB0 //--
PAGE:004FBBB3 cmp [eax+_EPROCESS.ActiveThreads], 1 //--
PAGE:004FBBB3 //ActiveThreads:记录了当前进程有多少活动线程。
PAGE:004FBBB3 //当该值减为0时,所有的线程将退出,于是进程也退出。
PAGE:004FBBB3 //--
PAGE:004FBBBA jnz short loc_4FBBFF //--
PAGE:004FBBBA //ActiveThreads != 1,说明ActiveThreads > 1,不会 <= 1不然进程应该结束了
PAGE:004FBBBA //ActiveThreads > 1,Kill当前线程(KPCR)
PAGE:004FBBBA //--
PAGE:004FBBBC mov eax, 0C00000DBh //--
PAGE:004FBBBC //STATUS_CANT_TERMINATE_SELF
PAGE:004FBBBC //ActiveThreads == 1,说明当前线程是该进程最后一个线程了
PAGE:004FBBBC //表示默认情况下,线程试图终止自己(称为NULL的NtTerminateThread),它是当前进程中的最后一个线程。
PAGE:004FBBBC //--
PAGE:004FBBC1 jmp short loc_4FBC1E //返回,也就是说当传入的句柄值Handle == NULL :
PAGE:004FBBC1 //Handle == 0,KTHREAD.ApcState.Process.ActiveThreads == 1时直接返回,不结束自己
PAGE:004FBBC3 ; ---------------------------------------------------------------------------
PAGE:004FBBC3
PAGE:004FBBC3 loc_4FBBC3: //CODE XREF: NtTerminateThread(x,x)+16↑j
PAGE:004FBBC3 cmp [ebp+ThreadHandle], 0FFFFFFFEh //--
PAGE:004FBBC3 //ThreadHandle != 0
PAGE:004FBBC3 //判断 ThreadHandle == -2?
PAGE:004FBBC3 ;
PAGE:004FBBC3 //ThreadHandle句柄值:
PAGE:004FBBC3 //1、 0:
PAGE:004FBBC3 // - ActiveThreads != 1,结束当前线程
PAGE:004FBBC3 // - ActiveThreads == 1,直接返回,也就是不能结束当前进程的最后一个线程
PAGE:004FBBC3 //2、 -1 代表当前进程
PAGE:004FBBC3 //3、 -2 代表当前线程
PAGE:004FBBC3 //4、 为4倍数的正数:句柄表中的索引
PAGE:004FBBC3 //5、 负数:其值的绝对值为System进程中的句柄值,ObpKernelHandleTable,仅限于内核模式的函数可以引用。
PAGE:004FBBC3 //--
PAGE:004FBBC7 jz short loc_4FBBFF //调用 PspTerminateThreadByPointer 来Kill当前线程(KPCR)
PAGE:004FBBC9 mov al, [esi+_KTHREAD.PreviousMode]
PAGE:004FBBCF push 0 //HandleInformation
PAGE:004FBBD1 mov [ebp+AccessMode], al
PAGE:004FBBD4 lea eax, [ebp+ThreadHandle]
PAGE:004FBBD7 push eax //函数调用后ThreadHandle保存PVOID的对象地址,就不是句柄了
PAGE:004FBBD8 push dword ptr [ebp+AccessMode] //AccessMode
PAGE:004FBBDB push _PsThreadType //ObjectType
PAGE:004FBBE1 push 1 //DesiredAccess
PAGE:004FBBE3 push [ebp+ThreadHandle] //Handle
PAGE:004FBBE6 call _ObReferenceObjectByHandle@24 //NTSTATUS ObReferenceObjectByHandle (
PAGE:004FBBE6 // IN HANDLE Handle,
PAGE:004FBBE6 // IN ACCESS_MASK DesiredAccess,
PAGE:004FBBE6 // IN POBJECT_TYPE ObjectType OPTIONAL,
PAGE:004FBBE6 // IN KPROCESSOR_MODE AccessMode,
PAGE:004FBBE6 // OUT PVOID *Object,
PAGE:004FBBE6 // OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL
PAGE:004FBBE6 // )
PAGE:004FBBEB mov edi, eax
PAGE:004FBBED test edi, edi
PAGE:004FBBEF jl short loc_4FBC1C //返回
PAGE:004FBBF1 mov ebx, [ebp+ThreadHandle] //参考PAGE:004FBBD4,ThreadHandle保存PVOID的对象地址
PAGE:004FBBF4 cmp ebx, esi //esi:CurrentThread
PAGE:004FBBF6 jnz short loc_4FBC0A //要结束的线程与当前线程 不属于同一个进程
PAGE:004FBBF8 mov ecx, ebx //要结束的线程与当前线程 属于同一个进程
PAGE:004FBBFA call @ObfDereferenceObject@4 //--
PAGE:004FBBFA //减少指定对象的对象引用计数,并在计数变为零时进行清理工作。
PAGE:004FBBFA //不同于句柄引用计数
PAGE:004FBBFA //--
PAGE:004FBBFF
PAGE:004FBBFF loc_4FBBFF: //CODE XREF: NtTerminateThread(x,x)+22↑j
PAGE:004FBBFF //NtTerminateThread(x,x)+2F↑j
PAGE:004FBBFF push [ebp+ExitStatus] //调用 PspTerminateThreadByPointer 来Kill当前线程(KPCR)
PAGE:004FBC02 push esi //Object
PAGE:004FBC03 call _PspTerminateThreadByPointer@8 //--
PAGE:004FBC03 //NTSTATUS PspTerminateThreadByPointer(
PAGE:004FBC03 // IN PETHREAD Thread,
PAGE:004FBC03 // IN NTSTATUS ExitStatus
PAGE:004FBC03 // )
PAGE:004FBC03 //typedef struct FARSTRUCT _LARGE_INTEGER {
PAGE:004FBC03 // DWORD LowPart;
PAGE:004FBC03 // LONG HighPart;
PAGE:004FBC03 //} LARGE_INTEGER, *PLARGE_INTEGER;
PAGE:004FBC03 //--
PAGE:004FBC08 jmp short loc_4FBC1C //返回
PAGE:004FBC0A ; ---------------------------------------------------------------------------
PAGE:004FBC0A
PAGE:004FBC0A loc_4FBC0A: //CODE XREF: NtTerminateThread(x,x)+5E↑j
PAGE:004FBC0A push [ebp+ExitStatus] //要结束的线程与当前线程 不属于同一个进程
PAGE:004FBC0D push ebx //Object
PAGE:004FBC0E call _PspTerminateThreadByPointer@8 //--
PAGE:004FBC0E //NTSTATUS PspTerminateThreadByPointer(
PAGE:004FBC0E // IN PETHREAD Thread,
PAGE:004FBC0E // IN NTSTATUS ExitStatus
PAGE:004FBC0E // )
PAGE:004FBC0E //typedef struct FARSTRUCT _LARGE_INTEGER {
PAGE:004FBC0E // DWORD LowPart;
PAGE:004FBC0E // LONG HighPart;
PAGE:004FBC0E //} LARGE_INTEGER, *PLARGE_INTEGER;
PAGE:004FBC0E //--
PAGE:004FBC13 mov ecx, ebx //Object
PAGE:004FBC15 mov edi, eax
PAGE:004FBC17 call @ObfDereferenceObject@4 //--
PAGE:004FBC17 //减少指定对象的对象引用计数,并在计数变为零时进行清理工作。
PAGE:004FBC17 //不同于句柄引用计数
PAGE:004FBC17 //--
PAGE:004FBC1C
PAGE:004FBC1C loc_4FBC1C: //CODE XREF: NtTerminateThread(x,x)+57↑j
PAGE:004FBC1C //NtTerminateThread(x,x)+70↑j
PAGE:004FBC1C mov eax, edi //返回
PAGE:004FBC1E
PAGE:004FBC1E loc_4FBC1E: //CODE XREF: NtTerminateThread(x,x)+29↑j
PAGE:004FBC1E pop edi
PAGE:004FBC1F pop esi
PAGE:004FBC20 pop ebx
PAGE:004FBC21 leave
PAGE:004FBC22 retn 8
PAGE:004FBC22 _NtTerminateThread@8 endp
PAGE:004FBC22
PAGE:004FBC22 ; ---------------------------------------------------------------------------

NULL句柄的含义

逆向分析得,句柄值的处理:

句柄值 含义
0 ActiveThreads = KTHREAD.ApcState.Process.ActiveThreads
- ActiveThreads != 1,结束当前线程。
- ActiveThreads == 1,直接返回,也就是不能结束当前进程的最后一个线程。
在分析NtTerminateThread(IN HANDLE ThreadHandle OPTIONAL,IN NTSTATUS ExitStatus)时逆得的。
-1 代表当前进程
-2 代表当前线程
一个为4倍数的正数 句柄表中的索引
负数 其值的绝对值为System进程中的句柄值,ObpKernelHandleTable,仅限于内核模式的函数可以引用。

还需要说说明的是:

  • Handle == -2,结束当前线程。
  • Handle == NULL
    • ActiveThreads != 1,结束当前线程。
    • ActiveThreads == 1,直接返回,不可结束当前线程

1.2 PspTerminateThreadByPointer

PspTerminateThreadByPointer函数:将指定的线程终止(This function causes the specified thread to terminate.)。

1
2
3
4
NTSTATUS __stdcall PspTerminateThreadByPointer(
IN PETHREAD Thread, //要结束线程地址
IN NTSTATUS ExitStatus //线程的退出代码
);

逆向分析函数流程如下:

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
if(ETHREAD.CrossThreadFlags == 0x40)//PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION,要求退出线程时中断到调试器
{
PspCatchCriticalBreak(); //中断到调试器,中断后继续执行结束线程的工作
}
if(Object == CurrentThread) //要kill当前线程
{
CrossThreadFlags.Terminated == 1;//线程已执行终止操作
PspExitThread(); //(thread has called PspExitThread itself)
}else //要Kill的不是当前线程
{
if(ETHREAD.CrossThreadFlags == 0x10)//PS_CROSS_THREAD_FLAGS_SYSTEM,判断是否是系统线程
{
//要Kill的线程不是当前线程,是系统线程,则无权限
return 0xC0000022; //STATUS_ACCESS_DENIED,没有权限结束系统线程
}else //要Kill的线程不是当前线程,也不是系统线程
{
LPVOID pMem = NULL;
while(1)
{
pMem = ExAllocatePoolWithTag();//分配指定类型的池内存,并返回指向分配块的指针
if(pMem != NULL)
{
Old_CrossThreadFlags.Terminated = CrossThreadFlags.Terminated;
CrossThreadFlags.Terminated = 1;
break;
}
KeDelayExecutionThread(); //将当前线程置于指定间隔内可被唤醒或不可吵醒的等待状态,然后继续申请内存
}//while
if(Old_CrossThreadFlags.Terminated == 1)//线程已经被终止过了
{
ExFreePoolWithTag();
return;
}else
{
KeInitializeApc(); //插入APC
KeInsertQueueApc();
ExFreePoolWithTag();
return;
}
}//要Kill的线程不是当前线程,也不是系统线程

}//要Kill的不是当前线程

逆向分析得这样的结论:如果要Kill自己,使用PspExitThread函数;如果要Kill其他线程,则需要插入APC去执行

《Windows内核原理与实现3.4 P147》

PspTerminateThreadByPointer函数中,如果当前线程被终止,则设置结束标志PS_CROSS_THREAD_FLAGS_TERMINATED,并调用PspExitThread退出线程,此函数执行实际的终止过程,并且不返回。如果不是当前线程,则在该线程中插人一个内核模式APC,为它指定PsExitSpecialApcPspExitApcRundownPspExitNormalApc例程,由它们完成真正的终止过程。关于AP℃对象的交付过程,即它指定的这些例程是如何被执行的,请参考本书5.2.6节。最终PsExitSpecialApc函数被执行,它也调用PspExitThread函数来完成终止过程,所以下面只讨论PspExitThread函数的处理过程。

源码如下:

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
NTSTATUS PspTerminateThreadByPointer(
IN PETHREAD Thread,
IN NTSTATUS ExitStatus,
IN BOOLEAN DirectTerminate
)
//Routine Description: This function causes the specified thread to terminate.
{
NTSTATUS Status;
PKAPC ExitApc=NULL;
ULONG OldMask;

PAGED_CODE();

if (Thread->CrossThreadFlags
& PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION) {
PspCatchCriticalBreak("Terminating critical thread 0x%p (in %s)\n",
Thread,
THREAD_TO_PROCESS(Thread)->ImageFileName);
}

if (DirectTerminate && Thread == PsGetCurrentThread()) {
ASSERT (KeGetCurrentIrql() < APC_LEVEL);
PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_TERMINATED);
PspExitThread (ExitStatus);

// Never Returns
} else {
// Cross thread deletion of system threads won't work.
if (IS_SYSTEM_THREAD (Thread)) {
return STATUS_ACCESS_DENIED;
}

Status = STATUS_SUCCESS;

while (1) {
ExitApc = (PKAPC) ExAllocatePoolWithTag (NonPagedPool,
sizeof(KAPC),
'xEsP');
if (ExitApc != NULL) {
break;
}
KeDelayExecutionThread(KernelMode, FALSE, &ShortTime);
}

// Mark the thread as terminating and call the exit function.
OldMask = PS_TEST_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_TERMINATED);

// If we are the first to set the terminating flag then queue the APC
if ((OldMask & PS_CROSS_THREAD_FLAGS_TERMINATED) == 0) {

KeInitializeApc (ExitApc,
PsGetKernelThread (Thread),
OriginalApcEnvironment,
PsExitSpecialApc,
PspExitApcRundown,
PspExitNormalApc,
KernelMode,
ULongToPtr (ExitStatus));

if (!KeInsertQueueApc (ExitApc, ExitApc, NULL, 2)) {
// If APC queuing is disabled then the thread is exiting anyway
ExFreePool (ExitApc);
Status = STATUS_UNSUCCESSFUL;
} else {
// We queued the APC to the thread. Wake up the thread if it was suspended.
KeForceResumeThread (&Thread->Tcb);
}
} else {
ExFreePool (ExitApc);
}
}

return Status;
}

ETHREAD.CrossThreadFlags

CrossThreadFlags域是一些针对跨线程访问的标志位(《Windows内核原理与实现3.4 P128》),包括:

  • Terminated(线程已执行终止操作)
  • DeadThread(创建失败)
  • HideFromDebugger(该线程对于调试器不可见)
  • Activelmpersonationlnfo(线程正在模仿)
  • SystemThread(是一个系统线程)
  • HardErrorsAreDisabled(对于该线程,硬件错误无效)
  • BreakOnTermination(调试器在线程终止时停下该线程,即要求退出进程时中断到调试器)
  • SkipCreationMsg(不向调试器发送创建消息)
  • SkipTerminationMsg(不向调试器发送终止消息)。

在创建线程时,经常将CrossThreadFlags.SystemThread设置为1,来伪装成系统线程,这样普通线程就没有办法将自己Kill了。

由定义可以知道,低1字节的8bit为常用位,其他高位为未定义或者不常用。

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
union {

ULONG CrossThreadFlags;
// The following fields are for the debugger only. Do not use.
// Use the bit definitions instead.以下字段仅适用于调试器。不使用。请改用位定义。
struct {
ULONG Terminated : 1;
ULONG DeadThread : 1;
ULONG HideFromDebugger : 1;
ULONG ActiveImpersonationInfo : 1;
ULONG SystemThread : 1;
ULONG HardErrorsAreDisabled : 1;
ULONG BreakOnTermination : 1;
ULONG SkipCreationMsg : 1;
ULONG SkipTerminationMsg : 1;
};
};
ACCESS_MASK GrantedAccess;
// Flags for cross thread access. Use interlocked operations
// via PS_SET_BITS etc.

// Used to signify that the delete APC has been queued or the
// thread has called PspExitThread itself.
#define PS_CROSS_THREAD_FLAGS_TERMINATED 0x00000001UL

// Thread create failed
#define PS_CROSS_THREAD_FLAGS_DEADTHREAD 0x00000002UL

// Debugger isn't shown this thread
#define PS_CROSS_THREAD_FLAGS_HIDEFROMDBG 0x00000004UL

// Thread is impersonating
#define PS_CROSS_THREAD_FLAGS_IMPERSONATING 0x00000008UL

// This is a system thread
#define PS_CROSS_THREAD_FLAGS_SYSTEM 0x00000010UL

// Hard errors are disabled for this thread
#define PS_CROSS_THREAD_FLAGS_HARD_ERRORS_DISABLED 0x00000020UL

// We should break in when this thread is terminated
#define PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION 0x00000040UL

// This thread should skip sending its create thread message
#define PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG 0x00000080UL

// This thread should skip sending its final thread termination message
#define PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG 0x00000100UL

KeDelayExecutionThread

KeDelayExecutionThread函数:将当前线程置于指定间隔内可被唤醒或不可吵醒的等待状态。

1
2
3
4
5
6
NTSTATUS __stdcall KeDelayExecutionThread(
[in] KPROCESSOR_MODE WaitMode, //指定调用者等待的处理器模式,可以是KernelMode或UserMode。
[in] BOOLEAN Alertable,//是否可被吵醒
[in] PLARGE_INTEGER Interval //指定等待的绝对或相对时间,单位为100纳秒。负值表示相对时间。
//绝对到期时间跟踪系统时间的任何变化;相对到期时间不受系统时间变化的影响。
);

1.3 PspExitThread

PspExitThread函数的功能:This function causes the currently executing thread to terminate. This function is only called from within the process structure. It is called either from mainline exit code to exit the current thread, or from PsExitSpecialApc (as a piggyback to user-mode PspExitNormalApc).

函数原型:

1
VOID __stdcall PspExitThread(IN NTSTATUS ExitStatus)

这个函数相当复杂,等我有时间,学到更多知识后再来逆😭。

2 SuspendThread

SuspendThread函数:挂起/暂停指定的线程。

64位应用程序可以使用Wow64SuspendThread函数挂起WOW64线程。

  • WOW64:WOW64(Windows-On-Windows 64bit)是X64 Windows操作系统的一个子系统,为32位应用程序提供运行环境。
  • WOW32:负责在32位Windows系统上运行16位应用程序。
  • Wow64SuspendThread:此功能适用于64位应用程序。32位Windows不支持它;此类调用失败,并将最后一个错误代码设置为ERROR_INVALID_FUNCTION。32位应用程序可以在WOW64线程上调用此函数;结果与调用SuspendThread函数相同。在64位程序开发中,编译器识别本函数,可能无法识别SuspendThread。

函数原型如下:

1
DWORD __stdcall SuspendThread([in] HANDLE hThread)
  • 参数hThread:待挂起的线程的句柄。
  • 返回值:如果函数成功,返回值是线程之前的暂停计数;否则,它是(DWORD) -1。要获取扩展错误信息,请使用GetLastError函数。

暂停计数(SuspendCount):也叫做挂起计数记录线程被挂起的次数。每个线程都有一个暂停计数(最大值为MAXIMUM_SUSPEND_COUNT)。

1
2
3
4
KTHREAD.SuspendCount(+0x1b9)

#define MAXIMUM_SUSPEND_COUNT MAXCHAR
#define MAXCHAR 255

如果挂起计数大于零,则线程将被挂起;否则,线程不会挂起,并且符合执行条件。调用``SuspendThread`导致目标线程的挂起计数增加。尝试超过最大挂起计数会导致错误而不增加计数。ResumeThread函数会减去挂起线程的挂起计数。

函数调用过程:

1
SuspendThread --> NtSuspendThread(0FEh) --> NtSuspendThread --> PsSuspendThread --> KeSuspendThread

2.1 NtSuspendThread

NtSuspendThread函数:This function suspends the target thread, and optionally returns the previous suspend count.

1
2
3
4
NTSTATUS __stdcall NtSuspendThread(
IN HANDLE ThreadHandle, //Supplies a handle to the thread object to suspend.
OUT PULONG PreviousSuspendCount OPTIONAL //An optional parameter, that if specified points to a variable
) //that receives the thread's previous suspend count.

注意这里的PreviousSuspendCount是一个地址,指向先前线程被挂起的次数

逆向分析函数流程如下:

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
if(PreviousMode != 0)	//调用来自3环,需要进行参数检查
{
if(PreviousSuspendCount == NULL) goto StartOperations;//传入参数为NULL,无需获取先前线程被挂起的次数
else //PreviousSuspendCount != NULL
{
if(PreviousSuspendCount >= MmUserProbeAddress) //MmUserProbeAddress == 7fff0000,划定用户空间和内核空间界限的地址
{
MmUserProbeAddress = NULL; //用于抛出异常,因为NULL地址不可写
}
}
}
StartOperations:
Object = ObReferenceObjectByHandle();
PsSuspendThread(Thread,PreviousSuspendCount); //挂起线程
ObfDereferenceObject(); //少指定对象的对象引用计数,并在计数变为零时进行清理工作。不同于句柄引用计数
try {

if (PreviousSuspendCount != NULL) {
*PreviousSuspendCount = (DWORD)SuspendThread;//写入错误就会触发异常
}

} except (EXCEPTION_EXECUTE_HANDLER) {

st = GetExceptionCode(); //PreviousSuspendCount == NULL,触发异常

}
return st;

#define EXCEPTION_EXECUTE_HANDLER 1 //代码进入except块
#define EXCEPTION_CONTINUE_SEARCH 0 //不处理异常,由上一层调用函数处理
#define EXCEPTION_CONTINUE_EXECUTION -1 //回去继续执行错误处的代码

MmUserProbeAddress、MmHighestUserAddress

MmUserProbeAddress是一个全局变量,指向一个划定用户空间和内核空间界限的地址。

与该值的比较用于确定地址是指向用户空间还是内核空间。

1
2
kd> dd MmUserProbeAddress
80563134 7fff0000 80000000 7ffeffff 00000000

可以看到MmUserProbeAddress == 0x7fff0000,则用户空间(3环)可以访问的最大地址是0x7fff0000

源码中有:PreviousSuspendCount >= MmUserProbeAddress,就会MmUserProbeAddress = NULL。就会抛出异常,因为NULL地址不可写。实际上源码中用了try-except,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
try {

Mode = KeGetPreviousMode();

if ( Mode != KernelMode ) {
if (ARGUMENT_PRESENT(PreviousSuspendCount)) {
ProbeForWriteUlong(PreviousSuspendCount);
}
}
} except (EXCEPTION_EXECUTE_HANDLER) {

return GetExceptionCode();
}

#define ARGUMENT_PRESENT(p) ((p) != NULL)

#define ProbeForWriteUlong(Address) {
if ((Address) >= (ULONG * const)MM_USER_PROBE_ADDRESS) {
*(volatile ULONG * const)MM_USER_PROBE_ADDRESS = 0;
}

*(volatile ULONG *)(Address) = *(volatile ULONG *)(Address);
}

可以看到,当try中的ProbeForWriteUlong(PreviousSuspendCount)执行到*(volatile ULONG * const)MM_USER_PROBE_ADDRESS = 0就会触发异常(因为NULL地址不可读写),然后执行except中的代码。

0环下大部分的Nt-函数都会对来自3环调用的参数进行检查,常用到MmUserProbeAddress

1
2
3
4
5
6
7
8
9
#define MM_HIGHEST_USER_ADDRESS *MmHighestUserAddress
#define MM_SYSTEM_RANGE_START *MmSystemRangeStart
#define MM_USER_PROBE_ADDRESS *MmUserProbeAddress
#define PTE_BASE 0xc0000000

MmHighestUserAddress = 0x7ffeffff
MmUserProbeAddress = 0x7fff0000
MmSystemRangeStart = 0x80000000
MmUserProbeAddress = MmHighestUserAddress + 1
  • 用户空间可以访问的最高地址是MmHighestUserAddress(0x7ffeffff),从MmUserProbeAddress(0x7fff0000)开始就不能访问了。一般文献中讲Windows系统中用户空间与系统空间的分界线是0x80000000,而这里之所以是0x7fff0000而不是0x80000000,是因为在分界下面留了64KB的空间不让访问,以此作为隔离区。(64KB = 2^6*2^10共16位)

  • MmUserProbeAddress是一个界限,该地址不可用

    • 3环可用地址<=MmHighestUserAddress
    • 3环可用地址<MmUserProbeAddress

这部分内容的讲解在《Windows内核情景分析5.2 P253》有、《Windows内核原理与实现4.2 P207》。

volatile类型修饰符

volatile关键字是一种类型修饰符。Volatile意思是“易变的”,应该解释为“直接存取原始内存地址”比较合适。这个关键词volatile总是与优化有关,多线程读取变量时也用到。

使用volatile修饰的变量,在编译这样的变量时,编译器一般都会作减少存取内存的优化,但有可能会读脏数据。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问

但是也不能频繁的使用volatile,否则会影响程序运行的性能。具体可以参考C/C++ 中 volatile 关键字详解

以下举个例子说明一下:

  1. 不使用volatile时编译Release版本(vc6编译器):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include "stdafx.h"

    int TestNum = 0;
    int main(int argc, char* argv[])
    {
    int temp = 0;

    TestNum = 2;
    temp = TestNum;
    TestNum = 5;

    getchar();
    return 0;
    }

    Release版本编译代码如下:

    1
    2
    3
    4
    5
    00401000	mov eax,dword ptr ds:[0x407034]
    00401005 mov dword ptr ds:[0x4098A8],0x5
    0040100F dec eax
    00401010 mov dword ptr ds:[0x407034],eax
    ...
    • 0x4098A8即为变量TestNum的地址,由于源代码最后一次赋值是TestNum = 5,则代码TestNum = 2是无用的被优化了。
    • 代码temp = TestNum中,赋值后temp没有被用到,在代码中也被优化了。
  2. 使用volatile修饰变量时编译Release版本(vc6编译器):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include "stdafx.h"

    volatile int TestNum = 0;
    int main(int argc, char* argv[])
    {
    int temp = 0;

    TestNum = 2;
    temp = TestNum;
    TestNum = 5;

    getchar();
    return 0;
    }

    Release版本编译代码如下:

    1
    2
    3
    4
    5
    6
    7
    00401000	mov dword ptr ds:[0x4098A8],0x2
    0040100A mov eax,dword ptr ds:[0x4098A8]
    0040100F mov dword ptr ds:[0x4098A8],0x5
    00401019 mov eax,dword ptr ds:[0x407034]
    0040101E dec eax
    0040101F mov dword ptr ds:[0x407034],eax
    ...

    可以看到,两次对TestNum的赋值都没有被优化,对于取地址0x4098A8中的值给temp赋值的代码也没有优化。

家宇花园

2.2 PsSuspendThread

PsSuspendThread函数:This function suspends the target thread, and optionally returns the previous suspend count.

1
2
3
4
NTSTATUS __stdcall PsSuspendThread (
IN PETHREAD Thread, //要挂起的线程
OUT PULONG PreviousSuspendCount OPTIONAL //线程先前被挂起的次数
)

逆向分析函数流程如下:

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
if(Object == CurrentThread)	//要挂起当前线程
{
KeSuspendThread(Thread); //挂起线程
Status = STATUS_SUCCESS;
}else //要挂起的不是当前线程
{
bRet = ExAcquireRundownProtectio(RunRef);//开启运行保护,保证在访问Thread线程完成之前不会删除该对象

//返回值,TRUE:已获取运行保护,FALSE:运行保护处于活动状态或已完成,必须将对象视为无效。
if(bRet == FALSE)
{
Status = 0xC000004B; //STATUS_THREAD_IS_TERMINATING,试图暂停已经开始终止的线程
}else //bRet == TRUE
{

if(CrossThreadFlags.Terminated == 1)//线程已执行终止操作
{
Status = 0xC000004B; //STATUS_THREAD_IS_TERMINATING,试图暂停已经开始终止的线程
}else //CrossThreadFlags.Terminated == 0,线程未执行终止操作
{
KeSuspendThread(Thread); //挂起线程
Status = STATUS_SUCCESS;
if(CrossThreadFlags.Terminated == 1)
{
KeForceResumeThread();
SuspendCount = 0;
Status = 0xC000004B; //STATUS_THREAD_IS_TERMINATING,试图暂停已经开始终止的线程
}
}//CrossThreadFlags.Terminated == 0,线程未执行终止操作

ExReleaseRundownProtection();
}//bRet == TRUE

}//要挂起的不是当前线程
if(PreviousSuspendCount != 0)
{
PreviousSuspendCount = SuspendCount;
}
return Status;

ExAcquireRundownProtection

ExAcquireRundownProtection函数:尝试获取共享对象的运行保护,以便调用方可以安全地访问该对象。

当运行保护生效时,驱动程序可以安全地访问对象,而不会有在访问完成之前删除该对象的风险。

1
2
3
NTKERNELAPI BOOLEAN FASTCALL ExAcquireRundownProtection (
IN PEX_RUNDOWN_REF RunRef //跟踪关联的共享对象的运行状态
)
  • 参数RunRef:指向先前对 ExInitializeRundownProtection 例程的调用初始化的EX_RUNDOWN_REF结构的指针。 运行保护例程使用此结构来跟踪关联的共享对象的运行状态。 此结构对驱动程序不透明。
  • 返回值:如果例程成功获取调用方的运行保护,ExAcquireRundownProtection 将返回 TRUE。 否则,它将返回 FALSEFALSE 的返回值指示对象的运行已启动,并且该对象必须被视为无效,不可再访问该对象。

停止运行保护(Run-Down Protection):从 Windows XP 开始,内核驱动就支持停运保护机制。驱动通过停运机制可以安全地访问在系统内存中的对象,通常这些对象是由其他内核驱动创建和销毁的。

拥有共享对象的驱动程序可以允许其他驱动程序获取和释放对象的运行时保护。 当停运保护生效时,除对象的所有者外,其他驱动可以访问该对象而不用担心在访问结束前该对象会被其所有者删除。在访问开始之前,要访问的驱动会提出对目标对象实施停运保护的请求。对于一个存活周期较长的对象来说,这类请求几乎都是被允许的。当访问结束时,执行访问的驱动会卸除之前对对象实施的停运保护。

参考:同步下的资源互斥:停运保护(Run-Down Protection)机制

2.3 KeSuspendThread

KeSuspendThread函数:将线程挂起。如果挂起计数溢出最大挂起计数,则会引发一个条件。

1
2
3
ULONG __stdcall KeSuspendThread (
IN PKTHREAD Thread //要挂起的线程
) //返回值:线程原先被挂起的次数

逆向分析函数流程如下:

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
#define MAXIMUM_SUSPEND_COUNT 255
if(SuspendCount == MAXIMUM_SUSPEND_COUNT) //线程被挂起的次数达到最大值
{
Status = 0xC000004A; //STATUS_SUSPEND_COUNT_EXCEEDED,试图暂停计数达到最大值的线程
ExRaiseStatus(); //异常处理,直接就退出本函数了,后面的代码不会执行了
}
if(ApcQueueable == 1) //表示可以向线程的APC队列中插入APC
{
Old_SuspendCount = SuspendCount;
SuspendCount++;

if(Old_SuspendCount != 0)//非首次挂起该线程
{
return Old_SuspendCount; //该线程已经被挂起过,非首次挂起,SuspendCount++后直接返回
}else //Old_SuspendCount == 0,线程第一次被挂起
{
if(FreezeCount == 0) //当首次挂起和恢复时,FreezeCount必须为0
{
bRet = (BOOLEAN)KiInsertQueueApc();//插入APC去挂起线程
if(bRet == FALSE) //APC插入失败
Thread->SuspendSemaphore.Header.SignalState--;
}
}//Old_SuspendCount == 0

}
return Old_SuspendCount;

在调用SuspendThread时,SuspendCount 自加1;如果大于0表示挂起了。调用ResumeThread时,SuspendCount 自减1;如果等于0,系统会恢复线程。

当挂起和恢复时,FreezeCount必须为0,否则挂起和恢复操作都直接pass了。所以,可以修改 SuspendCount 和 FreezeCount来达到反挂起的目的。

源码如下:

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
ULONG KeSuspendThread ( IN PKTHREAD Thread )
{
KLOCK_QUEUE_HANDLE LockHandle;
ULONG OldCount;

ASSERT_THREAD(Thread);
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

// Raise IRQL to SYNCH_LEVEL, acquire the thread APC queue lock, and lock
// the dispatcher database.
KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock, &LockHandle);
KiLockDispatcherDatabaseAtSynchLevel();

// Capture the current suspend count.
// If the suspend count is at its maximum value, then unlock the
// dispatcher database, unlock the thread APC queue lock, lower IRQL
// to its previous value, and raise an error condition.
OldCount = Thread->SuspendCount;
if (OldCount == MAXIMUM_SUSPEND_COUNT) {
KiUnlockDispatcherDatabaseFromSynchLevel();
KeReleaseInStackQueuedSpinLock(&LockHandle);
ExRaiseStatus(STATUS_SUSPEND_COUNT_EXCEEDED);
}

// Don't suspend the thread if APC queuing is disabled. In this case the
// thread is being deleted.
if (Thread->ApcQueueable == TRUE) {

// Increment the suspend count. If the thread was not previously
// suspended, then queue the thread's suspend APC.
Thread->SuspendCount += 1;
if ((OldCount == 0) && (Thread->FreezeCount == 0)) {
if (KiInsertQueueApc(&Thread->SuspendApc, RESUME_INCREMENT) == FALSE) {
Thread->SuspendSemaphore.Header.SignalState -= 1;
}
}
}

// Unlock the dispatcher database from SYNCH_LEVEL, unlock the thread APC
// queue lock and lower IRQL to its previous value, and return the old
// count.

KiUnlockDispatcherDatabaseFromSynchLevel();
KeReleaseInStackQueuedSpinLock(&LockHandle);
return OldCount;
}

KTHREAD.SuspendCount

SuspendCount:记录线程被挂起的次数。
  • 在调用SuspendThread时,SuspendCount 自加1;如果大于0表示挂起了。
  • 调用ResumeThread时,SuspendCount 自减1;如果等于0,系统会恢复线程。

SuspendCount的最大值为MAXIMUM_SUSPEND_COUNT源码的定义为(线程能够被挂起的最大次数):

1
2
#define MAXIMUM_SUSPEND_COUNT MAXCHAR
#define MAXCHAR 255

源码是:0xFF,这里逆向分析的是0x7F,为一半,不知道为啥 。

当挂起和恢复时,FreezeCount必须为0。

KTHREAD.FreezeCount

互联网上没有查到这个字段的详细解释,《Windows内核原理与实现》和《Windows内核情景分析》也没有解释,不过经过你想分析有这样的结论:

当线程首次挂起恢复运行时,必须有FreezeCount == 0才会继续执行。

所以,可以修改 SuspendCount 和 FreezeCount来达到反挂起的目的。

3 ResumeThread

ResumeThread函数:减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。

⚠️注意:该函数并不是直接将线程恢复,而只是减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。

函数原型如下:

1
DWORD __stdcall ResumeThread([in] HANDLE hThread)	//hThread:要重新启动的线程的句柄

返回值:如果函数成功,返回值是线程之前的暂停计数。如果函数失败,返回值为(DWORD)-1。要获取扩展错误信息,请调用GetLastError

函数调用过程:

1
ResumeThread --> NtResumeThread(0CEh) --> NtResumeThread --> PsResumeThread --> KeResumeThread

3.1 NtResumeThread

NtResumeThread函数:This function resumes a thread that was previously suspened.

1
2
3
4
NTSTATUS __stdcall NtResumeThread(
IN HANDLE ThreadHandle, //线程句柄
OUT PULONG PreviousSuspendCount OPTIONAL //指向线程原先被挂起次数
)

逆向分析函数流程如下:

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
NtResumeThread(HANDLE ThreadHandle, PULONG SuspendCount)

if(PreviousMode != 0) //调用来自3环,需要进行参数检查
{
try
{
if(SuspendCount >= MmUserProbeAddress)
{
MmUserProbeAddress = 0; //抛出异常,因为该NULL地址不可写
}
}except (EXCEPTION_EXECUTE_HANDLER)
{
return GetExceptionCode();
}

}

Object = ObReferenceObjectByHandle(); //句柄转对象地址
PsResumeThread(); //恢复挂起的线程
ObfDereferenceObject(); //减少指定对象的对象引用计数,并在计数变为零时进行清理工作。不同于句柄引用计数
try
{
if( ((PULONG))SuspendCount ) *(PULONG)SuspendCount = OUT_SuspendCount;
}except (EXCEPTION_EXECUTE_HANDLER)
{
return GetExceptionCode ();
}

return STATUS_SUCCESS;

⚠️注意:该函数只是简单的对参数进行了检查,然后将句柄转换为对象地址,最后调用函数PsResumeThread

并没有看到怎么处理:减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。

源码如下:

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
//Routine Description: This function resumes a thread that was previously suspened
NTSTATUS NtResumeThread(
IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL
)
{
PETHREAD Thread;
NTSTATUS st;
KPROCESSOR_MODE Mode;
ULONG LocalPreviousSuspendCount;

PAGED_CODE();

try {

Mode = KeGetPreviousMode();

if ( Mode != KernelMode ) {
if (ARGUMENT_PRESENT(PreviousSuspendCount))
ProbeForWriteUlong(PreviousSuspendCount);
}
} except (EXCEPTION_EXECUTE_HANDLER) {

return GetExceptionCode();
}

st = ObReferenceObjectByHandle(
ThreadHandle,
THREAD_SUSPEND_RESUME,
PsThreadType,
Mode,
(PVOID *)&Thread,
NULL
);

if ( !NT_SUCCESS(st) ) {
return st;
}

PsResumeThread (Thread, &LocalPreviousSuspendCount);

ObDereferenceObject (Thread);

try {
if (ARGUMENT_PRESENT (PreviousSuspendCount)) {
*PreviousSuspendCount = LocalPreviousSuspendCount;
}

} except (EXCEPTION_EXECUTE_HANDLER) {

return GetExceptionCode ();
}

return STATUS_SUCCESS;

}

3.2 PsResumeThread

PsResumeThread函数:This function resumes a thread that was previously suspened.

1
2
3
4
NTSTATUS __stdcal PsResumeThread (
IN PETHREAD Thread,
OUT PULONG PreviousSuspendCount OPTIONAL
)

逆向分析函数流程如下:

1
2
ulRet = (ULONG)KeResumeThread();
if(PreviousSuspendCount) *PreviousSuspendCount = ulRet;

函数太简单了,下面看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Routine Description: This function resumes a thread that was previously suspened
NTSTATUS PsResumeThread (
IN PETHREAD Thread,
OUT PULONG PreviousSuspendCount OPTIONAL
)
{
ULONG LocalPreviousSuspendCount;

LocalPreviousSuspendCount = (ULONG) KeResumeThread(&Thread->Tcb);

if (ARGUMENT_PRESENT (PreviousSuspendCount)) {
*PreviousSuspendCount = LocalPreviousSuspendCount;
}

return STATUS_SUCCESS;
}

⚠️注意:

并没有看到怎么处理:减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。

3.3 KeResumeThread

KeResumeThread函数:此函数恢复挂起线程的执行。如果指定的线程未挂起,则不执行任何操作。

1
ULONG __stdcall KeResumeThread ( IN PKTHREAD Thread )

返回值:The previous suspend count.

逆向分析函数流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(SuspendCount != 0)	//线程被挂起了
{

SuspendCount -= 1;
if(SuspendCount != 0) return SuspendCount; //说明还有其他人把这个线程挂起,还不能恢复
else //SuspendCount == 0,线程可以恢复
{
if(FreezeCount == 0) //执行恢复线程
{
SuspendSemaphore.Header.SignalState += 1;
KiWaitTest(); //循环地将在该等待对象上的所有线程从等待网上摘掉,并且从线程等待链上摘掉,信号值也进行处理
}
}

}
return SuspendCount;

在这里我们看到了怎么处理:减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。

源代码如下:

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
ULONG KeResumeThread ( IN PKTHREAD Thread )
/*++
Routine Description:
This function resumes the execution of a suspended thread. If the
specified thread is not suspended, then no operation is performed.
Return Value: The previous suspend count.
--*/
{

ULONG OldCount;
KIRQL OldIrql;

ASSERT_THREAD(Thread);
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

// Raise IRQL to dispatcher level and lock dispatcher database.
KiLockDispatcherDatabase(&OldIrql);

// Capture the current suspend count.
OldCount = Thread->SuspendCount;

// If the thread is currently suspended, then decrement its suspend count.
if (OldCount != 0) { //线程被挂起了
Thread->SuspendCount -= 1;

// If the resultant suspend count is zero and the freeze count is
// zero, then resume the thread by releasing its suspend semaphore.
if ((Thread->SuspendCount == 0) && (Thread->FreezeCount == 0)) {
Thread->SuspendSemaphore.Header.SignalState += 1;
KiWaitTest(&Thread->SuspendSemaphore, RESUME_INCREMENT);
}
}

// Unlock dispatcher database and lower IRQL to its previous value.
KiUnlockDispatcherDatabase(OldIrql);

// Return the previous suspend count.
return OldCount;
}