Windows XP APC(二)

ʕ •̀ o •́ ʔ

1 APC函数执行

上一篇文章《Windows XP APC(一)》已经将APC内核对象插入到目标线程的APC队列讲解完成了,接下来讲解APC函数在目标线程中是如何执行的。

一个线程如果要执行挂在APC队列的APC函数,最先会调用函数KiDeliverApc去处理后续的流程。

1.1 APC函数执行的条件

哪些情况下线程会执行APC函数:

  1. 线程切换。线程切换后(KiSwapThread),新线程执行时会调用KiDeliverApc
  2. 系统调用、中断、异常。会执行函数_KiServiceExit,然后调用KiDeliverApc

总结一句话:凡是会调用KiDeliverApc函数的点,都有机会执行APC函数。

函数_KiServiceExit是CPU从系统调用、中断或异常处理返回用户空间时的必经之路。

在函数KiDeliverApc按一下X查看一下交叉引用(ntkrpamp.exe2-9-9-12分页多核):

2.png

实际上调用KiDeliverApc的函数有以上那些。

然后看一下《Windows内核原理与实现5.2 P335》中,在WRK版本里除了上面提到的线程切换、系统调用、中断、异常,还有一种情况是:

当内核代码离开一个临界区或者守护区(调用KeLeaveGuardedRegionKeLeaveCriticalRegion)时,通过KiCheckForKernelApcDelivery函数直接调用KiDeliverApc,或者调用KiRequestSoftwareInterrupt函数请求一个APC_LEVEL的软件中断。这是因为,当线程进入临界区或守护区时,普通内核模式APC或特殊内核模式APC被禁止了,所以,当离开时,KiCheckForKernelApcDelivery函数被调用,以便及时地交付内核模式APC。

1.2 内核APC执行过程

跟着海哥追了一下SwapContext最后的代码,如果切换后的线程NextThread的KernelApcPending != 0,则会返回eax = 1,然后一直返回到函数KiSwapThread中,紧接着该函数就调用函数KiDeliverApc去线程的APC。

KiDeliverApc函数执行内核APC流程:
1)判断第一个链表是否为空
2)判断KTHREAD.ApcState.KernelApcInProgress是否为1
3)判断是否禁用内核APC(KTHREAD.KernelApcDisable是否为1)
4)将当前KAPC结构体从链表中摘除
5)执行KAPC.KernelRoutine指定的函数 释放KAPC结构体占用的空间
6)将KTHREAD.ApcState.KernelApcInProgress设置为1 标识正在执行内核APC
7)执行真正的内核APC函数(KAPC.NormalRoutine)
8)执行完毕 将KernelApcInProgress改为0
9)循环

实际执行过程会在后面的KiDeliverApc函数逆向分析时候具体解释。

总结:
1)内核APC在线程切换的时候就会执行,这也就意味着,只要插入内核APC很快就会执行。
2)在执行用户APC之前会先执行内核APC。
3)内核APC在内核空间执行,不需要换栈,一个循环全部执行完毕。

1.3 用户APC执行过程

处理用户APC要比内核APC复杂的多,因为用户APC函数要在用户空间执行的,这里涉及到大量换栈的操作:

  1. 当线程从用户层进入内核层时,要保留原来的运行环境,比如各种寄存器,栈的位置等等 (_Trap_Frame),然后切换成内核的堆栈,如果正常返回,恢复堆栈环境即可。

  2. 但如果有用户APC要执行的话,就意味着线程要提前返回到用户空间去执行,而且返回的位置不是线程进入内核时的位置,而是返回到其他的位置,每处理一个用户APC都会涉及到:内核 –> 用户空间 –> 再回到内核空间。

KiDeliverApc函数分析:
1)判断用户APC链表是否为空
2)判断第一个参数是为1
3)判断ApcState.UserApcPending是否为1
4)将ApcState.UserApcPending设置为0
5)链表操作 将当前APC从用户队列中拆除
6)调用函数(KAPC.KernelRoutine)释放KAPC结构体内存空间
7)调用KiInitializeUserApc函数

  1. KiInitializeUserApc函数分析:备份CONTEXT

    线程进0环时,原来3环的运行环境(寄存器栈顶等)保存到0环的一块内存_Trap_Frame结构体中,如果要提前返回3环去处理用户APC,就必须要修改_Trap_Frame结构体,但是又要保证不影响正常调用时线程返回到原来3环的地方,所以需要对_Trap_Frame的值进行备份:

    • 比如:进0环时的位置存储在EIP中,现在要提前返回,而且返回的并不是原来的位置,那就意味着必须要修改EIP为新的返回位置。还有堆栈ESP也要修改为处理APC需要的堆栈。那原来的值怎么办呢?处理完APC后该如何返回原来的位置呢?
    • 函数KiInitializeUserApc要做的第一件事就是备份:将原来_Trap_Frame的值备份到一个新的结构体中(CONTEXT),这个功能由其子函数KeContextFromKframes来完成。
  2. KiInitializeUserApc函数分析:线程0环与3环堆栈切换、准备用户层执行环境。

    • 堆栈示意图如下

      3.png

    • 准备用户层执行环境

      4.png

  3. ntdll.KiUserApcDispatcher函数分析:

    1、当用户在3环调用QueueUserAPC函数来插入APC时,不需要提供NormalRoutine,这个参数是在QueueUserAPC内部指定的:
    BaseDispatchAPC
    2、ZwContinue函数的意义:

    • 返回内核,如果还有用户APC,重复上面的执行过程。
    • 如果没有需要执行的用户APC,会将CONTEXT赋值给Trap_Frame结构体。就像从来没有修改过一样。ZwContinue后面的代码不会执行,线程从哪里进0环仍然会从哪里回去。

2 KiDeliverApc

KiDeliverApc:This function is called from the APC interrupt code and when one or more of the APC pending flags are set at system exit and the previous IRQL is zero. All special kernel APC’s are delivered first, followed by normal kernel APC’s if one is not already in progress, and finally if the user APC queue is not empty, the user APC pending flag is set, and the previous mode is user, then a user APC is delivered. On entry to this routine IRQL is set to APC_LEVEL.

1
2
3
4
5
VOID __stdcall KiDeliverApc (
IN KPROCESSOR_MODE PreviousMode, //调用函数的先前模式
IN PKEXCEPTION_FRAME ExceptionFrame,//指向异常帧,在 NT386 模式下该指针为 NULL
IN PKTRAP_FRAME TrapFrame //_Trap_Frame
)

函数逆向分析如下:

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
VOID __stdcall KiDeliverApc(PreviousMode, ExceptionFrame, TrapFrame)

if(TrapFrame != 0)
{
if(TrapFrame->eip >= (ULONG)&ExpInterlockedPopEntrySListResume &&
TrapFrame->eip <= (ULONG)&ExpInterlockedPopEntrySListEnd)
{
TrapFrame->eip = (ULONG)&ExpInterlockedPopEntrySListResume;
}
}

Old_TrapFrame = CurrentThread->TrapFrame;
Old_Process = CurrentThread->ApcState->Process;

CurrentThread->TrapFrame = TrapFrame;
CurrentThread->ApcState->KernelApcPending = 0;

Thread->ApcState.KernelApcPending = FALSE;

//内核APC不为空
while(&CurrentThread->ApcState->ApcListHead[0] != CurrentThread->ApcState->ApcListHead[0]->Flink)
{
NextEntry = CurrentThread->ApcState->ApcListHead[0]->Flink;
Apc = (KAPC)(KAPC->ApcListEntry - 0xC);
KernelRoutine = Apc->KernelRoutine;
NormalRoutine = Apc->NormalRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;

//普通内核APC函数
if(Apc->NormalRoutine != 0)
{
if(CurrentThread.ApcState.KernelApcInProgress == 0 && CurrentThread.KernelApcDisable == 0)
{
//将当前APC对象从内核APC队列里摘除
//将CurrentThread->ApcState->ApcListHead[0]->Flink摘除
ecx = NextEntry->Flink;
eax = NextEntry->Blink;
NextEntry->Blink->Flink = NextEntry->Flink;
NextEntry->Flink->Blink = NextEntry->Blink;

Apc.Inserted = 0;
(KernelRoutine)(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);

if(Apc->NormalRoutine != 0)
{
CurrentThread->ApcState->KernelApcInProgress = 1;
KfLowerIrql(0);

NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);

KfRaiseIrql(1); //APC_LEVEL
} //00428896
CurrentThread->ApcState->KernelApcInProgress = 0;
}else //00428967,CurrentThread.ApcState.KernelApcInProgress != 0 || CurrentThread.KernelApcDisable != 0
{
goto BugCheckAndReturn;
}

//特殊APC,KAPC->NormalRoutine == 0,00428818
}else
{
//将当前APC对象从内核APC队列里摘除
//将CurrentThread->ApcState->ApcListHead[0]->Flink摘除
ecx = NextEntry->Flink;
eax = NextEntry->Blink;
NextEntry->Blink->Flink = NextEntry->Flink;
NextEntry->Flink->Blink = NextEntry->Blink;

Apc.Inserted = 0;
(KernelRoutine)(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
}
}

//用户APC,004288D4,004288E1
NextEntry = CurrentThread->ApcState->ApcListHead[1]->Flink;
if(&CurrentThread->ApcState->ApcListHead[1] != NextEntry && PreviousMode == 1 &&
CurrentThread->ApcState[UserMode]->UserApcPending == 1)
{
//提前声明用户APC队列没有正在等待执行的用户APC
Thread->ApcState[UserMode].UserApcPending = FALSE;

Apc = (KAPC)(KAPC->ApcListEntry - 0xC);
KernelRoutine = Apc->KernelRoutine;
NormalRoutine = Apc->NormalRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;

//将当前APC对象从用户APC队列里摘除
ecx = NextEntry->Flink;
eax = NextEntry->Blink;
NextEntry->Blink->Flink = NextEntry->Flink;
NextEntry->Flink->Blink = NextEntry->Blink;

Apc->Inserted = 0;
(KernelRoutine)(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);

if(Apc->NormalRoutine != 0)
{
// 准备回3环调用 NormalContext
KiInitializeUserApc(ExceptionFrame,
TrapFrame,
NormalRoutine, // 用户APC总入口 BaseDispatchAPC(3环函数)
NormalContext, // 3环APC函数
SystemArgument1, // 3环APC函数的参数
SystemArgument2); // 作用不明,BaseDispatchAPC 里用到了
}else //0042893F
{
//检查该线程是否可以交付另一个用户模式的APC
KeTestAlertThread(UserMode); //UserMode == 1
goto BugCheckAndReturn;
}
}

BugCheckAndReturn:
//Check if process was attached during the APC routine
if(CurrentThread->ApcState->Process != Old_Process) KeBugCheckEx();
CurrentThread->TrapFrame = Old_TrapFrame;
return;

函数KiDeliverApc主要功能如下:

  1. 先将当前线程的TrapFrame和所属进程进行备份,然后使用参数中的TrapFrame给当前线程的TrapFrame赋值;
  2. KernelApcPending = FALSE提前声明没有待执行的内核APC函数,因为马上会循环全部执行完;
  3. 循环检查内核APC队列,不为空的话,再判断内核APC函数是普通APC还是特殊APC:
    • 普通APC
      • 如果KernelApcInProgress ==0 && CurrentThread.KernelApcDisable == 0,将当前APC对象从内核APC队列里摘除。如果前面的条件不满足就去BugCheckAndReturn检查是否需要调用函数KeBugCheckEx去蓝屏;
      • 执行函数Apc->KernelRoutine
      • 如果Apc->NormalRoutine != 0的话(KernelRoutine的执行有可能改变这个指针的值)就先将KernelApcInProgress = TRUE,并将IRQL降为PASSIVE_LEVEL(0)
      • 执行函数Apc->NormalRoutine
      • 将IRQL升为APC_LEVEL(1)
      • 设置KernelApcInProgress = FALSE
    • 特殊APC
      • 将当前APC对象从内核APC队列里摘除;
      • 执行函数Apc->KernelRoutine
  4. 如果用户APC队列不为空,并且UserApcPending == 1,就会试图执行用户APC。
    • UserApcPending = FALSE,提前声明用户APC队列没有正在等待执行的用户APC,因为一次只会执行一次(没有循环);
    • 将当前APC对象从用户APC队列里摘除;
    • 执行函数Apc->KernelRoutine
    • 判断Apc->NormalRoutine是否为0KernelRoutine的执行有可能改变这个指针的值):
  5. 恢复TrapFrame。用之前备份的TrapFrame给当前线程的TrapFrame赋值。

需要注意一下几点:

  • 执行内核NormalRoutine时才会KernelApcInProgress = 1,执行KernelRoutineKernelApcInProgress不会置1。
  • 执行内核NormalRoutineIRQL是0
  • 用函数KiInitializeUserApc去准备回3环调用Apc->NormalContext时并不会将IRQL降为0
  • 每次进入函数KiDeliverApc,会将当前线程的所有内核APC函数全部循环执行了,但是只会执行挂在用户APC队列的第一个用户APC函数,处理用户APC函数并没有循环。《Windows内核情景分析5.8 P373》

APC整个执行过程在《Windows内核情景分析5.8》都有函数分析。

源代码如下:

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
VOID KiDeliverApc (
IN KPROCESSOR_MODE PreviousMode,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame
)
/*++
Routine Description:
This function is called from the APC interrupt code and when one or
more of the APC pending flags are set at system exit and the previous
IRQL is zero. All special kernel APC's are delivered first, followed
by normal kernel APC's if one is not already in progress, and finally
if the user APC queue is not empty, the user APC pending flag is set,
and the previous mode is user, then a user APC is delivered. On entry
to this routine IRQL is set to APC_LEVEL.

N.B. The exception frame and trap frame addresses are only guaranteed
to be valid if, and only if, the previous mode is user.
Arguments:
PreviousMode - Supplies the previous processor mode.

指向异常帧,在 NT386 模式下该指针为 NULL
ExceptionFrame - Supplies a pointer to an exception frame.
TrapFrame - Supplies a pointer to a trap frame.
Return Value: None.
--*/
{

PKAPC Apc;
PKKERNEL_ROUTINE KernelRoutine;
KLOCK_QUEUE_HANDLE LockHandle;
PLIST_ENTRY NextEntry;
ULONG64 NewPC;
PVOID NormalContext;
PKNORMAL_ROUTINE NormalRoutine;
ULONG64 PC;
PKPROCESS Process;
PVOID SystemArgument1;
PVOID SystemArgument2;
PKTHREAD Thread;
PKTRAP_FRAME OldTrapFrame;

//
// If the thread was interrupted in the middle of the SLIST pop code,
// then back up the PC to the start of the SLIST pop.
//

if (TrapFrame != NULL) {

#if defined(_AMD64_)

if ((TrapFrame->Rip >= (ULONG64)&ExpInterlockedPopEntrySListResume) &&
(TrapFrame->Rip <= (ULONG64)&ExpInterlockedPopEntrySListEnd)) {

TrapFrame->Rip = (ULONG64)&ExpInterlockedPopEntrySListResume;
}

#elif defined(_IA64_)

//
// Add the slot number so we do the right thing for the instruction
// group containing the interlocked compare exchange.
//

PC = TrapFrame->StIIP + ((TrapFrame->StIPSR & IPSR_RI_MASK) >> PSR_RI);
NewPC = (ULONG64)((PPLABEL_DESCRIPTOR)ExpInterlockedPopEntrySListResume)->EntryPoint;
if ((PC >= NewPC) &&
(PC <= (ULONG64)((PPLABEL_DESCRIPTOR)ExpInterlockedPopEntrySListEnd)->EntryPoint)) {

TrapFrame->StIIP = NewPC;
TrapFrame->StIPSR &= ~IPSR_RI_MASK;
}

#elif defined(_X86_)

if ((TrapFrame->Eip >= (ULONG)&ExpInterlockedPopEntrySListResume) &&
(TrapFrame->Eip <= (ULONG)&ExpInterlockedPopEntrySListEnd)) {

TrapFrame->Eip = (ULONG)&ExpInterlockedPopEntrySListResume;
}

#else
#error "No Target Architecture"
#endif

}

//
// Raise IRQL to dispatcher level and lock the APC queue.
//

// 获取当前线程
Thread = KeGetCurrentThread();

OldTrapFrame = Thread->TrapFrame;

Thread->TrapFrame = TrapFrame;

// 获取当前进程(提供CR3的进程)
Process = Thread->ApcState.Process;

KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);

//
// Get address of current thread object, clear kernel APC pending, and
// check if any kernel mode APC's can be delivered.
//

// 接下来要执行内核APC,这里提前声明处理完毕
Thread->ApcState.KernelApcPending = FALSE;

// 遍历内核APC队列
while (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) {
// 获取 APC,获取 APC 的成员
NextEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
KernelRoutine = Apc->KernelRoutine;
NormalRoutine = Apc->NormalRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;

if (NormalRoutine == (PKNORMAL_ROUTINE)NULL)
{
// NormalRoutine 等于 NULL 的情况属于特殊内核APC,我不知道什么时候会插入这样的APC
// 所以这里就不分析了,假如您读到这里,又知道相关的信息,不妨留言提示我一下^_^
// 2020年11月29日21:04:21
//
// First entry in the kernel APC queue is a special kernel APC.
// Remove the entry from the APC queue, set its inserted state
// to FALSE, release dispatcher database lock, and call the kernel
// routine. On return raise IRQL to dispatcher level and lock
// dispatcher database lock.
//
RemoveEntryList(NextEntry);

Apc->Inserted = FALSE;

KeReleaseInStackQueuedSpinLock(&LockHandle);

(KernelRoutine)(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);

#if DBG
// 蓝屏警告
if (KeGetCurrentIrql() != LockHandle.OldIrql) {
KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
KeGetCurrentIrql() << 16 | LockHandle.OldIrql << 8,
(ULONG_PTR)KernelRoutine,
(ULONG_PTR)Apc,
(ULONG_PTR)NormalRoutine);
}

#endif

KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);

}
else
{
// 走这个分支说明 NormalRoutine 非空,是普通的内核APC,PspTerminateThreadByPointer 和 NtQueueApcThread 都走这里
//
// First entry in the kernel APC queue is a normal kernel APC.
// If there is not a normal kernel APC in progress and kernel
// APC's are not disabled, then remove the entry from the APC
// queue, set its inserted state to FALSE, release the APC queue
// lock, call the specified kernel routine, set kernel APC in
// progress, lower the IRQL to zero, and call the normal kernel
// APC routine. On return raise IRQL to dispatcher level, lock
// the APC queue, and clear kernel APC in progress.
//

if ((Thread->ApcState.KernelApcInProgress == FALSE) && // 没有内核APC正在执行 并且
(Thread->KernelApcDisable == 0)) // 没有禁用内核APC
{
// 从内核 APC 队列中移除这个 APC
RemoveEntryList(NextEntry);

// APC Inserted 标志清零
Apc->Inserted = FALSE;

KeReleaseInStackQueuedSpinLock(&LockHandle);

// 调用 KernelRoutine,举两个例子说明
// 如果 APC 通过PspTerminateThreadByPointer 构造, KernelRoutine 是 PsExitSpecialApc ,
// 那么执行的操作就是释放APC内存,并终止当前线程
// 如果 APC 通过 NtQueueApcThread 构造,KernelRoutine 是 PspQueueApcSpecialApc,
//执行的操作仅仅是释放APC内存
// 不过 NtQueueApcThread 插入的属于用户APC,不走这里,而是等内核APC执行完后再执行
// KernelRoutine 的工作是释放APC内存,也可能包括一些额外的工作,如退出、挂起、恢复线程
// KernelRoutine 是调用 KeInitializeApc 时决定的,是不确定的,各种函数对参数的使用情况都不一样
// 例如 PspTerminateThreadByPointer 初始化 KernelRoutine 传的函数是 PsExitSpecialApc,
//就只使用了第一个参数 Apc
(KernelRoutine)(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);

#if DBG

if (KeGetCurrentIrql() != LockHandle.OldIrql) {
KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
KeGetCurrentIrql() << 16 | LockHandle.OldIrql << 8 | 1,
(ULONG_PTR)KernelRoutine,
(ULONG_PTR)Apc,
(ULONG_PTR)NormalRoutine);
}

#endif

// NormalRoutine 是内核APC函数,经分析,我觉得能执行到这里,NormalRoutine 应该不是 NULL 的
// 唯一可能修改 NormalRoutine 的就是上面调用的 KernelRoutine 函数
if (NormalRoutine != (PKNORMAL_ROUTINE)NULL) {

// 内核APC正在执行
Thread->ApcState.KernelApcInProgress = TRUE;

// 降低IRQL到0
KeLowerIrql(0);

// 调用内核APC函数
(NormalRoutine)(NormalContext,
SystemArgument1,
SystemArgument2);

// 恢复IRQL到APC_LEVEL(1)
KeRaiseIrql(APC_LEVEL, &LockHandle.OldIrql);
}

KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);

// 没有内核APC正在执行
Thread->ApcState.KernelApcInProgress = FALSE;

} else {
KeReleaseInStackQueuedSpinLock(&LockHandle);
goto CheckProcess;
}
}
}

//
// Kernel APC queue is empty. If the previous mode is user, user APC
// pending is set, and the user APC queue is not empty, then remove
// the first entry from the user APC queue, set its inserted state to
// FALSE, clear user APC pending, release the dispatcher database lock,
// and call the specified kernel routine. If the normal routine address
// is not NULL on return from the kernel routine, then initialize the
// user mode APC context and return. Otherwise, check to determine if
// another user mode APC can be processed.
//
// 内核APC执行完毕
// 如果 PreviousMode 是用户模式(1),并且有用户APC,并且用户APC队列非空

if ((IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE) &&
(PreviousMode == UserMode) &&
(Thread->ApcState.UserApcPending != FALSE))
{
// 提前声明用户APC队列已清空
Thread->ApcState.UserApcPending = FALSE;

// 获取APC和其属性
NextEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
KernelRoutine = Apc->KernelRoutine;
NormalRoutine = Apc->NormalRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;

// 从用户APC队列中取出
RemoveEntryList(NextEntry);

// 标记插入状态为FALSE
Apc->Inserted = FALSE;

KeReleaseInStackQueuedSpinLock(&LockHandle);

// KernelRoutine 应该就是 PspQueueApcSpecialApc
// 因为用户APC是 NtQueueApcThread 函数构造和插入的,它就是这样初始化APC的
// PspQueueApcSpecialApc 的唯一作用是释放APC内存
(KernelRoutine)(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);

if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {
// 此函数定义在 thredobj.c
KeTestAlertThread(UserMode);

} else {
// 准备回3环调用 NormalContext
KiInitializeUserApc(ExceptionFrame,
TrapFrame,
NormalRoutine, // 用户APC总入口 BaseDispatchAPC(3环函数)
NormalContext, // 3环APC函数
SystemArgument1, // 3环APC函数的参数
SystemArgument2); // 作用不明,BaseDispatchAPC 里用到了
}

} else {
KeReleaseInStackQueuedSpinLock(&LockHandle);
}

// Check if process was attached during the APC routine.
// 检查当前进程是否发生变化(执行 APC 函数时发生了 attach)

CheckProcess:
if (Thread->ApcState.Process != Process) {
// 蓝屏警告
KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,
(ULONG_PTR)Process,
(ULONG_PTR)Thread->ApcState.Process,
(ULONG)Thread->ApcStateIndex,
(ULONG)KeIsExecutingDpc());
}

Thread->TrapFrame = OldTrapFrame;
return;
}

3 KeTestAlertThread

通过对函数KiDeliverApc交付(Deliver)用户APC函数的过程,在交付用户过程中会先执行KernelRoutine函数(参数包含NormalRoutine),函数执行过程中有可能会修改NormalRoutine,所以需要对NormalRoutine进行检查是否为0

为什么需要判断NormalRoutine是否为NULL,是因为多核情况下其他CPU在某个过程中已经执行了这个用户APC么?

我认为,并不是因为多核的原因。因为在交付用户APC函数之前,调用了函数KeReleaseInStackQueuedSpinLock上了自旋锁。同时参考《Windows情景分析P371》,KernelRoutine执行的时候有可能改变指针NormalRoutine的值,所以需要测试一下。

函数KeTestAlertThread:This function tests to determine if the alerted variable for the specified processor mode has a value of TRUE or whether a user mode APC should be delivered to the current thread.(processor mode:UserMode/KernelMode处理器模式,variable-变量,specified-指定的)

该函数检查当前线程是否可以交付第一个用户模式的APC

如果吵醒模式AlertMode == 1,并且用户APC队列不为空,那就设置 Thread->ApcState.UserApcPending = TRUE 然后交付下一个用户APC。

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
BOOLEAN __stdcall KeTestAlertThread (
IN KPROCESSOR_MODE AlertMode
)
{
BOOLEAN Alerted;
KLOCK_QUEUE_HANDLE LockHandle;
PKTHREAD Thread;

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
Thread = KeGetCurrentThread();
KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock, &LockHandle);
KiLockDispatcherDatabaseAtSynchLevel

Alerted = Thread->Alerted[AlertMode];
if (Alerted == TRUE) {
Thread->Alerted[AlertMode] = FALSE;
} else if ((AlertMode == UserMode) &&
(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) != TRUE)) {
Thread->ApcState.UserApcPending = TRUE;
}

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

Alerted:用来说明线程在指定模式下是否为”已经被吵醒” 。具体在哪进行修改,目前就知道在函数KeAlertThread中,当吵醒条件不满足时候就会Thread->Alerted[AlertMode] = TRUE;来说明已经调用过该函数一次了,只是吵醒条件不满足,如果可以吵醒是不会设置的为TRUE的。所以这里的Thread->Alerted[AlertMode] = FALSE;我理解就是一个额外的修正操作。**但是在APC的交付过程中,一般使用到该函数的else if处的功能,即Thread->ApcState.UserApcPending = TRUE**。

在APC注入过程中,经常使用该函数,因为函数KiInsertQueueApc在KAPC插入过后判断条件满足:

1
(Thread.State == Waiting && Thread.WaitMode == 1 && (Thread.Alertable == 1 || Thread.ApcState.UserApcPending == 1))

就会设置Thread.ApcState.UserApcPending = 1然后调用KiUnwaitThread让线程开始转为就绪,线程切换后就有机会执行APC函数。上面可以看到Alertable == 1UserApcPending == 1满足其一即可,所以一般调用函数KeTestAlertThread就可进行注入,可以参考:

4 _CONTEXT

每个线程都会维护一个_CONTEXT结构,里面保存着线程运行在3环时的寄存器的值,里面保存了线程运行的状态,使得CPU可以记得上次运行该线程运行到哪里了,该从哪里开始运行。在线程的挂起、恢复线程状态切换时经常用到。

_CONTEXT结构与_Trap_Frame结构很相似,都是保存一堆寄存器的值,但是在用途上却不一样:

  • _CONTEXT:线程发生状态切换时,将当前寄存器的值保存下来,方便恢复线程运行的时候回到原来的地方继续运行(一个CPU就一套寄存器,线程切换后其他线程要使用寄存器,自己的寄存器的值要先保存起来)。
  • _Trap_Frame:线程从3环进入0环时,将3环的寄存器值保存到该结构中,线程从0环正常返回到3环后从原来的地方继续运行。
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
kd> dt _CONTEXT -v
nt!_CONTEXT
struct _CONTEXT, 25 elements, 0x2cc bytes
+0x000 ContextFlags : Uint4B

//调试寄存器组,ContextFlags 设置为 CONTEXT_DEBUG_REGISTERS
//注意:CONTEXT_DEBUG_REGISTERS 没有被包含在 CONTEXT_FULL 中。
+0x004 Dr0 : Uint4B
+0x008 Dr1 : Uint4B
+0x00c Dr2 : Uint4B
+0x010 Dr3 : Uint4B
+0x014 Dr6 : Uint4B
+0x018 Dr7 : Uint4B

//浮点寄存器组,ContextFlags 设置为 CONTEXT_FLOATING_POINT
+0x01c FloatSave : struct _FLOATING_SAVE_AREA, 9 elements, 0x70 bytes

//段寄存器组,ContextFlags 字包含 CONTEXT_SEGMENTS
+0x08c SegGs : Uint4B
+0x090 SegFs : Uint4B
+0x094 SegEs : Uint4B
+0x098 SegDs : Uint4B

//通用数据寄存器(整型寄存器)组,ContextFlags 字包含 CONTEXT_INTEGER
+0x09c Edi : Uint4B
+0x0a0 Esi : Uint4B
+0x0a4 Ebx : Uint4B
+0x0a8 Edx : Uint4B
+0x0ac Ecx : Uint4B
+0x0b0 Eax : Uint4B

//控制寄存器组,ContextFlags 字包含 CONTEXT_CONTROL
+0x0b4 Ebp : Uint4B
+0x0b8 Eip : Uint4B
+0x0bc SegCs : Uint4B
+0x0c0 EFlags : Uint4B
+0x0c4 Esp : Uint4B
+0x0c8 SegSs : Uint4B

//拓展寄存器组,ContextFlags 字包含 CONTEXT_EXTENDED_REGISTERS
+0x0cc ExtendedRegisters : [512] UChar

源代码定义如下:

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
// Context Frame
//
// This frame has a several purposes: 1) it is used as an argument to
// NtContinue, 2) is is used to constuct a call frame for APC delivery,
// and 3) it is used in the user level thread creation routines.
//
// The layout of the record conforms to a standard call frame.

typedef struct _CONTEXT {

// The flags values within this flag control the contents of
// a CONTEXT record.
//
// If the context record is used as an input parameter, then
// for each portion of the context record controlled by a flag
// whose value is set, it is assumed that that portion of the
// context record contains valid context. If the context record
// is being used to modify a threads context, then only that
// portion of the threads context will be modified.
//
// If the context record is used as an IN OUT parameter to capture
// the context of a thread, then only those portions of the thread's
// context corresponding to set flags will be returned.
//
// The context record is never used as an OUT only parameter.

ULONG ContextFlags;

// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
// included in CONTEXT_FULL.
ULONG Dr0;
ULONG Dr1;
ULONG Dr2;
ULONG Dr3;
ULONG Dr6;
ULONG Dr7;

// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
FLOATING_SAVE_AREA FloatSave;

// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_SEGMENTS.
ULONG SegGs;
ULONG SegFs;
ULONG SegEs;
ULONG SegDs;

// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_INTEGER.
ULONG Edi;
ULONG Esi;
ULONG Ebx;
ULONG Edx;
ULONG Ecx;
ULONG Eax;

// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
ULONG Ebp;
ULONG Eip;
ULONG SegCs; // MUST BE SANITIZED
ULONG EFlags; // MUST BE SANITIZED
ULONG Esp;
ULONG SegSs;

// This section is specified/returned if the ContextFlags word
// contains the flag CONTEXT_EXTENDED_REGISTERS.
// The format and contexts are processor specific
UCHAR ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;

该结构的ContextFlags成员很重要,当我们要查询CONTEXT中某些寄存器的值的时候,需要先指定ContextFlags的值,由它来指定有哪些权限,能查询到哪些寄存器的值。

寄存器组 ContextFlags 包含的寄存器 对应值
调试寄存器组 CONTEXT_DEBUG_REGISTERS Dr0-7 0x00010000 | 0x00000010 == 0x00010010
浮点寄存器组 CONTEXT_FLOATING_POINT 387 state 0x00010000 | 0x00000008 == 0x00010008
段寄存器组 CONTEXT_SEGMENTS DS, ES, FS, GS 0x00010000 | 0x00000004 == 0x00010004
通用数据寄存器 CONTEXT_INTEGER AX, BX, CX, DX, SI, DI 0x00010000 | 0x00000002 == 0x00010002
控制寄存器组 CONTEXT_CONTROL SS:SP, CS:IP, FLAGS, BP 0x00010000 | 0x00000001 == 0x00010001
拓展寄存器组 CONTEXT_EXTENDED_REGISTERS cpu specific extensions 0x00010000 | 0x00000020 == 0x00010020
FULL字段 CONTEXT_FULL 控制、通用、段寄存器 CONTEXT_CONTROL |CONTEXT_INTEGER | CONTEXT_SEGMENTS
0x00010000 | 0x00000004 | 0x00000002 | 0x00000001 == 0x00010007

有这样的定义:CONTEXT_i386 = 0x00010000

还可以参考:[API档案] CONTEXT 结构Windows线程的上下文结构体(线程本质

5 KiInitializeUserApc

函数KiInitializeUserApc:该函数给用户模式的APC初始化一个CONTEXT结构。

1
2
3
4
5
6
7
8
VOID __stdcall KiInitializeUserApc (
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN PKNORMAL_ROUTINE NormalRoutine,
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)

执行环境条件:Environment:Kernel mode only, IRQL APC_LEVEL.

KiInitializeUserApc函数:

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
VOID KiInitializeUserApc (
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN PKNORMAL_ROUTINE NormalRoutine,
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
/*++
Routine Description: This function is called to initialize the context for a user mode APC.
Arguments:
ExceptionFrame - Supplies a pointer to an exception frame.
TrapFrame - Supplies a pointer to a trap frame.
NormalRoutine - Supplies a pointer to the user mode APC routine.
NormalContext - Supplies a pointer to the user context for the APC routine.
SystemArgument1 - Supplies the first system supplied value.
SystemArgument2 - Supplies the second system supplied value.

Return Value: None.
--*/
{
EXCEPTION_RECORD ExceptionRecord;
CONTEXT ContextFrame;
LONG Length;
ULONG UserStack;

// APCs are not defined for V86 mode; however, it is possible a
// thread is trying to set it's context to V86 mode - this isn't
// going to work, but we don't want to crash the system so we
// check for the possibility before hand.

if (TrapFrame->EFlags & EFLAGS_V86_MASK) {
return ;
}

// Move machine state from trap and exception frames to the context frame.

ContextFrame.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextFrame);

// Transfer the context information to the user stack, initialize the
// APC routine parameters, and modify the trap frame so execution will
// continue in user mode at the user mode APC dispatch routine.

try {
ASSERT((TrapFrame->SegCs & MODE_MASK) != KernelMode); // Assert usermode frame

// Compute length of context record and new aligned user stack pointer.
Length = ((sizeof(CONTEXT) + CONTEXT_ROUND) &
~CONTEXT_ROUND) + sizeof(KAPC_RECORD);
UserStack = (ContextFrame.Esp & ~CONTEXT_ROUND) - Length;

// Probe user stack area for writability and then transfer the
// context record to the user stack.
ProbeForWrite((PCHAR)UserStack, Length, CONTEXT_ALIGN);
RtlCopyMemory((PULONG)(UserStack + (sizeof(KAPC_RECORD))),
&ContextFrame, sizeof(CONTEXT));

// Force correct R3 selectors into TrapFrame.
TrapFrame->SegCs = SANITIZE_SEG(KGDT_R3_CODE, UserMode);
TrapFrame->HardwareSegSs = SANITIZE_SEG(KGDT_R3_DATA, UserMode);
TrapFrame->SegDs = SANITIZE_SEG(KGDT_R3_DATA, UserMode);
TrapFrame->SegEs = SANITIZE_SEG(KGDT_R3_DATA, UserMode);
TrapFrame->SegFs = SANITIZE_SEG(KGDT_R3_TEB, UserMode);
TrapFrame->SegGs = 0;
TrapFrame->EFlags = SANITIZE_FLAGS( ContextFrame.EFlags, UserMode );

// If thread is supposed to have IOPL, then force it on in eflags
if (KeGetCurrentThread()->Iopl) {
TrapFrame->EFlags |= (EFLAGS_IOPL_MASK & -1); // IOPL = 3
}

// Set the address of the user APC routine, the APC parameters, the
// new frame pointer, and the new stack pointer in the current trap
// frame. Set the continuation address so control will be transferred
// to the user APC dispatcher.
TrapFrame->HardwareEsp = UserStack;
TrapFrame->Eip = (ULONG)KiUserApcDispatcher; //当线程回到3环时执行的函数,位于ntdll.dll中
TrapFrame->ErrCode = 0;
*((PULONG)UserStack)++ = (ULONG)NormalRoutine;
*((PULONG)UserStack)++ = (ULONG)NormalContext;
*((PULONG)UserStack)++ = (ULONG)SystemArgument1;
*((PULONG)UserStack)++ = (ULONG)SystemArgument2;
} except (KiCopyInformation(&ExceptionRecord,
(GetExceptionInformation())->ExceptionRecord)) {

// Set the address of the exception to the current program address
// and raise the exception by calling the exception dispatcher.
ExceptionRecord.ExceptionAddress = (PVOID)(TrapFrame->Eip);
KiDispatchException(&ExceptionRecord,
ExceptionFrame,
TrapFrame,
UserMode,
TRUE);
}
return;
}

逆向分析KiInitializeUserApc

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
.text:0042D2C0 // =============== S U B R O U T I N E =======================================
.text:0042D2C0
.text:0042D2C0 // ----
.text:0042D2C0 // VOID __stdcall KiInitializeUserApc (
.text:0042D2C0 // IN PKEXCEPTION_FRAME ExceptionFrame,
.text:0042D2C0 // IN PKTRAP_FRAME TrapFrame,
.text:0042D2C0 // IN PKNORMAL_ROUTINE NormalRoutine,
.text:0042D2C0 // IN PVOID NormalContext,
.text:0042D2C0 // IN PVOID SystemArgument1,
.text:0042D2C0 // IN PVOID SystemArgument2
.text:0042D2C0 // )
.text:0042D2C0 // ----
.text:0042D2C0 // Attributes: bp-based frame
.text:0042D2C0
.text:0042D2C0 // __stdcall KiInitializeUserApc(x, x, x, x, x, x)
.text:0042D2C0 _KiInitializeUserApc@24 proc near // CODE XREF: KiDeliverApc(x,x,x)+1D0↑p
.text:0042D2C0
.text:0042D2C0 ExceptionRecord = EXCEPTION_RECORD ptr -34Ch
.text:0042D2C0 var_2FC = dword ptr -2FCh
.text:0042D2C0 var_2F8 = dword ptr -2F8h
.text:0042D2C0 var_ExceptionFrame= dword ptr -2F4h
.text:0042D2C0 BugCheckParameter3= dword ptr -2F0h
.text:0042D2C0 var_2EC = dword ptr -2ECh
.text:0042D2C0 var_2E8 = dword ptr -2E8h
.text:0042D2C0 var_228 = dword ptr -228h
.text:0042D2C0 var_224 = dword ptr -224h
.text:0042D2C0 var_JinSiQue = dword ptr -1Ch
.text:0042D2C0 ms_exc = CPPEH_RECORD ptr -18h
.text:0042D2C0 ExceptionFrame = dword ptr 8
.text:0042D2C0 TrapFrame = dword ptr 0Ch
.text:0042D2C0 NormalRoutine = dword ptr 10h
.text:0042D2C0 NormalContext = dword ptr 14h
.text:0042D2C0 SystemArgument1 = dword ptr 18h
.text:0042D2C0 SystemArgument2 = dword ptr 1Ch
.text:0042D2C0
.text:0042D2C0 // __unwind { // __SEH_prolog
.text:0042D2C0 push 33Ch
.text:0042D2C5 push offset stru_402AC0
.text:0042D2CA call __SEH_prolog
.text:0042D2CF mov eax, ___security_cookie // ----
.text:0042D2CF // 金丝雀
.text:0042D2CF // 实际上就是用来检查缓冲区(局部数组)是否已经溢出。
.text:0042D2CF // 原来:在函数堆栈存放顺序:ebp/返回地址 security_cookie Buffer[MAX_SIZE]
.text:0042D2CF // 由于数组Buffer是从低地址向高地址存放的,如果溢出了就会覆盖掉security_cookie,
.text:0042D2CF //
.text:0042D2CF //
.text:0042D2CF // 在函数执行的最后快要返回时,判断一下这个变量security_cookie的值是否被覆盖,
.text:0042D2CF // 所以金丝雀可以用来检查缓冲区是否溢出。(https://www.kn0sky.com/?p=66)
.text:0042D2CF // ----
.text:0042D2D4 mov [ebp+var_JinSiQue], eax
.text:0042D2D7 mov eax, [ebp+ExceptionFrame]
.text:0042D2DA mov [ebp+var_ExceptionFrame], eax
.text:0042D2E0 mov ebx, [ebp+TrapFrame]
.text:0042D2E3 mov [ebp+BugCheckParameter3], ebx
.text:0042D2E9 test byte ptr [ebx+(_KTRAP_FRAME.EFlags+2)], 2 // ----
.text:0042D2E9 // 拿Eflag的高2字节的同 0010 做比较
.text:0042D2E9 // 判断是否是虚拟8086模式
.text:0042D2E9 // ----
.text:0042D2ED jnz loc_42D43D // 虚拟8086模式
.text:0042D2F3 mov [ebp+var_2E8], 10017h
.text:0042D2FD lea ecx, [ebp+var_2E8] // ContextRecord
.text:0042D303 push ecx // 一个局部变量的地址
.text:0042D304 push eax // ExceptionFrame
.text:0042D305 push ebx // TrapFrame
.text:0042D306 call _KeContextFromKframes@12 // ----
.text:0042D306 // VOID __stdcall KeContextFromKframes (
.text:0042D306 // IN PKTRAP_FRAME TrapFrame,
.text:0042D306 // IN PKEXCEPTION_FRAME ExceptionFrame,
.text:0042D306 // IN OUT PCONTEXT ContextRecord
.text:0042D306 // )
.text:0042D306 //
.text:0042D306 // 可以看到根本没有使用到 ebp+0xC,即没有使用到ExceptionFrame
.text:0042D306 // ----
.text:0042D30B // __try { // __except at loc_42D410
.text:0042D30B and [ebp+ms_exc.registration.TryLevel], 0
.text:0042D30F mov eax, 2DCh
.text:0042D314 mov [ebp+var_2F8], eax // var_2F8 =0x2DC
.text:0042D31A mov esi, [ebp+var_224] // esi = esp3
.text:0042D320 and esi, 0FFFFFFFCh // 4字节对齐
.text:0042D323 sub esi, eax // ----
.text:0042D323 // esp3 = esp3 - 0x2DC,现在的esp3已经抬高
.text:0042D323 // sizeof(CONTEXT) +0x10 == 0x2DC
.text:0042D323 //
.text:0042D323 // 这里0x10为:
.text:0042D323 // //APC Parameter structure.
.text:0042D323 // typedef struct _KAPC_RECORD {
.text:0042D323 // PKNORMAL_ROUTINE NormalRoutine//
.text:0042D323 // PVOID NormalContext//
.text:0042D323 // PVOID SystemArgument1//
.text:0042D323 // PVOID SystemArgument2//
.text:0042D323 // } KAPC_RECORD, *PKAPC_RECORD//
.text:0042D323 // ----
.text:0042D325 mov [ebp+var_2EC], esi
.text:0042D32B push 4 // Alignment
.text:0042D32D push eax // Length
.text:0042D32E push esi // Address
.text:0042D32F call _ProbeForWrite@12 // ----
.text:0042D32F // void ProbeForWrite_(PVOID Address, ULONG Length, ULONG Alignment)
.text:0042D32F // 判断指定长度的3环缓冲区是否可写、该长度的缓冲区是否已经按参数三指定的字节长度进行对齐。
.text:0042D32F // 参数一只能指向3环的地址,不能用于0环的地址检查。
.text:0042D32F //
.text:0042D32F // 如果不可写或者没有按指定字节长度对齐就会抛出异常
.text:0042D32F // ----
.text:0042D32F //
.text:0042D334 lea edi, [esi+10h] // 将局部变量CONTEXT结构拷贝到esp3刚开辟的高0x2CC空间中,剩下0x10的空间用于存放参数
.text:0042D337 mov ecx, 0B3h
.text:0042D33C lea esi, [ebp+var_2E8] // 将数据从0环复制到3环地址空间
.text:0042D342 rep movsd
.text:0042D344 mov [ebx+_KTRAP_FRAME.SegCs], 1Bh // 开始修改 TrapFrame 段寄存器的值
.text:0042D34B push 23h
.text:0042D34D pop eax
.text:0042D34E mov [ebx+_KTRAP_FRAME.HardwareSegSs], eax
.text:0042D351 mov [ebx+_KTRAP_FRAME.SegDs], eax
.text:0042D354 mov [ebx+_KTRAP_FRAME.SegEs], eax
.text:0042D357 mov [ebx+_KTRAP_FRAME.SegFs], 3Bh
.text:0042D35E and [ebx+_KTRAP_FRAME.SegGs], 0
.text:0042D362 mov ecx, [ebp+var_228] // ecx = CONTEXT.EFlags
.text:0042D368 test ecx, 20000h // 判断是否是虚拟8086模式
.text:0042D36E jz short loc_42D388 // 非虚拟8086模式
.text:0042D370 cmp byte ptr _KeI386VdmIoplAllowed, 0
.text:0042D377 jz short loc_42D388 // 非虚拟8086模式
.text:0042D379 mov eax, _KeI386EFlagsAndMaskV86
.text:0042D37E and eax, ecx
.text:0042D380 or eax, _KeI386EFlagsOrMaskV86
.text:0042D386 jmp short loc_42D396
.text:0042D388 // ---------------------------------------------------------------------------
.text:0042D388
.text:0042D388 loc_42D388: // CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+AE↑j
.text:0042D388 // KiInitializeUserApc(x,x,x,x,x,x)+B7↑j
.text:0042D388 and ecx, 3E0DD7h // 非虚拟8086模式
.text:0042D38E or ecx, 200h
.text:0042D394 mov eax, ecx
.text:0042D396
.text:0042D396 loc_42D396: // CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+C6↑j
.text:0042D396 mov [ebx+_KTRAP_FRAME.EFlags], eax
.text:0042D399 mov eax, large fs:124h
.text:0042D39F mov [ebp+var_2FC], eax
.text:0042D3A5 cmp [eax+_KTHREAD.Iopl], 0
.text:0042D3A9 jz short loc_42D3AF // eax == 抬升 0x2DC 后的 ESP3
.text:0042D3AB or byte ptr [ebx+71h], 30h // If thread is supposed to have IOPL, then force it on in eflags
.text:0042D3AF
.text:0042D3AF loc_42D3AF: // CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+E9↑j
.text:0042D3AF mov eax, [ebp+var_2EC] // eax == 抬升 0x2DC 后的 ESP3
.text:0042D3B5 mov [ebx+_KTRAP_FRAME.HardwareEsp], eax // 修改 _TRAP_FRAME 的 ESP3,修改堆栈位置
.text:0042D3B8 mov ecx, _KeUserApcDispatcher
.text:0042D3BE mov [ebx+_KTRAP_FRAME._Eip], ecx // 修改EIP
.text:0042D3C1 and [ebx+_KTRAP_FRAME.ErrCode], 0
.text:0042D3C5 mov ecx, [ebp+NormalRoutine] // 4个参数入栈
.text:0042D3C8 mov [eax], ecx
.text:0042D3CA push 4
.text:0042D3CC pop ecx
.text:0042D3CD add eax, ecx
.text:0042D3CF mov [ebp+var_2EC], eax
.text:0042D3D5 mov edx, [ebp+NormalContext]
.text:0042D3D8 mov [eax], edx
.text:0042D3DA add eax, ecx
.text:0042D3DC mov [ebp+var_2EC], eax
.text:0042D3E2 mov edx, [ebp+SystemArgument1]
.text:0042D3E5 mov [eax], edx
.text:0042D3E7 add eax, ecx
.text:0042D3E9 mov [ebp+var_2EC], eax
.text:0042D3EF mov edx, [ebp+SystemArgument2]
.text:0042D3F2 mov [eax], edx
.text:0042D3F4 add eax, ecx
.text:0042D3F6 mov [ebp+var_2EC], eax
.text:0042D3FC jmp short loc_42D439
.text:0042D3FE // ---------------------------------------------------------------------------
.text:0042D3FE
.text:0042D3FE loc_42D3FE: // DATA XREF: .text:stru_402AC0↑o
.text:0042D3FE // __except filter // owned by 42D30B
.text:0042D3FE mov eax, [ebp+ms_exc.exc_ptr]
.text:0042D401 push dword ptr [eax]
.text:0042D403 lea eax, [ebp+ExceptionRecord]
.text:0042D409 push eax
.text:0042D40A call _KiCopyInformation@8 // KiCopyInformation(x,x)
.text:0042D40F retn
.text:0042D410 // ---------------------------------------------------------------------------
.text:0042D410
.text:0042D410 loc_42D410: // DATA XREF: .text:stru_402AC0↑o
.text:0042D410 // __except(loc_42D3FE) // owned by 42D30B
.text:0042D410 mov esp, [ebp+ms_exc.old_esp]
.text:0042D413 mov eax, [ebp+BugCheckParameter3]
.text:0042D419 mov ecx, [eax+68h]
.text:0042D41C mov [ebp+ExceptionRecord.ExceptionAddress], ecx
.text:0042D422 push 1 // char
.text:0042D424 push 1 // int
.text:0042D426 push eax // BugCheckParameter3
.text:0042D427 push [ebp+var_ExceptionFrame] // int
.text:0042D42D lea eax, [ebp+ExceptionRecord]
.text:0042D433 push eax // ExceptionRecord
.text:0042D434 call _KiDispatchException@20 // KiDispatchException(x,x,x,x,x)
.text:0042D434 // } // starts at 42D30B
.text:0042D439
.text:0042D439 loc_42D439: // CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+13C↑j
.text:0042D439 or [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
.text:0042D43D
.text:0042D43D loc_42D43D: // CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+2D↑j
.text:0042D43D mov ecx, [ebp+var_JinSiQue] // 虚拟8086模式
.text:0042D440 call @xHalReferenceHandler@4 // xHalReferenceHandler(x)
.text:0042D445 call __SEH_epilog
.text:0042D44A retn 18h
.text:0042D44A // } // starts at 42D2C0
.text:0042D44A _KiInitializeUserApc@24 endp
.text:0042D44A
.text:0042D44A // ---------------------------------------------------------------------------

6 KeContextFromKframes

KeContextFromKframes函数:

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
VOID KeContextFromKframes (
IN PKTRAP_FRAME TrapFrame,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN OUT PCONTEXT ContextFrame
)
/*++
Routine Description:
This routine moves the selected contents of the specified trap and exception frames
frames into the specified context frame according to the specified context flags.
Arguments:
TrapFrame - Supplies a pointer to a trap frame from which volatile context
should be copied into the context record.
ExceptionFrame - Supplies a pointer to an exception frame from which context
should be copied into the context record. This argument is ignored since
there is no exception frame on NT386.
ContextFrame - Supplies a pointer to the context frame that receives the
context copied from the trap and exception frames.

Return Value: None.
--*/
{

PFX_SAVE_AREA NpxFrame;
BOOLEAN StateSaved;
ULONG i;
struct _FPSaveBuffer {
UCHAR Buffer[15];
FLOATING_SAVE_AREA SaveArea;
} FloatSaveBuffer;
PFLOATING_SAVE_AREA PSaveArea;

UNREFERENCED_PARAMETER( ExceptionFrame );

// Set control information if specified.
if ((ContextFrame->ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL) {

// Set registers ebp, eip, cs, eflag, esp and ss.
ContextFrame->Ebp = TrapFrame->Ebp;
ContextFrame->Eip = TrapFrame->Eip;

if (((TrapFrame->SegCs & FRAME_EDITED) == 0) &&
((TrapFrame->EFlags & EFLAGS_V86_MASK) == 0)) {
ContextFrame->SegCs = TrapFrame->TempSegCs & SEGMENT_MASK;
} else {
ContextFrame->SegCs = TrapFrame->SegCs & SEGMENT_MASK;
}
ContextFrame->EFlags = TrapFrame->EFlags;
ContextFrame->SegSs = KiSegSsFromTrapFrame(TrapFrame);
ContextFrame->Esp = KiEspFromTrapFrame(TrapFrame);
}

// Set segment register contents if specified.
if ((ContextFrame->ContextFlags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS) {

// Set segment registers gs, fs, es, ds.
//
// These values are junk most of the time, but useful
// for debugging under certain conditions. Therefore,
// we report whatever was in the frame.
if (TrapFrame->EFlags & EFLAGS_V86_MASK) {
ContextFrame->SegGs = TrapFrame->V86Gs & SEGMENT_MASK;
ContextFrame->SegFs = TrapFrame->V86Fs & SEGMENT_MASK;
ContextFrame->SegEs = TrapFrame->V86Es & SEGMENT_MASK;
ContextFrame->SegDs = TrapFrame->V86Ds & SEGMENT_MASK;
}
else {
if (TrapFrame->SegCs == KGDT_R0_CODE) {

// Trap frames created from R0_CODE traps do not save
// the following selectors. Set them in the frame now.
TrapFrame->SegGs = 0;
TrapFrame->SegFs = KGDT_R0_PCR;
TrapFrame->SegEs = KGDT_R3_DATA | RPL_MASK;
TrapFrame->SegDs = KGDT_R3_DATA | RPL_MASK;
}

ContextFrame->SegGs = TrapFrame->SegGs & SEGMENT_MASK;
ContextFrame->SegFs = TrapFrame->SegFs & SEGMENT_MASK;
ContextFrame->SegEs = TrapFrame->SegEs & SEGMENT_MASK;
ContextFrame->SegDs = TrapFrame->SegDs & SEGMENT_MASK;
}

}

// Set integer register contents if specified.
if ((ContextFrame->ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER) {

// Set integer registers edi, esi, ebx, edx, ecx, eax
ContextFrame->Edi = TrapFrame->Edi;
ContextFrame->Esi = TrapFrame->Esi;
ContextFrame->Ebx = TrapFrame->Ebx;
ContextFrame->Ecx = TrapFrame->Ecx;
ContextFrame->Edx = TrapFrame->Edx;
ContextFrame->Eax = TrapFrame->Eax;
}

if (((ContextFrame->ContextFlags & CONTEXT_EXTENDED_REGISTERS) ==
CONTEXT_EXTENDED_REGISTERS) &&
((TrapFrame->SegCs & MODE_MASK) == UserMode)) {

// This is the base TrapFrame, and the NpxFrame is on the base
// of the kernel stack, just above it in memory.
NpxFrame = (PFX_SAVE_AREA)(TrapFrame + 1);

if (KeI386NpxPresent) {
KiFlushNPXState (NULL);
RtlCopyMemory( (PVOID)&(ContextFrame->ExtendedRegisters[0]),
(PVOID)&(NpxFrame->U.FxArea),
MAXIMUM_SUPPORTED_EXTENSION
);
}
}

// Fetch floating register contents if requested, and type of target
// is user. (system frames have no fp state, so ignore request)

if ( ((ContextFrame->ContextFlags & CONTEXT_FLOATING_POINT) ==
CONTEXT_FLOATING_POINT) &&
((TrapFrame->SegCs & MODE_MASK) == UserMode)) {

// This is the base TrapFrame, and the NpxFrame is on the base
// of the kernel stack, just above it in memory.
NpxFrame = (PFX_SAVE_AREA)(TrapFrame + 1);

if (KeI386NpxPresent) {

// Force the coprocessors state to the save area and copy it
// to the context frame.

if (KeI386FxsrPresent == TRUE) {

// FP state save was done using fxsave. Get the save
// area in fnsave format
//
// Save area must be 16 byte aligned so we cushion it with
// 15 bytes (in the locals declaration above) and round
// down to align.

ULONG_PTR Temp;
Temp = (ULONG_PTR)&FloatSaveBuffer.SaveArea;
Temp &= ~0xf;
PSaveArea = (PFLOATING_SAVE_AREA)Temp;
KiFlushNPXState (PSaveArea);
} else {

PSaveArea = (PFLOATING_SAVE_AREA)&(NpxFrame->U.FnArea);
KiFlushNPXState (NULL);

}

ContextFrame->FloatSave.ControlWord = PSaveArea->ControlWord;
ContextFrame->FloatSave.StatusWord = PSaveArea->StatusWord;
ContextFrame->FloatSave.TagWord = PSaveArea->TagWord;
ContextFrame->FloatSave.ErrorOffset = PSaveArea->ErrorOffset;
ContextFrame->FloatSave.ErrorSelector = PSaveArea->ErrorSelector;
ContextFrame->FloatSave.DataOffset = PSaveArea->DataOffset;
ContextFrame->FloatSave.DataSelector = PSaveArea->DataSelector;
ContextFrame->FloatSave.Cr0NpxState = NpxFrame->Cr0NpxState;

for (i = 0; i < SIZE_OF_80387_REGISTERS; i++) {
ContextFrame->FloatSave.RegisterArea[i] = PSaveArea->RegisterArea[i];
}

} else {

// The 80387 is being emulated by the R3 emulator.
// ** The only time the Npx state is ever obtained or set is
// ** for userlevel handling. Current Irql must be 0 or 1.
// Go slurp the emulator's R3 data and generate the floating point context
StateSaved = KiEm87StateToNpxFrame(&ContextFrame->FloatSave);
if (StateSaved) {
ContextFrame->FloatSave.Cr0NpxState = NpxFrame->Cr0NpxState;
} else {

// The floatingpoint state can not be determined.
// Remove the floatingpoint flag from the context frame flags.
ContextFrame->ContextFlags &= (~CONTEXT_FLOATING_POINT) | CONTEXT_i386;
}
}
}

// Fetch Dr register contents if requested. Values may be trash.
if ((ContextFrame->ContextFlags & CONTEXT_DEBUG_REGISTERS) ==
CONTEXT_DEBUG_REGISTERS) {

ContextFrame->Dr0 = TrapFrame->Dr0;
ContextFrame->Dr1 = TrapFrame->Dr1;
ContextFrame->Dr2 = TrapFrame->Dr2;
ContextFrame->Dr3 = TrapFrame->Dr3;
ContextFrame->Dr6 = TrapFrame->Dr6;

// If it's a user mode frame, and the thread doesn't have DRs set,
// and we just return the trash in the frame, we risk accidentally
// making the thread active with trash values on a set. Therefore,
// Dr7 must be set to 0 if we get a non-active user mode frame.
if ((((TrapFrame->SegCs & MODE_MASK) != KernelMode) ||
((TrapFrame->EFlags & EFLAGS_V86_MASK) != 0)) &&
(KeGetCurrentThread()->DebugActive == TRUE)) {

ContextFrame->Dr7 = TrapFrame->Dr7;

} else {

ContextFrame->Dr7 = 0L;

}
}

}

KiUserApcDispatcher

ntdll.ll中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:7C92E430 // =============== S U B R O U T I N E =======================================
.text:7C92E430
.text:7C92E430
.text:7C92E430 // __stdcall KiUserApcDispatcher(x, x, x, x, x)
.text:7C92E430 public _KiUserApcDispatcher@20
.text:7C92E430 _KiUserApcDispatcher@20 proc near // DATA XREF: .text:off_7C923428↑o
.text:7C92E430
.text:7C92E430 arg_C = byte ptr 10h
.text:7C92E430
.text:7C92E430 lea edi, [esp+arg_C]
.text:7C92E434 pop eax
.text:7C92E435 call eax
.text:7C92E437 push 1
.text:7C92E439 push edi // 在3环函数中保存的 CONTEXT 的首地址
.text:7C92E43A call _ZwContinue@8 // ZwContinue(x,x)
.text:7C92E43F nop
.text:7C92E43F _KiUserApcDispatcher@20 endp // sp-analysis failed
.text:7C92E43F

R3 ZwContinue

ntdll.ll中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:7C92D040 // =============== S U B R O U T I N E =======================================
.text:7C92D040
.text:7C92D040
.text:7C92D040 // __stdcall ZwContinue(x, x)
.text:7C92D040 public _ZwContinue@8
.text:7C92D040 _ZwContinue@8 proc near // CODE XREF: KiUserApcDispatcher(x,x,x,x,x)+A↓p
.text:7C92D040 // KiUserExceptionDispatcher(x,x)+17↓p ...
.text:7C92D040 mov eax, 20h // NtContinue
.text:7C92D045 mov edx, 7FFE0300h
.text:7C92D04A call dword ptr [edx]
.text:7C92D04C retn 8
.text:7C92D04C _ZwContinue@8 endp
.text:7C92D04C
.text:7C92D04C // ---------------------------------------------------------------------------

R0 NtContinue

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
.text:0046DE6C // =============== S U B R O U T I N E =======================================
.text:0046DE6C
.text:0046DE6C // ==========
.text:0046DE6C // 在KiFastCallEntry进入当前函数之前:
.text:0046DE6C // 1、esp 指向拷贝过来的3环API的第一个参数(此时在本函数中的esp指向返回地址)
.text:0046DE6C // 2、ebp = _KTRAP_FRAME首地址
.text:0046DE6C // 3、ebx = 系统服务函数地址
.text:0046DE6C // 4、edx:指向3环 API 的第一个参数
.text:0046DE6C // 5、eax = 系统服务号的低 12 位
.text:0046DE6C // 6、esi == edi = 指向拷贝过来的3环API的最后一个参数
.text:0046DE6C // ==========
.text:0046DE6C // 参数Context为上一个用户APC函数的Context起始地址
.text:0046DE6C // TestAlert:3环传来的是 1
.text:0046DE6C // Attributes: bp-based frame
.text:0046DE6C
.text:0046DE6C // NTSTATUS __stdcall NtContinue(PCONTEXT Context, BOOLEAN TestAlert)
.text:0046DE6C _NtContinue@8 proc near // DATA XREF: .text:0042D4D0↑o
.text:0046DE6C
.text:0046DE6C var_s0 = dword ptr 0
.text:0046DE6C Context = dword ptr 8
.text:0046DE6C TestAlert = byte ptr 0Ch
.text:0046DE6C arg_34 = dword ptr 3Ch
.text:0046DE6C
.text:0046DE6C push ebp
.text:0046DE6D mov ebx, large fs:124h // ----
.text:0046DE6D // 从3环进来是走 KiFastEntry
.text:0046DE6D // 此时的 ebp 指向3环的 trapflame 首地址
.text:0046DE6D // ----
.text:0046DE74 mov edx, [ebp+_KTRAP_FRAME._Edx]
.text:0046DE77 mov [ebx+_KTHREAD.TrapFrame], edx
.text:0046DE7D mov ebp, esp // 指向 Old_TrapFrame
.text:0046DE7F mov eax, [ebp+var_s0]
.text:0046DE82 mov ecx, [ebp+Context] // 这里是没有毛病的
.text:0046DE85 push eax // eax 为Old_Trapframe地址
.text:0046DE86 push 0
.text:0046DE88 push ecx
.text:0046DE89 call _KiContinue@12 // ---
.text:0046DE89 // NTSTATUS KiContinue (
.text:0046DE89 // IN PCONTEXT ContextRecord,
.text:0046DE89 // IN PKEXCEPTION_FRAME ExceptionFrame,
.text:0046DE89 // IN PKTRAP_FRAME TrapFrame
.text:0046DE89 // )
.text:0046DE89 //
.text:0046DE89 // Move information from ContextFrame to TrapFrame according to the
.text:0046DE89 // specified context flags In APC_LEVEL.
.text:0046DE89 // 实际是恢复到正常调用时的TrapFrame状态
.text:0046DE89 // ---
.text:0046DE8E or eax, eax
.text:0046DE90 jnz short loc_46DEAC // KiContinue函数调用失败,紧接着调用KiServiceExit
.text:0046DE92 cmp [ebp+TestAlert], 0
.text:0046DE96 jz short loc_46DEA4 // TestAlert == 0
.text:0046DE98 mov al, [ebx+_KTHREAD.PreviousMode]
.text:0046DE9E push eax // eax == 1
.text:0046DE9F call _KeTestAlertThread@4 // ---
.text:0046DE9F // BOOLEAN __stdcall KeTestAlertThread (
.text:0046DE9F // IN KPROCESSOR_MODE AlertMode
.text:0046DE9F // )
.text:0046DE9F //
.text:0046DE9F // 如果吵醒模式AlertMode == 1,并且用户APC队列不为空,
.text:0046DE9F // 那就设置 Thread->ApcState.UserApcPending = TRUE 然后交付下一个用户APC
.text:0046DE9F // 【检查该线程是否可以交付另一个用户模式的APC】
.text:0046DE9F // ---
.text:0046DEA4
.text:0046DEA4 loc_46DEA4: // CODE XREF: NtContinue(x,x)+2A↑j
.text:0046DEA4 pop ebp // TestAlert == 0
.text:0046DEA5 mov esp, ebp
.text:0046DEA7 jmp _KiServiceExit2
.text:0046DEAC // ---------------------------------------------------------------------------
.text:0046DEAC
.text:0046DEAC loc_46DEAC: // CODE XREF: NtContinue(x,x)+24↑j
.text:0046DEAC pop ebp // KiContinue函数调用失败,紧接着调用KiServiceExit
.text:0046DEAD mov esp, ebp
.text:0046DEAF jmp _KiServiceExit // 可以看到这个函数是 KiFastCallEntry 函数的一部分,紧接着这个函数的
.text:0046DEAF _NtContinue@8 endp

KiSwapThread

A线程调用SwapContext函数切换到线程B后,B原路返回执行么?分析一下KiSwapThread,直接将当前线程的context复制给下一个线程?

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
LONG_PTR FASTCALL KiSwapThread(VOID);

Kprcb = fs:[20]; //局部变量

if(Kprcb->NextThread != 0)
{
Kprcb->NextThread = 0;
KiSwapContext(Kprcb->NextThread);
//0042C831
//....
}

KiSwapContext(Kprcb->NextThread)
{
ebx = Kpcr = fs:[0x1c]; //KPCR
esi = Kprcb->NextThread;
edi = Kprcb.PrcbData.CurrentThread;

Old_CurrentThread = Kpcr.PrcbData.CurrentThread;
Kprcb.PrcbData.CurrentThread = Kprcb->NextThread; //并不是进行线程切换,此处更新KPRCB的当前线程

ecx = KTHREAD.WaitIrql;
SwapContext();
//0046E996
//...
}

SwapContext()
{
NextThread->State = Running; //2
pushf; //在线程切换时,会有很多判断操作,势必会影响到标志寄存器的值,这里需要保存一下
push Kpcr.NtTib.ExceptionList;//仅仅在Windows Server2003中,5.2版本出现的一个字段,位于NtTib+0x08的位置,主要与日志,调式相关的。

if(Kpcr.PrcbData.DpcRoutineActive ! =0)
{
KeBugCheck(0xB8);
}

Kpcr.DebugActive = NextThread->DebugActive;
cli; //屏蔽中断

//开始线程切换的第一步。
//InitialStack:栈底 KernelStack:栈顶 StackLimit:栈大小
CurrentThread->KernelStack = esp; //保存旧esp
NextThread->InitialStack -= 0x210; //在线程Trap_Frame之前有0x210个字节用于存储浮点寄存器相关的内容
Kpcr.NtTib.StackLimit = NextThread->StackLimit;
Kpcr.NtTib.StackBase = NextThread->InitialStack;

//NpxState(浮点寄存器的状态)
TempNum = NextThread->InitialStack + 0x20C; //是某个浮点寄存器
NpxState = NextThread->NpxState;
NpxState |= (CR0 & 0xFFFFFFF1); //将Cr0寄存器的TS、EM、MP位清零,判断NpxState有没有浮点支持
NpxState |= TempNum;

if(CR0 != NpxState) CR0 = NpxState;

//此处用来判断是否是虚拟8086模式
//如果有V86寄存器的话,eax-1Ch刚好指向Eflag寄存器
if(*(PULONG)(NextThread->InitialStack - 0x1c != 0x20000) //不是虚拟8086模式
{
//快速调用从3环进入0环后esp0就是指向这里
//现在栈底指向 TrapFrame.HardwareSegSs
NextThread->InitialStack -= 0x10;
}

Tss = Kpcr->TSS;
Tss->esp0 = NextThread->InitialStack;
esp = NextThread->KernelStack; //线程切换

Kpcr->NtTib->Self = NextThread->Teb;
sti;

CurrentThread->IdleSwapBlock = 0;

//判断两个线程是否属于同一个进程
if(NextThread->ApcState->Process != NextThread->ApcState->Process)
{}
}

在整个线程切换的过程中,两个线程的堆栈是怎么变化的?trapframe、context又是怎样变化的?

线程切换后,EIP没有切换。就只是切换了esp?是的,将当前的esp修改为新线程的InitStack后,就已经切换到新线程了。然后按照新线程堆栈压入的返回地址进行返回,注意这里的堆栈里的返回地址不是trapFrame里面的esp,不要搞混了。

分析SwapContext

学完APC就去学异常,应该是分发那里不太一样。调试就是异常处理,异常处理只不过是我们提供了一个函数来处理这个异常,调试器不过就是把这个异常传给另外一个进程,让调试器去处理。

调试器就是想方设法让对方程序抛异常,程序一抛异常了就会把异常传回来,如果此时有调试器,先传给调试器,如果没有调试器就把异常传给异常处理程序。

异常和APC几乎一摸一样,异常也分两类,即0环和3环的异常。

两种被动切换:

正在执行的线程如何知道时间片已经用完?

正在执行的线程如何被其他线程抢占执行的?

难道被动切换是:一个正在执行的线程被下了一个BreakPoint?然后当前线程去处理这个BreakPoint?

可以看一下《Windows内核原理与实现》P166

《Windows内核情景分析》P382

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
//精况一:线程等待状态 alertble=0 APC不执行
#include "stdafx.h"
#define _WIN32_WINNT 0x0400
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID pM)
{
//SleepEx(3000, TRUE);
Sleep (10000);
for(int i = 0; i < 10; i++)
{
printf("ThreadProc执行了 %d\n",i);
}


__asm
{
push eax;
push ecx;
mov ecx,dword ptr ds:[0x7FFE0300];
mov eax,0x103;
call ecx;


pop ecx;
pop eax;
}
//NtTestAlert();
//SleepEx(3000, TRUE);
for (int k = 0; k < 10; k++)
{
printf("ThreadProc执行了 %d\n",k);
}

return 0;
}

void WINAPI ApcProc(unsigned long para)
{
printf("ApcProc执行了---------------! \n");
}

void WINAPI ApcProc2(unsigned long para)
{
printf("ApcProc2执行了---------------! \n");
}

int main(int argc,char* arg[])
{
DWORD dwRet;
HANDLE handle=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
//注入APC
Sleep(3000);
dwRet = QueueUserAPC(ApcProc,handle,NULL);
if(dwRet > 0)
{
printf("插入OK_1 \n");
}
//Sleep(3000);
dwRet = QueueUserAPC(ApcProc2, handle, NULL);
if(dwRet > 0)
{
printf("插入OK_2 \n");
}
CloseHandle (handle);
Sleep (20000);
getchar();
return 0;
}

555.png

KiFastCallEntry

系统调用:

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
.text:0046A520 // =============== S U B R O U T I N E =======================================
.text:0046A520
.text:0046A520 // ----
.text:0046A520 // 从3环函数 KiFastSystemCall 的 sysenter 指令调用
.text:0046A520 //
.text:0046A520 // edx = esp3, eax = 0xIndex
.text:0046A520 //
.text:0046A520 // ----
.text:0046A520
.text:0046A520 _KiFastCallEntry proc near // DATA XREF: KiLoadFastSyscallMachineSpecificRegisters(x)+24↑o
.text:0046A520 // _KiTrap01+74↓o
.text:0046A520
.text:0046A520 var_B = byte ptr -0Bh
.text:0046A520
.text:0046A520 // FUNCTION CHUNK AT .text:0046A4ED SIZE 00000026 BYTES
.text:0046A520 // FUNCTION CHUNK AT .text:0046A7C0 SIZE 00000014 BYTES
.text:0046A520
.text:0046A520 mov ecx, 23h
.text:0046A525 push 30h // 开始修改段寄存器为0环的值
.text:0046A527 pop fs // fs = 0x30
.text:0046A529 mov ds, ecx
.text:0046A52B mov es, ecx
.text:0046A52D mov ecx, large fs:_KPCR.TSS
.text:0046A534 mov esp, [ecx+4] // ----
.text:0046A534 // esp = TSS.esp0,切换到0环堆栈
.text:0046A534 //
.text:0046A534 // TSS.esp0 指向当前线程 TrapFrame 0x7C V86ES
.text:0046A534 //
.text:0046A534 // 从这里可以知道 KiFastEntry 的函数堆栈即为当前线程的 TrapFrame
.text:0046A534 // 下面开始从 HardwareSegss 开始填充 TrapFrame (当前函数堆栈)
.text:0046A534 // ----
.text:0046A537 push 23h // HardwareSegSs = 0x23
.text:0046A539 push edx // HardwareSegEsp = esp3 == 3环的第二个返回地址
.text:0046A53A pushf
.text:0046A53B
.text:0046A53B loc_46A53B: // CODE XREF: _KiFastCallEntry2+23↑j
.text:0046A53B push 2
.text:0046A53D add edx, 8 // 此时 edx 指向 3 环的第一个参数
.text:0046A540 popf // EFlags = 0x02,即清空0环所有标志位
.text:0046A541 or [esp+0Ch+var_B], 2 // ---
.text:0046A541 // 此时esp 指向 KTRAP_FRAME.EFlags
.text:0046A541 // eflag(8~15bit)
.text:0046A541 // 将3环 EFlags 的 IF = 1,以响应可屏蔽中断
.text:0046A541 // ---
.text:0046A546 push 1Bh // KTRAP_FRAME.SegCs = 0x1B = CS3
.text:0046A548 push dword ptr ds:0FFDF0304h // --
.text:0046A548 // KTRAP_FRAME.Eip = _KUSER_SHARED_DATA.SystemCallReturn返回地址
.text:0046A548 //
.text:0046A548 // 也就是说从 0 环返回 3 环时将会返回到这个函数
.text:0046A548 // --
.text:0046A54E push 0
.text:0046A550 push ebp
.text:0046A551 push ebx
.text:0046A552 push esi
.text:0046A553 push edi
.text:0046A554 mov ebx, large fs:_KPCR.SelfPcr
.text:0046A55B push 3Bh // _KTRAP_FRAME.SegFs = 0x3B
.text:0046A55D mov esi, [ebx+_KPCR.PrcbData.CurrentThread]
.text:0046A563 push dword ptr [ebx] // 保存旧的 ExceptionList
.text:0046A565 mov dword ptr [ebx], 0FFFFFFFFh // 当前线程ExceptionList = -1
.text:0046A56B mov ebp, [esi+_KTHREAD.InitialStack] // ----
.text:0046A56B // ebp = _KPCR._KPRCB._KTHREAD.InitialStack(栈底),KernelStack(栈顶)
.text:0046A56B // 从线程切换分析可以知道:KTHREAD.InitialStack(栈底)指向当前线程的浮点寄存器 共0x210字节
.text:0046A56B //
.text:0046A56B // 也就是 KTHREAD 堆栈的是这样的:
.text:0046A56B // - TrapFrame(共0x8c字节) + 浮点寄存器(共0x210字节) == 0x29C
.text:0046A56B // - KTHREAD.InitialStack = 指向 TrapFrame + 浮点寄存器,【栈底】
.text:0046A56B //
.text:0046A56B // ----
.text:0046A56E push 1 // _KTRAP_FRAME.PreviousPreviousMode = 1,表示从3环来
.text:0046A570 sub esp, 48h // esp0 指向_TrapFrame首地址
.text:0046A573 sub ebp, 29Ch // esp0 = ebp0 = _KTRAP_FRAME首地址
.text:0046A579 mov [esi+_KTHREAD.PreviousMode], 1
.text:0046A580 cmp ebp, esp
.text:0046A582 jnz short loc_46A511 // esp0 != ebp0,跳到异常处理即 INT 6,6号异常中断--无效操作码
.text:0046A584 and [ebp+_KTRAP_FRAME.Dr7], 0
.text:0046A588 test [esi+_KTHREAD.DebugActive], 0FFh
.text:0046A58C mov [esi+_KTHREAD.TrapFrame], ebp // _KTHREAD.TrapFrame = ebp 存起来
.text:0046A592 jnz Dr_FastCallDrSave // DebugActive != -1,就表示被调试,会把cr0-cr7存到TrapFrame结构体中
.text:0046A592 // 如果当前的线程处于调试状态,那么这里面的值不为零
.text:0046A598
.text:0046A598 loc_46A598: // CODE XREF: Dr_FastCallDrSave+10↑j
.text:0046A598 // Dr_FastCallDrSave+7C↑j
.text:0046A598 mov ebx, [ebp+_KTRAP_FRAME._Ebp] // ebx == ebp3,但是在本函数内,并没有对ebp3进行赋值处理
.text:0046A59B mov edi, [ebp+_KTRAP_FRAME._Eip] // edi = eip3 == _KUSER_SHARED_DATA.SystemCallReturn
.text:0046A59E mov [ebp+_KTRAP_FRAME.DbgArgPointer], edx // edx 指向 3 环的第一个参数
.text:0046A5A1 mov [ebp+_KTRAP_FRAME.DbgArgMark], 0BADB0D00h
.text:0046A5A8 mov [ebp+_KTRAP_FRAME.DbgEbp], ebx
.text:0046A5AB mov [ebp+_KTRAP_FRAME.DbgEip], edi
.text:0046A5AE sti
.text:0046A5AF
.text:0046A5AF loc_46A5AF: // CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:0046A5AF // _KiSystemService+72↑j
.text:0046A5AF mov edi, eax // ---
.text:0046A5AF // 下面开始是 KiSystemService 和 KiFastCallEntry 的汇聚点
.text:0046A5AF // - esp = ebp = _KTRAP_FRAME首地址
.text:0046A5AF // - edx == 指向3环参数
.text:0046A5AF // - eax == 系统服务号
.text:0046A5AF // ---
.text:0046A5B1 shr edi, 8
.text:0046A5B4 and edi, 30h // --
.text:0046A5B4 // 取系统服务号的 bit12,bit13== 0。
.text:0046A5B4 // 即00bit13bit12 xxxx & 0011 0000 == 0001 0000/0000 0000
.text:0046A5B4 // 结果为:0x10/0x00
.text:0046A5B4 // --
.text:0046A5B7 mov ecx, edi
.text:0046A5B9 add edi, [esi+_KTHREAD.ServiceTable] // 取 SSDT/SSDT Shadow 表
.text:0046A5BF mov ebx, eax
.text:0046A5C1 and eax, 0FFFh // 取系统服务号的低 12 位,即在SST函数表的索引号
.text:0046A5C6 cmp eax, [edi+8] // 系统调用号 - SST.ServiceLimit,判断函数是否在表的范围内
.text:0046A5C9 jnb _KiBBTUnexpectedRange // 系统调用号 >= SST.ServiceLimit,不在表里,跳转异常处理
.text:0046A5CF cmp ecx, 10h
.text:0046A5D2 jnz short loc_46A5EF // --
.text:0046A5D2 // 判断系统服务号的函数是ntoskrnl.exe
.text:0046A5D2 // 系统调用计数加1,即函数KiFastCallEntry被调用次数++
.text:0046A5D2 // --
.text:0046A5D4 mov ecx, large fs:18h
.text:0046A5DB xor ebx, ebx
.text:0046A5DD
.text:0046A5DD loc_46A5DD: // DATA XREF: _KiTrap0E+117↓o
.text:0046A5DD or ebx, [ecx+0F70h]
.text:0046A5E3 jz short loc_46A5EF // --
.text:0046A5E3 // 判断系统服务号的函数是ntoskrnl.exe
.text:0046A5E3 // 系统调用计数加1,即函数KiFastCallEntry被调用次数++
.text:0046A5E3 // --
.text:0046A5E5 push edx
.text:0046A5E6 push eax
.text:0046A5E7 call ds:_KeGdiFlushUserBatch
.text:0046A5ED pop eax
.text:0046A5EE pop edx
.text:0046A5EF
.text:0046A5EF loc_46A5EF: // CODE XREF: _KiFastCallEntry+B2↑j
.text:0046A5EF // _KiFastCallEntry+C3↑j
.text:0046A5EF inc large dword ptr fs:_KPCR.PrcbData.KeSystemCalls // --
.text:0046A5EF // 判断系统服务号的函数是ntoskrnl.exe
.text:0046A5EF // 系统调用计数加1,即函数KiFastCallEntry被调用次数++
.text:0046A5EF // --
.text:0046A5F6 mov esi, edx // edx 指向3环 API 的第一个参数
.text:0046A5F8 mov ebx, [edi+0Ch] // ebx 指向 SST 的参数表,ebx = SST.ArgmentTable
.text:0046A5FB xor ecx, ecx
.text:0046A5FD mov cl, [eax+ebx] // ecx = SST.ArgmentTable + 系统调用号低12位*1,即ecx = 函数参数大小
.text:0046A600 mov edi, [edi] // edi = SST.ServiceTable = KeServiceDescriptorTable
.text:0046A602 mov ebx, [edi+eax*4] // ebx = SST.ServiceTable + 系统调用号低12位*4 = 函数地址
.text:0046A605 sub esp, ecx // 0环抬高栈顶,马上将3环参数复制到0环
.text:0046A607 shr ecx, 2 // ecx = 参数个数 = 参数大小/4,作为拷贝的次数(一次拷贝4字节)
.text:0046A60A mov edi, esp
.text:0046A60C cmp esi, ds:_MmUserProbeAddress
.text:0046A612 jnb loc_46A7C0
.text:0046A618
.text:0046A618 loc_46A618: // CODE XREF: _KiFastCallEntry+2A4↓j
.text:0046A618 // DATA XREF: _KiTrap0E+10D↓o
.text:0046A618 rep movsd // 可以得知:当前函数的堆栈(也是当前线程的堆栈)从栈顶到栈底的分布情况:
.text:0046A618 // 1. 从3环拷贝过来的3环API参数
.text:0046A618 // 2. Trapframe(共0x8C字节)
.text:0046A618 // 3. 浮点寄存器(共0x210字节)
.text:0046A61A call ebx // ==========
.text:0046A61A // 此时:
.text:0046A61A // 1、esp 指向拷贝过来的3环API的第一个参数
.text:0046A61A // 2、ebp = _KTRAP_FRAME首地址
.text:0046A61A // 3、ebx = 系统服务函数地址
.text:0046A61A // 4、edx:指向3环 API 的第一个参数
.text:0046A61A // 5、eax = 系统服务号的低 12 位
.text:0046A61A // 6、esi == edi = 指向拷贝过来的3环API的最后一个参数
.text:0046A61A // ==========
.text:0046A61C
.text:0046A61C loc_46A61C: // CODE XREF: _KiFastCallEntry+2AF↓j
.text:0046A61C // DATA XREF: _KiTrap0E+12D↓o ...
.text:0046A61C mov esp, ebp
.text:0046A61E
.text:0046A61E loc_46A61E: // CODE XREF: _KiBBTUnexpectedRange+38↑j
.text:0046A61E // _KiBBTUnexpectedRange+43↑j
.text:0046A61E mov ecx, large fs:124h
.text:0046A625 mov edx, [ebp+_KTRAP_FRAME._Edx] // ---
.text:0046A625 // 在ROS P24 有这样的代码:
.text:0046A625 //
.text:0046A625 // Save the old trap frame pointer where EDX would be saved
.text:0046A625 //
.text:0046A625 // mov ebx, [esi+KTHREAD_TRAP_FRAME] //KTHREAD 结构中的指针 TrapFrame
.text:0046A625 // mov [ebp+KTRAP_FRAME_EDX], ebx //暂时保存在这里
.text:0046A625 //
.text:0046A625 // 这是因为:加入一个线程多次从R3-->R0-->R3-->R0,每一次进来R0都会构建一个新
.text:0046A625 // 的TrapFrame结构在当前线程堆栈里面,那么可以使用当前KTRAP_FRAME_EDX来保存上一个
.text:0046A625 // TrapFrame,因为这一操作是在下面的指令前进行的操作:
.text:0046A625 // mov [esi+_KTHREAD.TrapFrame], ebp
.text:0046A625 // ---
.text:0046A628 mov [ecx+_KTHREAD.TrapFrame], edx
.text:0046A628 _KiFastCallEntry endp
.text:0046A628
.text:0046A62E
.text:0046A62E // =============== S U B R O U T I N E =======================================
.text:0046A62E
.text:0046A62E // 可以看到这个函数是 KiFastCallEntry 函数的一部分,紧接着这个函数的
.text:0046A62E
.text:0046A62E _KiServiceExit proc near // CODE XREF: KiCallUserMode(x,x)+EC↑j
.text:0046A62E // _KiSetLowWaitHighThread+80↓j ...
.text:0046A62E
.text:0046A62E arg_C = dword ptr 10h
.text:0046A62E arg_10 = dword ptr 14h
.text:0046A62E arg_40 = dword ptr 44h
.text:0046A62E arg_44 = dword ptr 48h
.text:0046A62E arg_48 = dword ptr 4Ch
.text:0046A62E arg_60 = dword ptr 64h
.text:0046A62E arg_64 = dword ptr 68h
.text:0046A62E arg_68 = dword ptr 6Ch
.text:0046A62E arg_6C = dword ptr 70h
.text:0046A62E
.text:0046A62E // FUNCTION CHUNK AT .text:0046A738 SIZE 00000088 BYTES
.text:0046A62E
.text:0046A62E cli
.text:0046A62F test [ebp+_KTRAP_FRAME.EFlags], 20000h // 判断是否是虚拟8086模式
.text:0046A636 jnz short loc_46A63E // 是虚拟8086模式
.text:0046A638 test byte ptr [ebp+_KTRAP_FRAME.SegCs], 1
.text:0046A63C jz short loc_46A694 // ---
.text:0046A63C // 来自内核,此时esp指向的trapframe是线程堆栈最栈顶上面的那个,
.text:0046A63C // 就是最近一次从3环进入到0环时使用的
.text:0046A63C // ---
.text:0046A63E
.text:0046A63E loc_46A63E: // CODE XREF: _KiServiceExit+8↑j
.text:0046A63E // _KiServiceExit+64↓j
.text:0046A63E mov ebx, large fs:124h // ---
.text:0046A63E // 从3环退出前,只要还有3环APC还没执行,就会不断的循环去执行
.text:0046A63E // 虽然在 KiDeliverApc 中一次只会执行一个用户APC,但是
.text:0046A63E // 本函数这里是一个循环
.text:0046A63E // ---
.text:0046A645 mov [ebx+_KTHREAD.Alerted], 0
.text:0046A649 cmp [ebx+_KTHREAD.ApcState.UserApcPending], 0
.text:0046A64D jz short loc_46A694 // ---
.text:0046A64D // 来自内核,此时esp指向的trapframe是线程堆栈最栈顶上面的那个,
.text:0046A64D // 就是最近一次从3环进入到0环时使用的
.text:0046A64D // ---
.text:0046A64F mov ebx, ebp
.text:0046A651 mov [ebx+_KTRAP_FRAME._Eax], eax
.text:0046A654 mov [ebx+_KTRAP_FRAME.SegFs], 3Bh
.text:0046A65B mov [ebx+_KTRAP_FRAME.SegDs], 23h
.text:0046A662 mov [ebx+_KTRAP_FRAME.SegEs], 23h
.text:0046A669 mov [ebx+_KTRAP_FRAME.SegGs], 0
.text:0046A670 mov ecx, 1 // NewIrql
.text:0046A675 call ds:__imp_@KfRaiseIrql@4 // KfRaiseIrql(x)
.text:0046A67B push eax
.text:0046A67C sti
.text:0046A67D push ebx
.text:0046A67E push 0
.text:0046A680 push 1
.text:0046A682 call _KiDeliverApc@12 // VOID KiDeliverApc (
.text:0046A682 // IN KPROCESSOR_MODE PreviousMode,
.text:0046A682 // IN PKEXCEPTION_FRAME ExceptionFrame,
.text:0046A682 // IN PKTRAP_FRAME TrapFrame
.text:0046A682 // )
.text:0046A687 pop ecx // NewIrql
.text:0046A688 call ds:__imp_@KfLowerIrql@4 // KfLowerIrql(x)
.text:0046A68E mov eax, [ebx+_KTRAP_FRAME._Eax]
.text:0046A691 cli
.text:0046A692 jmp short loc_46A63E // ---
.text:0046A692 // 从3环退出前,只要还有3环APC还没执行,就会不断的循环去执行
.text:0046A692 // 虽然在 KiDeliverApc 中一次只会执行一个用户APC,但是
.text:0046A692 // 本函数这里是一个循环
.text:0046A692 // ---
.text:0046A694 // ---------------------------------------------------------------------------
.text:0046A694
.text:0046A694 loc_46A694: // CODE XREF: _KiServiceExit+E↑j
.text:0046A694 // _KiServiceExit+1F↑j
.text:0046A694 mov edx, [esp+_KTRAP_FRAME.ExceptionList] // ---
.text:0046A694 // 来自内核,此时esp指向的trapframe是线程堆栈最栈顶上面的那个,
.text:0046A694 // 就是最近一次从3环进入到0环时使用的
.text:0046A694 // ---
.text:0046A698 mov ebx, large fs:_KPCR.DebugActive // 如果当前的线程处于调试状态,那么这里面的值不为零
.text:0046A69F mov large fs:_KPCR, edx
.text:0046A6A6 mov ecx, [esp+_KTRAP_FRAME.PreviousPreviousMode]
.text:0046A6AA mov esi, large fs:_KPCR.PrcbData.CurrentThread
.text:0046A6B1 mov [esi+_KTHREAD.PreviousMode], cl
.text:0046A6B7 test ebx, 0FFh
.text:0046A6BD jnz short loc_46A738 // 异常链表非空,有异常需要处理
.text:0046A6BF
.text:0046A6BF loc_46A6BF: // CODE XREF: _KiServiceExit+11A↓j
.text:0046A6BF // _KiServiceExit+149↓j
.text:0046A6BF test [esp+_KTRAP_FRAME.EFlags], 20000h // 判断是否是V86(虚拟8086即16位操作系统)
.text:0046A6C7 jnz loc_46AFD8 // 虚拟8086模式
.text:0046A6CD test word ptr [esp+_KTRAP_FRAME.SegCs], 0FFF8h
.text:0046A6D4 jz loc_46A78E // CS最低位为0,说明先前模式是0
.text:0046A6DA cmp word ptr [esp+_KTRAP_FRAME.SegCs], 1Bh
.text:0046A6E0 bt word ptr [esp+_KTRAP_FRAME.SegCs], 0 // CF位置1
.text:0046A6E7 cmc // ---
.text:0046A6E7 // CF取反,则 CF == 0
.text:0046A6E7 // https://blog.csdn.net/xiong_xin/article/details/103665440
.text:0046A6E7 // ---
.text:0046A6E8 ja loc_46A77C // CF ∨ ZF = 0 时跳转
.text:0046A6EE cmp word ptr [ebp+_KTRAP_FRAME.SegCs], 8
.text:0046A6F3 jz short loc_46A6FA
.text:0046A6F5
.text:0046A6F5 loc_46A6F5: // CODE XREF: _KiServiceExit+15B↓j
.text:0046A6F5 lea esp, [ebp+_KTRAP_FRAME.SegFs]
.text:0046A6F8 pop fs // 将3环的 FS 赋值给 FS 寄存器
.text:0046A6FA assume fs:nothing
.text:0046A6FA
.text:0046A6FA loc_46A6FA: // CODE XREF: _KiServiceExit+C5↑j
.text:0046A6FA lea esp, [ebp+_KTRAP_FRAME._Edi]
.text:0046A6FD pop edi
.text:0046A6FE pop esi
.text:0046A6FF pop ebx
.text:0046A700 pop ebp // 此时的esp指向ErrCode(+0x64)
.text:0046A701 cmp word ptr [esp-60h+arg_64], 80h // CS
.text:0046A708 ja loc_46AFF4
.text:0046A70E add esp, 4 // esp指向EIP3(+0x68)
.text:0046A711 test [esp-64h+arg_64], 1 // cs
.text:0046A711 _KiServiceExit endp // sp-analysis failed
.text:0046A711
.text:0046A719
.text:0046A719 // =============== S U B R O U T I N E =======================================
.text:0046A719
.text:0046A719 // ========
.text:0046A719 // 实际上,如果当前系统支持快速系统调用,那么,这条指令将
.text:0046A719 // 被修正为“jnz short _KiSystemCallExi2“。而KiSystemCallExit2 所指的代码使用 sysexit
.text:0046A719 // 指令返回用户模式。这条指令的修正工作是在 KiEnableFastSyscallReturn 函数中完成的,
.text:0046A719 // 该函数被 KiRestoreFastSyscallReturnState 调用
.text:0046A719 // 《Windows内核原理与实现》 P553
.text:0046A719 // ========
.text:0046A719 //
.text:0046A719
.text:0046A719 _KiSystemCallExitBranch proc near // DATA XREF: KiDisableFastSyscallReturn()+9↑w
.text:0046A719 // KiEnableFastSyscallReturn():loc_427854↑r ...
.text:0046A719 jnz short _KiSystemCallExit
.text:0046A71B pop edx // edx = eip3
.text:0046A71C pop ecx // 从栈中移除cs
.text:0046A71D popf
.text:0046A71E jmp edx
.text:0046A720 // ---------------------------------------------------------------------------
.text:0046A720
.text:0046A720 _KiSystemCallExit: // CODE XREF: _KiSystemCallExitBranch↑j
.text:0046A720 // _KiSystemCallExit2+5↓j
.text:0046A720 // DATA XREF: ...
.text:0046A720 iret
.text:0046A720 _KiSystemCallExitBranch endp // sp-analysis failed
.text:0046A720
.text:0046A721
.text:0046A721 // =============== S U B R O U T I N E =======================================
.text:0046A721
.text:0046A721
.text:0046A721 _KiSystemCallExit2 proc near // DATA XREF: KiRestoreFastSyscallReturnState()+16↑o
.text:0046A721
.text:0046A721 arg_5 = byte ptr 9
.text:0046A721
.text:0046A721 test [esp+arg_5], 1
.text:0046A726 jnz short _KiSystemCallExit // ===========================================================
.text:0046A726 // EFLAG3.TF(Trap flag)!= 0,也就是说是从陷井进来的0环,不是快速调用进来的。
.text:0046A726 //
.text:0046A726 // 注意,这里首先测试 eflags 标志寄存器中的TF(陷阱) 标志是必要的,因为
.text:0046A726 // 在 Pentium II 以后的 Windows 系统上,若应用程序绕过KiFastSystemCall,而直接
.text:0046A726 // 利用 “int 2e” 指令进入内核,那么,在此处将会被检测到,从而通过irerd 返回,
.text:0046A726 // 这样就不会发生进人内核模式和返回用户模式不对称的情形。
.text:0046A726 //
.text:0046A726 // 《Windows内核原理与实现》 P554
.text:0046A726 // ====================================================================
.text:0046A728 pop edx // esp指向EIP3(+0x68)
.text:0046A729 add esp, 4 // 从栈中移除cs,此时 esp 指向eflag3
.text:0046A72C and [esp-8+arg_5], 0FDh // 禁止eflags中的中断标志
.text:0046A731 popf
.text:0046A732 pop ecx // 弹出esp
.text:0046A733 sti // 恢复中断,因为sysexit指令不会恢复中断标志
.text:0046A734 sysexit // 返回用户模式
.text:0046A736 iret
.text:0046A736 _KiSystemCallExit2 endp // sp-analysis failed
.text:0046A736
.text:0046A736 // ---------------------------------------------------------------------------
.text:0046A737 align 4
.text:0046A738 // START OF FUNCTION CHUNK FOR _KiServiceExit
.text:0046A738
.text:0046A738 loc_46A738: // CODE XREF: _KiServiceExit+8F↑j
.text:0046A738 test dword ptr [ebp+70h], 20000h // 异常链表非空,有异常需要处理
.text:0046A73F jnz short loc_46A74E
.text:0046A741 test dword ptr [ebp+6Ch], 1
.text:0046A748 jz loc_46A6BF // 判断是否是V86(虚拟8086即16位操作系统)
.text:0046A74E
.text:0046A74E loc_46A74E: // CODE XREF: _KiServiceExit+111↑j
.text:0046A74E xor ebx, ebx
.text:0046A750 mov esi, [ebp+18h]
.text:0046A753 mov edi, [ebp+1Ch]
.text:0046A756 mov dr7, ebx
.text:0046A759 mov dr0, esi
.text:0046A75C mov ebx, [ebp+20h]
.text:0046A75F mov dr1, edi
.text:0046A762 mov dr2, ebx
.text:0046A765 mov esi, [ebp+24h]
.text:0046A768 mov edi, [ebp+28h]
.text:0046A76B mov ebx, [ebp+2Ch]
.text:0046A76E mov dr3, esi
.text:0046A771 mov dr6, edi
.text:0046A774 mov dr7, ebx
.text:0046A777 jmp loc_46A6BF // 判断是否是V86(虚拟8086即16位操作系统)
.text:0046A77C // ---------------------------------------------------------------------------
.text:0046A77C
.text:0046A77C loc_46A77C: // CODE XREF: _KiServiceExit+BA↑j
.text:0046A77C mov eax, [esp+arg_40]
.text:0046A780 add esp, 30h
.text:0046A783 pop gs
.text:0046A785 pop es
.text:0046A786 assume es:nothing
.text:0046A786 pop ds
.text:0046A787 assume ds:_data
.text:0046A787 pop edx
.text:0046A788 pop ecx
.text:0046A789 jmp loc_46A6F5
.text:0046A78E // ---------------------------------------------------------------------------
.text:0046A78E
.text:0046A78E loc_46A78E: // CODE XREF: _KiServiceExit+A6↑j
.text:0046A78E mov ebx, [esp+arg_C] // CS最低位为0,说明先前模式是0
.text:0046A792 mov [esp+arg_68], ebx
.text:0046A796 mov ebx, [esp+arg_10]
.text:0046A79A sub ebx, 0Ch
.text:0046A79D mov [esp+arg_60], ebx
.text:0046A7A1 mov esi, [esp+arg_6C]
.text:0046A7A5 mov [ebx+8], esi
.text:0046A7A8 mov esi, [esp+arg_68]
.text:0046A7AC mov [ebx+4], esi
.text:0046A7AF mov esi, [esp+arg_64]
.text:0046A7B3 mov [ebx], esi
.text:0046A7B5 add esp, 54h
.text:0046A7B8 pop edi
.text:0046A7B9 pop esi
.text:0046A7BA pop ebx
.text:0046A7BB pop ebp
.text:0046A7BC mov esp, [esp-64h+arg_60]
.text:0046A7BF iret
.text:0046A7BF // END OF FUNCTION CHUNK FOR _KiServiceExit
.text:0046A7C0 // ---------------------------------------------------------------------------
.text:0046A7C0 // START OF FUNCTION CHUNK FOR _KiFastCallEntry
.text:0046A7C0
.text:0046A7C0 loc_46A7C0: // CODE XREF: _KiFastCallEntry+F2↑j
.text:0046A7C0 test byte ptr [ebp+6Ch], 1
.text:0046A7C4 jz loc_46A618 // 可以得知:当前函数的堆栈(也是当前线程的堆栈)从栈顶到栈底的分布情况:
.text:0046A7C4 // 1. 从3环拷贝过来的3环API参数
.text:0046A7C4 // 2. Trapframe(共0x8C字节)
.text:0046A7C4 // 3. 浮点寄存器(共0x210字节)
.text:0046A7CA mov eax, 0C0000005h
.text:0046A7CF jmp loc_46A61C
.text:0046A7CF // END OF FUNCTION CHUNK FOR _KiFastCallEntry
.text:0046A7D4
.text:0046A7D4 // =============== S U B R O U T I N E =======================================
.text:0046A7D4
.text:0046A7D4
.text:0046A7D4 _KiServiceExit2 proc near // CODE XREF: NtContinue(x,x)+3B↓j
.text:0046A7D4 // NtRaiseException(x,x,x)+3E↓j ...
.text:0046A7D4
.text:0046A7D4 arg_C = dword ptr 10h
.text:0046A7D4 arg_10 = dword ptr 14h
.text:0046A7D4 arg_38 = dword ptr 3Ch
.text:0046A7D4 arg_3C = dword ptr 40h
.text:0046A7D4 arg_40 = dword ptr 44h
.text:0046A7D4 arg_44 = dword ptr 48h
.text:0046A7D4 arg_48 = dword ptr 4Ch
.text:0046A7D4 arg_60 = dword ptr 64h
.text:0046A7D4 arg_64 = dword ptr 68h
.text:0046A7D4 arg_68 = dword ptr 6Ch
.text:0046A7D4 arg_6C = dword ptr 70h
.text:0046A7D4
.text:0046A7D4 cli
.text:0046A7D5 test dword ptr [ebp+70h], 20000h
.text:0046A7DC jnz short loc_46A7E4
.text:0046A7DE test byte ptr [ebp+6Ch], 1
.text:0046A7E2 jz short loc_46A818
.text:0046A7E4
.text:0046A7E4 loc_46A7E4: // CODE XREF: _KiServiceExit2+8↑j
.text:0046A7E4 // _KiServiceExit2+42↓j
.text:0046A7E4 mov ebx, large fs:124h
.text:0046A7EB mov byte ptr [ebx+2Eh], 0
.text:0046A7EF cmp byte ptr [ebx+4Ah], 0
.text:0046A7F3 jz short loc_46A818
.text:0046A7F5 mov ebx, ebp
.text:0046A7F7 mov ecx, 1 // NewIrql
.text:0046A7FC call ds:__imp_@KfRaiseIrql@4 // KfRaiseIrql(x)
.text:0046A802 push eax
.text:0046A803 sti
.text:0046A804 push ebx
.text:0046A805 push 0
.text:0046A807 push 1
.text:0046A809 call _KiDeliverApc@12 // VOID KiDeliverApc (
.text:0046A809 // IN KPROCESSOR_MODE PreviousMode,
.text:0046A809 // IN PKEXCEPTION_FRAME ExceptionFrame,
.text:0046A809 // IN PKTRAP_FRAME TrapFrame
.text:0046A809 // )
.text:0046A80E pop ecx // NewIrql
.text:0046A80F call ds:__imp_@KfLowerIrql@4 // KfLowerIrql(x)
.text:0046A815 cli
.text:0046A816 jmp short loc_46A7E4
.text:0046A818 // ---------------------------------------------------------------------------
.text:0046A818
.text:0046A818 loc_46A818: // CODE XREF: _KiServiceExit2+E↑j
.text:0046A818 // _KiServiceExit2+1F↑j
.text:0046A818 mov edx, [esp+arg_48]
.text:0046A81C mov ebx, large fs:50h
.text:0046A823 mov large fs:0, edx
.text:0046A82A mov ecx, [esp+arg_44]
.text:0046A82E mov esi, large fs:124h
.text:0046A835 mov [esi+140h], cl
.text:0046A83B test ebx, 0FFh
.text:0046A841 jnz short loc_46A894
.text:0046A843
.text:0046A843 loc_46A843: // CODE XREF: _KiServiceExit2+D0↓j
.text:0046A843 // _KiServiceExit2+FB↓j
.text:0046A843 test [esp+arg_6C], 20000h
.text:0046A84B jnz loc_46AFD8 // 虚拟8086模式
.text:0046A851 test word ptr [esp+arg_68], 0FFF8h
.text:0046A858 jz short loc_46A8D4
.text:0046A85A mov edx, [esp+arg_38]
.text:0046A85E mov ecx, [esp+arg_3C]
.text:0046A862 mov eax, [esp+arg_40]
.text:0046A866 cmp word ptr [ebp+6Ch], 8
.text:0046A86B jz short loc_46A879
.text:0046A86D lea esp, [ebp+30h]
.text:0046A870 pop gs
.text:0046A872 pop es
.text:0046A873 pop ds
.text:0046A874 lea esp, [ebp+50h]
.text:0046A877 pop fs
.text:0046A879
.text:0046A879 loc_46A879: // CODE XREF: _KiServiceExit2+97↑j
.text:0046A879 lea esp, [ebp+54h]
.text:0046A87C pop edi
.text:0046A87D pop esi
.text:0046A87E pop ebx
.text:0046A87F pop ebp
.text:0046A880 cmp word ptr [esp-60h+arg_64], 80h
.text:0046A887 ja loc_46AFF4
.text:0046A88D add esp, 4
.text:0046A890 iret
.text:0046A890 // ---------------------------------------------------------------------------
.text:0046A891 align 4
.text:0046A894
.text:0046A894 loc_46A894: // CODE XREF: _KiServiceExit2+6D↑j
.text:0046A894 test dword ptr [ebp+70h], 20000h
.text:0046A89B jnz short loc_46A8A6
.text:0046A89D test dword ptr [ebp+6Ch], 1
.text:0046A8A4 jz short loc_46A843
.text:0046A8A6
.text:0046A8A6 loc_46A8A6: // CODE XREF: _KiServiceExit2+C7↑j
.text:0046A8A6 xor ebx, ebx
.text:0046A8A8 mov esi, [ebp+18h]
.text:0046A8AB mov edi, [ebp+1Ch]
.text:0046A8AE mov dr7, ebx
.text:0046A8B1 mov dr0, esi
.text:0046A8B4 mov ebx, [ebp+20h]
.text:0046A8B7 mov dr1, edi
.text:0046A8BA mov dr2, ebx
.text:0046A8BD mov esi, [ebp+24h]
.text:0046A8C0 mov edi, [ebp+28h]
.text:0046A8C3 mov ebx, [ebp+2Ch]
.text:0046A8C6 mov dr3, esi
.text:0046A8C9 mov dr6, edi
.text:0046A8CC mov dr7, ebx
.text:0046A8CF jmp loc_46A843
.text:0046A8D4 // ---------------------------------------------------------------------------
.text:0046A8D4
.text:0046A8D4 loc_46A8D4: // CODE XREF: _KiServiceExit2+84↑j
.text:0046A8D4 mov ebx, [esp+arg_C]
.text:0046A8D8 mov [esp+arg_68], ebx
.text:0046A8DC mov ebx, [esp+arg_10]
.text:0046A8E0 sub ebx, 0Ch
.text:0046A8E3 mov [esp+arg_60], ebx
.text:0046A8E7 mov esi, [esp+arg_6C]
.text:0046A8EB mov [ebx+8], esi
.text:0046A8EE mov esi, [esp+arg_68]
.text:0046A8F2 mov [ebx+4], esi
.text:0046A8F5 mov esi, [esp+arg_64]
.text:0046A8F9 mov [ebx], esi
.text:0046A8FB mov eax, [esp+arg_40]
.text:0046A8FF mov edx, [esp+arg_38]
.text:0046A903 mov ecx, [esp+arg_3C]
.text:0046A907 add esp, 54h
.text:0046A90A pop edi
.text:0046A90B pop esi
.text:0046A90C pop ebx
.text:0046A90D pop ebp
.text:0046A90E mov esp, [esp-64h+arg_60]
.text:0046A911 iret
.text:0046A911 _KiServiceExit2 endp // sp-analysis failed
.text:0046A911
.text:0046A912 // ---------------------------------------------------------------------------
.text:0046A912 retn
.text:0046A912 // ---------------------------------------------------------------------------

666.png

7 总结

7.1 关于用户APC的执行

在函数KiInsertQueueApc,最后处理用户APC时有如下代码:

1
2
3
4
5
6
if(Thread.State == Waiting && Thread.WaitMode == UserMode && (Thread.Alertable == 1 || 
Thread.ApcState[UserMode].UserApcPending == 1))
{
Thread.ApcState[UserMode].UserApcPending = 1;
KiUnwaitThread(); //修改线程状态为就绪,并提升优先级
}

需要说明是,如果要让一个刚才插入的用户APC的UserApcPending = 1,最重要的两件事:

  1. 在APC插入前:目标线程的Thread.Alertable == 1 || Thread.ApcState[UserMode].UserApcPending == 1在APC对象插入目标线程前,才会设置Thread.ApcState[UserMode].UserApcPending = 1

  2. 在APC插入后:除了上述的那两种情况可以使刚插入的用户APC获得执行机会之外,还有一种可以让用户APC可执行的方法。在函数KiDeliverApc中对于非空的用户APC队列有如下的判断,如果符合如下条件就会执行用户APC函数:

    1
    PreviousMode == 1 && CurrentThread->ApcState->UserApcPending == 1

    所以当一个用户APC对象插入APC队列之后,在目标线程3环调用函数NtTestAlert也是可以的,因为:

    R3 NtTestAlert:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //R3 NtTestAlert
    .text:7C92DE70 ; _DWORD __stdcall NtTestAlert()
    .text:7C92DE70 public _NtTestAlert@0
    .text:7C92DE70 _NtTestAlert@0 proc near // CODE XREF: _LdrpInitialize(x,x,x):loc_7C93AFF7↓p
    .text:7C92DE70 // DATA XREF: .text:off_7C923428↑o
    .text:7C92DE70 mov eax, 103h // NtTestAlert
    .text:7C92DE75 mov edx, 7FFE0300h
    .text:7C92DE7A call dword ptr [edx]
    .text:7C92DE7C retn
    .text:7C92DE7C _NtTestAlert@0 endp

    R0 NtTestAlert:

    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
    //R0 NtTestAlert
    PAGE:004FDBFE ; =============== S U B R O U T I N E =======================================
    PAGE:004FDBFE
    PAGE:004FDBFE
    PAGE:004FDBFE ; NTSTATUS __stdcall NtTestAlert()
    PAGE:004FDBFE _NtTestAlert@0 proc near // DATA XREF: .text:0042D85C↑o
    PAGE:004FDBFE mov eax, large fs:124h
    PAGE:004FDC04 movsx eax, [eax+_KTHREAD.PreviousMode]
    PAGE:004FDC0B push eax
    PAGE:004FDC0C call _KeTestAlertThread@4 // ---
    PAGE:004FDC0C // BOOLEAN __stdcall KeTestAlertThread (
    PAGE:004FDC0C // IN KPROCESSOR_MODE AlertMode
    PAGE:004FDC0C // )
    PAGE:004FDC0C //
    PAGE:004FDC0C // 如果吵醒模式AlertMode == 1,并且用户APC队列不为空,
    PAGE:004FDC0C // 那就设置 Thread->ApcState.UserApcPending = TRUE 然后交付
    //下一个用户APC
    PAGE:004FDC0C // 【检查该线程是否可以交付另一个用户模式的APC】
    PAGE:004FDC0C // ---
    PAGE:004FDC11 neg al
    PAGE:004FDC13 sbb eax, eax
    PAGE:004FDC15 and eax, 101h
    PAGE:004FDC1A retn
    PAGE:004FDC1A _NtTestAlert@0 endp
    PAGE:004FDC1A
    PAGE:004FDC1A ; ---------------------------------------------------------------------------

7.2 多个用户APC在R0-R3来回循环执行过程

假设第一个用户APC是在线程正常从R0返回R3时触发的,因为KiFastCallEntry函数在call ebx调用完成之后(ebxNt*函数),会继续往下走直接调用函数KiServiceExit

KiServiceExit函数开头判断KTRAP_FRAME.SegCs最低位为1,这里实际上就是类似验证PreviousMode是来自3环,就会调用函数KiDeliverApc

这个时候才会开始进行APC的交付调度。

  1. 在函数KiDeliverApc中对用户APC作如下判断,如果条件符合就开始交付:

    1
    2
    if(&CurrentThread->ApcState->ApcListHead[1] != NextEntry && PreviousMode == 1 && 
    CurrentThread->ApcState[UserMode]->UserApcPending == 1)
  2. 首先会设置Thread->ApcState[UserMode].UserApcPending = FALSE,然后往下判断Apc->NormalRoutine != 0就会调用KiInitializeUserApc去将“从3环经过快速调用进0环后”在函数KiFastCallEntry中的TrapFrame拷贝到Conntext后对TrapFrame相关成员进行修改,使其从KiServiceExit返回到ntdll.dll!KiUserApcDispatcher函数中去。(我将这里的TrapFrame叫做TrapFrame_Initial

  3. 在函数KiUserApcDispatcher中做了以下这些事:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //R3 ntdll.dll KiUserApcDispatcher
    .text:7C92E430 // =============== S U B R O U T I N E =======================================
    .text:7C92E430
    .text:7C92E430
    .text:7C92E430 // __stdcall KiUserApcDispatcher(x, x, x, x, x)
    .text:7C92E430 public _KiUserApcDispatcher@20
    .text:7C92E430 _KiUserApcDispatcher@20 proc near // DATA XREF: .text:off_7C923428↑o
    .text:7C92E430
    .text:7C92E430 pR3_Stack_Context = byte ptr 10h
    .text:7C92E430
    .text:7C92E430 lea edi, [esp+pR3_Stack_Context]
    .text:7C92E434 pop eax // NormalRoutine
    .text:7C92E435 call eax
    .text:7C92E437 push 1 // TestAlert
    .text:7C92E439 push edi // 在3环函数中保存的 CONTEXT 的首地址
    .text:7C92E43A call _ZwContinue@8 // __stdcall NtContinue(PCONTEXT Context, BOOLEAN TestAlert)
    .text:7C92E43F nop
    .text:7C92E43F _KiUserApcDispatcher@20 endp // sp-analysis failed

    然后在3环的ZwContinue函数如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //R3 ZwContinue
    .text:7C92D040 // =============== S U B R O U T I N E =======================================
    .text:7C92D040
    .text:7C92D040 // NTSTATUS __stdcall NtContinue(PCONTEXT Context, BOOLEAN TestAlert)
    .text:7C92D040
    .text:7C92D040 // __stdcall ZwContinue(x, x)
    .text:7C92D040 public _ZwContinue@8
    .text:7C92D040 _ZwContinue@8 proc near // CODE XREF: KiUserApcDispatcher(x,x,x,x,x)+A↓p
    .text:7C92D040 // KiUserExceptionDispatcher(x,x)+17↓p ...
    .text:7C92D040 mov eax, 20h // NtContinue
    .text:7C92D045 mov edx, 7FFE0300h
    .text:7C92D04A call dword ptr [edx]
    .text:7C92D04C retn 8
    .text:7C92D04C _ZwContinue@8 endp
    .text:7C92D04C
    .text:7C92D04C // ---------------------------------------------------------------------------
  4. 可以看到,通过_ZwContinue函数走的是快速调用KiFastSystemCall进的0环。进入0环后的函数是KiFastCallEntry,然后将其分发到函数NtContinue中。

  5. 0环NtContinue函数调用KiContinue将保存在3环的Context拷贝到TrapFrame_Initial使其被更新,然后调用KeTestAlertThread去设置Thread.ApcState[UserMode].UserApcPending = 1

  6. 然后NtContinue接着调用函数KiServiceExit2继续去判断是否还有用户APC待执行,这样就会构成一个循环。让用户APC全部能够得到执行。

以上可以看到,步骤2每当在KiDeliverApc中准备去执行一个用户APC时会设置Thread->ApcState[UserMode].UserApcPending = FALSE,而步骤5当这一个用户APC在3环执行完毕并返回0环后,会调用KeTestAlertThread去设置Thread.ApcState[UserMode].UserApcPending = 1。这样在NtContinue又会接着调用函数KiServiceExit2继续去判断是否还有用户APC待执行,这样就会构成一个循环。让用户APC全部能够得到执行。

7.3 3环的APC注入