Windows XP APC(一)

ʕ •ᴥ•ʔ ɔ:

1 APC

线程是自己结束的,线程不能被其他线程杀死,考虑一种极端情况,线程屏蔽了时钟中断,不发生异常,那么它就能一直执行下去。

前面分析过,PspTerminateThreadByPointerKeSuspendThread函数改变其他线程行为的方式是向线程APC队列添加一个APC结构,线程执行到某个时刻,会检查自己的APC队列,判断要做什么工作,包括结束自己。

  • 控制其他线程,就给他插入APC。
  • 想要改变另外一个线程的行为,就给它提供一个函数,让他某个时候去执行,这个函数称为APC函数。
  • 线程只能自杀,不能他杀。控制别的线程,就是通过APC实现的。

那如果想改变一个线程的行为该怎么办呢?
可以给他提供一个函数,让它自己去调用,这个函数就是APC(Asyncroneus Procedure Call),即异步过程调用

海哥说,从0环回去执行3环API的过程是最难理解的。理解了就基本学会调试了。

1.1 APC队列

APC队列:_KAPC

APC状态是挂在线程结构体KTHREAD上的,如下:

1
2
3
4
5
6
kd> dt _KTHREAD
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
...
+0x034 ApcState : _KAPC_STATE
...

APC状态的结构为_KAPC_STATE,如下:

1
2
3
4
5
6
7
kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY //2个APC队列,内核APC和用户APC。每个队列8字节,共16字节
+0x010 Process : Ptr32 _KPROCESS //线程所属或者所挂靠的进程
+0x014 KernelApcInProgress : UChar //内核APC是否正在执行,1:正在执行,0:不在执行
+0x015 KernelApcPending : UChar //是否有正在等待执行的内核APC,1:有,0:没有
+0x016 UserApcPending : UChar //是否有正在等待执行的用户APC,1:有,0:没有
  • 用户APC:APC函数地址位于用户空间,在用户空间执行。
  • 内核APC:APC函数地址位于内核空间,在内核空间执行。

用户APC,从0环回去执行3环API的过程是最难理解的。

APC队列的结构_KAPC如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kd> dt _KAPC
ntdll!_KAPC
+0x000 Type : Int2B //类型 APC类型为18
+0x002 Size : Int2B //本结构体的大小 0x30
+0x004 Spare0 : Uint4B //未使用
+0x008 Thread : Ptr32 _KTHREAD //目标线程
+0x00c ApcListEntry : _LIST_ENTRY //APC队列挂的位置
+0x014 KernelRoutine : Ptr32 void //指向一个函数(调用ExFreePoolWithTag 释放APC),用来处理当前KAPC结构占用的内存
+0x018 RundownRoutine : Ptr32 void //略
+0x01c NormalRoutine : Ptr32 void //用户APC总入口/真正的内核apc函数
+0x020 NormalContext : Ptr32 Void //内核APC:NULL 用户APC:真正的APC函数
+0x024 SystemArgument1 : Ptr32 Void //APC函数的参数
+0x028 SystemArgument2 : Ptr32 Void //APC函数的参数
+0x02c ApcStateIndex : Char //挂哪个队列,有四个值:0 1 2 3
+0x02d ApcMode : Char //内核APC/用户APC
+0x02e Inserted : UChar //表示本apc是否已挂入队列 挂入前:0 挂入后 1

APC函数何时被执行?

KiServiceExit函数: 这个函数是系统调用异常中断返回用户空间的必经之路。

KiDeliverApc函数:负责执行APC函数。

1.2 备用APC队列

KTHREAD中和APC相关的几个属性如下:

1
2
3
4
5
6
7
8
9
10
11
12
kd> dt _KTHREAD
ntdll!_KTHREAD
...
+0x034 ApcState : _KAPC_STATE //APC队列所属结构
...
+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE //指针数组
...
+0x14c SavedApcState : _KAPC_STATE //APC 备用队列所属结构
...
+0x165 ApcStateIndex : UChar //ApcStatePointer 的指针索引
+0x166 ApcQueueable : UChar //表示是否可以向线程的APC队列中插入APC
...

ApcState在上一节已经进行说明。

SavedApcState

SavedApcState的结构也是_KAPC_STATE,第一个成员称为APC备用队列。成员SavedApcState与线程挂靠相关。

为什么需要备用队列?

假设A进程有x、y两个线程,x线程有一个APC队列,当x线程挂靠B进程后,APC队列中存储的却仍然是原来的APC!具体点说,比如某个APC函数要读取一个地址为0x12345678的数据,如果此时进行读取,读到的将是B进程的地址空间,这样逻辑就错误了!

为了避免混乱,在x线程挂靠B进程时,会将ApcState中的值暂时存储到SavedApcState中,等回到原进程A时,再将APC队列恢复。

所以,SavedApcState中的ApcListHead又称为备用APC队列

A进程的T线程挂靠B进程:A是T的所属进程B是T的挂靠进程

  • ApcState:挂靠进程相关的APC函数。
  • SavedApcState:所属进程相关的APC函数。

在正常情况下,当前进程就是所属进程A,如果是挂靠情况下,当前进程就是挂靠进程B。

ApcStatePointer

为了操作方便,_KTHREAD结构体中定义了一个指针数组ApcStatePointer,长度为2。可以方便直接找到ApcStateSavedApcState

正常情况下 挂靠情况下
ApcStatePointer[0] 指向 ApcState ApcStatePointer[0] 指向 SavedApcState
ApcStatePointer[1] 指向 SavedApcState ApcStatePointer[1] 指向 ApcState

ApcStateIndex

ApcStateIndex用来标识当前线程处于什么状态:

  • 0 正常状态
  • 1 挂靠状态

ApcStatePointerApcStateIndex 组合寻址:

  • 正常情况下,向ApcState队列中插入APC时:
    ApcStatePointer[0] 指向 ApcState,此时ApcStateIndex的值为0ApcStatePointer[ApcStateIndex] 指向 ApcState

  • 挂靠情况下,向ApcState队列中插入APC时:
    ApcStatePointer[1] 指向 ApcState,此时ApcStateIndex的值为1ApcStatePointer[ApcStateIndex] 指向 ApcState

⚠️注意:KTHREAD.ApcStateIndexKAPC.ApcStateIndex是不一样的,需要注意区分。

总结:

  • 无论什么环境下,ApcStatePointer[ApcStateIndex] 指向的都是ApcState
  • ApcState则总是表示线程当前使用的APC状态。

ApcQueueable

ApcQueueable用于表示是否可以向线程的APC队列中插入APC。

当线程正在执行退出的代码时,会将这个值设置为0 ,如果此时执行插入APC的代码(KeInsertQueueApc 后面会讲),在插入函数中会判断这个值的状态,如果为0,则插入失败。

2 APC挂入过程

无论是正常状态还是挂靠状态,都有两个APC队列:

  • ApcState[0]:内核队列
  • ApcState[1]:用户队列

ApcState[0] + 0x8 == ApcState[1],每一个都是_LIST_ENTRY双向循环链表。

1
2
3
4
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;

每当要挂入一个APC函数时,不管是内核APC还是用户APC,内核都要准备一个KAPC的数据结构,并且将这个KAPC结构挂到相应的APC队列中。

2.1 KAPC结构

_KAPC即为APC队列或叫做APC对象,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kd> dt _KAPC
ntdll!_KAPC
+0x000 Type : Int2B //类型 APC类型为18
+0x002 Size : Int2B //本结构体的大小 0x30
+0x004 Spare0 : Uint4B //未使用
+0x008 Thread : Ptr32 _KTHREAD //目标线程
+0x00c ApcListEntry : _LIST_ENTRY //APC队列挂的位置
+0x014 KernelRoutine : Ptr32 void //指向一个函数(调用ExFreePoolWithTag 释放APC),用来处理当前KAPC结构占用的内存
+0x018 RundownRoutine : Ptr32 void //略
+0x01c NormalRoutine : Ptr32 void //用户APC总入口/真正的内核apc函数
+0x020 NormalContext : Ptr32 Void //内核APC:NULL 用户APC:真正的APC函数
+0x024 SystemArgument1 : Ptr32 Void //APC函数的参数
+0x028 SystemArgument2 : Ptr32 Void //APC函数的参数
+0x02c ApcStateIndex : Char //挂哪个队列,有四个值:0 1 2 3
+0x02d ApcMode : Char //内核APC/用户APC
+0x02e Inserted : UChar //表示本apc是否已挂入队列 挂入前:0 挂入后 1

挂入流程:

  1. QueueUserAPC(kernel32.dll)→ NtQueueApcThread(ntosker.exe )分配空间
  2. KeInitializeApc(初始化KAPC结构体)→ KeInsertQueueApc → KiInsertQueueApc(将KAPC插入指定APC队列)

2.2 KeInitializeApc函数说明

KeInitializeApc函数:这个函数初始化一个内核 APC 对象。 线程、内核例程和可选的正常例程、处理器模式和正常上下文参数存储在 APC 对象中。

1
2
3
4
5
6
7
8
9
10
11
VOID KeInitializeApc
(
IN PKAPC Apc, //KAPC指针(在NtQueueApcThread中分配好空间但是还没有初始化的结构)
IN PKTHREAD Thread, //目标线程
IN KAPC_ENVIRONMENT TargetEnvironment, //0 1 2 3四种状态(挂到哪一个APC队列中去)
IN PKKERNEL_ROUTINE KernelRoutine, //销毁KAPC的函数地址
IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,
IN PKNORMAL_ROUTINE NormalRoutine, //用户APC总入口或者内核apc函数
IN KPROCESSOR_MODE Mode, //要插入用户apc队列还是内核apc队列
IN PVOID Context //内核APC:NULL 用户APC:真正的APC函数
)

2.3 _KAPC.ApcStateIndex

_KAPC.ApcStateIndexKTHREAD.ApcStateIndex(+0x165)的属性同名,但含义不一样:

  • KTHREAD.ApcStateIndex(+0x165):用来标识当前线程处于什么状态:

    • 0 正常状态
    • 1 挂靠状态
  • _KAPC.ApcStateIndex:用来标识将APC队列挂入哪个进程环境:

    _KAPC.ApcStateIndex 含义 挂入
    0 挂入原始进程环境(将APC挂入挂靠之前的进程) 正常:挂入ApcState,已挂靠:挂入SavedApcState
    1 挂入挂靠进程环境 (将APC挂入挂靠之后的进程) 正常:挂入SavedApcState,已挂靠:挂入ApcState
    2 挂入初始化时当前进程环境 (在KeInitializeApc中的当前进程环境) 赋值_KAPC.ApcStateIndex = KTHREAD.ApcStateIndex,APC挂入ApcState
    3 挂入插入APC时的当前环境(在KiInsertQueueApc中的当前进程环境,而非初始化时当前的环境) 在函数KiInsertQueueApc中才进行对ApcStateIndex赋值,APC挂入ApcState
    1
    2
    3
    4
    5
    6
    typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment, //原始环境
    AttachedApcEnvironment, //挂靠环境
    CurrentApcEnvironment, //当前环境,即Thread → ApcStateIndex
    InsertApcEnvironment //具体插入队列时的当前环境
    } KAPC_ENVIRONMENT;

正常情况下:

  • ApcStatePointer[0] 指向 ApcState
  • ApcStatePointer[1] 指向 SavedApcState

挂靠情况下:

  • ApcStatePointer[0] 指向 SavedApcState
  • ApcStatePointer[1] 指向 ApcState

2.4 KiInsertQueueApc函数说明

KiInsertQueueApc函数:此函数将 APC 对象插入线程的 APC 队列。 线程对象的地址、APC队列、APC的类型都是从APC对象派生的。 如果 APC 对象已经在 APC 队列中,则不执行任何操作并返回函数值 FALSE。 否则将APC插入到指定的APC队列中,其插入状态设置为TRUE,并返回函数值TRUE。 当存在适当的启用条件时,APC 将实际交付。

函数KiInsertQueueApc主要做了以下几件事:

1)根据KAPC结构中的ApcStateIndex找到对应的APC队列
2)再根据KAPC结构中的ApcMode确定是用户队列还是内核队列
3)将KAPC挂到对应的队列中(挂到KAPC的ApcListEntry处)
4)再根据KAPC结构中的Inserted置1,标识当前的KAPC为已插入状态
5)修改KAPC_STATE结构中的KernelApcPending/UserApcPending

关于以上第5)点需要进行说明,在设置KernelApcPending/UserApcPending,对内核APC和用户APC的设置条件不同:

  • 内核APC:直接修改。

    1
    mov     [ecx+_KTHREAD.ApcState.KernelApcPending], 1
  • 用户APC:分两种情况,进行条件判断。

    第一种:

    1
    2
    3
    4
    if ((ApcMode != KernelMode) && (Apc->KernelRoutine == PsExitSpecialApc))
    {
    Thread->ApcState.UserApcPending = TRUE;
    }

    第二种:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //如果新插入的APC的环境和对应线程所处的环境一致,可以尝试让apc立即执行起来
    if(KAPC.ApcStateIndex == Thread.ApcStateIndex)
    {
    if(KTHREAD.State != Running)
    {
    if(KTHREAD.State == Waiting && KTHREAD.WaitMode == 1 && KTHREAD.Alertable == 1 ||
    AD.ApcState.UserApcPending == 1)
    {
    Thread->ApcState.UserApcPending = TRUE;
    KiUnwaitThread(Thread, STATUS_USER_APC, Increment, NULL);
    }
    }
    }

    //其他情况:虽然APC队列已经插入,但是得不到执行,除非请他情况下将UserApcPending置1

2.5 KTHREAD.Alertable

说明一个线程是否可以被吵醒。Alertable是个布尔量,表示是否允许本次等待因用户空间APC而中断,或者说被“吵醒(Alert)”。 吵醒与唤醒是不同的,唤醒是因为所等待的条件得到了满足(仓库到货),而吵醒是因为别的原因而醒来(与仓库无关)。《Windows内核情景分析6.3 P473》

关于上述2.3的第5)点:

  1. Alertable=0 当前插入的APC函数未必有机会执行:UserApcPending = 0
  2. Alertable=1,UserApcPending = 1,将目标线程唤醒(从等待链表中摘出来,并挂到调度链表)

3 挂入过程分析

3.1 QueueUserAPC

函数QueueUserAPC:将用户模式 APC 排队到指定线程的APC队列中。

1
2
3
4
5
DWORD WINAPI QueueUserAPC(
[in] PAPCFUNC pfnAPC, //用户模式要执行的APC函数地址,void Papcfunc([in] ULONG_PTR Parameter)
[in] HANDLE hThread, //用户APC函数要插入的目标线程句柄,该句柄需具有THREAD_SET_CONTEXT访问权限
[in] ULONG_PTR dwData //用户APC函数的参数
);

函数的调用过程:

1
QueueUserAPC → NtQueueApcThread → KeInitializeApc → KeInsertQueueApc → KiInsertQueueApc

这里有一个很奇怪的问题就是,QueueUserAPC函数只有3个参数,但是NtQueueApcThread是5个参数,为什么多两个?其他三个参数的对应关系又是什么?那还是需要分析一下原函数。

QueueUserAPC中参数入栈的顺序是:

1
2
3
4
5
6
7
8
9
10
11
12
13
push var_buffer;	//SystemArgument2
push dwData; //SystemArgument1
push pfnAPC; //NormalContext
push BaseDispatchAPC; //ApcRoutine,所有用户APC的总入口 BaseDispatchAPC(3环函数)
push hThread; //ThreadHandle

NTSYSAPI NTSTATUS NTAPI NtQueueApcThread(
IN HANDLE ThreadHandle,
IN PPS_APC_ROUTINE ApcRoutine,
IN PVOID ApcArgument1, //NormalContext
IN PVOID ApcArgument2,
IN PVOID ApcArgument3
)

函数源码如下:

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
WINBASEAPI DWORD WINAPI QueueUserAPC(
PAPCFUNC pfnAPC,
HANDLE hThread,
ULONG_PTR dwData
)
/*++
Routine Description:
This function is used to queue a user-mode APC to the specified thread. The APC
will fire when the specified thread does an alertable wait.
Arguments:
pfnAPC - Supplies the address of the APC routine to execute when the APC fires.
hHandle - Supplies a handle to a thread object. The caller
must have THREAD_SET_CONTEXT access to the thread.
dwData - Supplies a DWORD passed to the APC
Return Value:
TRUE - The operations was successful
FALSE - The operation failed. GetLastError() is not defined.
--*/
{
NTSTATUS Status;
PVOID Argument1 = (PVOID) pfnAPC;
PVOID Argument2 = (PVOID) dwData;
PVOID Argument3 = NULL;
ACTIVATION_CONTEXT_BASIC_INFORMATION acbi = { 0 };

Status = RtlQueryInformationActivationContext(
RTL_QUERY_INFORMATION_ACTIVATION_CONTEXT_FLAG_USE_ACTIVE_ACTIVATION_CONTEXT,
NULL,
0,
ActivationContextBasicInformation,
&acbi,
sizeof(acbi),
NULL);
if (!NT_SUCCESS(Status)) {
DbgPrint("SXS: %s failing because RtlQueryInformationActivationContext() returned
status %08lx\n", __FUNCTION__, Status);
return FALSE;
}

Argument3 = acbi.ActivationContext;

if (acbi.Flags & ACTIVATION_CONTEXT_FLAG_NO_INHERIT) {
// We're not supposed to propogate the activation context; set it to a value to indicate such.
Argument3 = INVALID_ACTIVATION_CONTEXT;
}

Status = NtQueueApcThread(
hThread,
&BaseDispatchAPC,
Argument1,
Argument2,
Argument3
);

if ( !NT_SUCCESS(Status) ) {
return 0;
}
return 1;
}

3.2 NtQueueApcThread

函数NtQueueApcThread:(0环)将用户模式 APC 排队到指定线程的APC队列中。当指定的线程被吵醒(alertable)时就会执行插入的APC函数。(This function is used to queue a user-mode APC to the specified thread. The APC will fire when the specified thread does an alertable wait.)

函数原型如下:

1
2
3
4
5
6
7
NTSYSAPI NTSTATUS NTAPI NtQueueApcThread(
IN HANDLE ThreadHandle, //目标线程句柄,函数调用者必须有对目标线程THREAD_SET_CONTEXT的访问权限
IN PPS_APC_ROUTINE ApcRoutine, //KAPC->NormalRoutine,用户APC函数,当执行条件满足时将会被执行
IN PVOID ApcArgument1, //KAPC->NormalContext,3环APC函数
IN PVOID ApcArgument2, //KAPC->SystemArgument1 ,3环APC函数的参数
IN PVOID ApcArgument3 //KAPC->SystemArgument2 ,作用不明,BaseDispatchAPC 里用到了
)

从函数QueueUserAPC调用参数压栈顺序来看对应关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DWORD WINAPI QueueUserAPC(
[in] PAPCFUNC pfnAPC, //用户模式要执行的APC函数地址,void Papcfunc([in] ULONG_PTR Parameter)
[in] HANDLE hThread, //用户APC函数要插入的目标线程句柄,该句柄需具有THREAD_SET_CONTEXT访问权限
[in] ULONG_PTR dwData //用户APC函数的参数
);

push var_buffer; //SystemArgument2
push dwData; //SystemArgument1
push pfnAPC; //NormalContext
push BaseDispatchAPC; //ApcRoutine,所有用户APC的总入口 BaseDispatchAPC(3环函数)
push hThread; //ThreadHandle

NTSYSAPI NTSTATUS NTAPI NtQueueApcThread(
IN HANDLE ThreadHandle, //QueueUserAPC→hThread -- KAPC->Thread
IN PPS_APC_ROUTINE ApcRoutine, //BaseDispatchAPC 3环函数 -- KAPC->NormalRoutine
IN PVOID ApcArgument1, //QueueUserAPC→pfnAPC,3环用户APC函数 -- KAPC->NormalContext
IN PVOID ApcArgument2, //QueueUserAPC→dwData,3环用户APC函数的参数 -- KAPC->SystemArgument1
IN PVOID ApcArgument3 //var_buffer,3环的一个局部变量 -- KAPC->SystemArgument2
)

函数处理过程逆向分析如下:

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(!NT_SUCCESS(ObReferenceObjectByHandle(ThreadHandle,x,x,x,Thread,x))) return Ntstatus;

//判断是否是系统线程
if(Thread->CrossThreadFlags == 0x10) //PS_CROSS_THREAD_FLAGS_SYSTEM
{
ObfDereferenceObject(Thread); //减少指定对象的对象引用计数,并在计数变为零时进行清理工作
return 0xC0000008; //STATUS_INVALID_HANDLE,目标线程是系统线程,则用户模式不能向系统线程插入APC
}

Kapc = ExAllocatePoolWithQuotaTag();
if(Kapc == NULL)
{
ObfDereferenceObject(Thread);
return 0xC0000017; //STATUS_NO_MEMORY,没有足够的虚拟内存或分页文件配额来完成指定的操作
}

//NtQueueApcThread(ThreadHandle,ApcRoutine,ApcArgument1,ApcArgument2,ApcArgument3)
//KeInitializeApc(Apc,Thread,Environment,KernelRoutine,RundownRoutine,NormalRoutine,ApcMode,NormalContext)
KeInitializeApc(Kapc,Thread,0,IopDeallocateApc,0,ApcRoutine,1,ApcArgument1); //IopDeallocateApc:销毁KAPC的函数地址

//KeInsertQueueApc (Apc,SystemArgument1,SystemArgument2,Increment)
bRet = KeInsertQueueApc(Kapc,SystemArgument1,SystemArgument2,0);
if(!bRet)
{
ExFreePoolWithTag();
ObfDereferenceObject(Thread);
return 0xC0000001; //STATUS_UNSUCCESSFUL
}

ObfDereferenceObject(Thread);
return 0x00000000; //STATUS_SUCCESS

函数NtQueueApcThread功能很简单,主要做了一下几件事:

  1. 判断目标线程是否是系统线程,如果是的话直接返回,用户模式不能向系统线程插入APC。
  2. 调用ExAllocatePoolWithQuotaTag分配一块空间给即将要插入的KAPC结构用。
  3. 调用KeInitializeApc函数,用参数对KAPC结构进行初始化。
  4. 调用KeInsertQueueApc函数,将KAPC插入到目标线程。

需要注意几个问题:

  • 在从3环调用本函数,设置参数如下:
    • Environment = 0
    • KernelRoutine = IopDeallocateApc 用来销毁KAPC的函数地址
    • RundownRoutine = 0
    • ApcRoutine = BaseDispatchAPC 3环函数
    • ApcMode = 1 UserMode
    • NormalContext = SystemArgument1 3环的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
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
NTSYSAPI NTSTATUS NTAPI NtQueueApcThread(
IN HANDLE ThreadHandle, // 线程句柄,用来获取线程结构 ETHREAD
IN PPS_APC_ROUTINE ApcRoutine, // Apc->NormalRoutine ,是所有用户APC的总入口 BaseDispatchAPC(3环函数)
IN PVOID ApcArgument1, // Apc->NormalContext ,3环APC函数
IN PVOID ApcArgument2, // Apc->SystemArgument1 ,3环APC函数的参数
IN PVOID ApcArgument3 // Apc->SystemArgument2 ,作用不明,BaseDispatchAPC 里用到了
)
/*++
Routine Description:
This function is used to queue a user-mode APC to the specified thread. The APC
will fire when the specified thread does an alertable wait
Arguments:
ThreadHandle - Supplies a handle to a thread object. The caller
must have THREAD_SET_CONTEXT access to the thread.
ApcRoutine - Supplies the address of the APC routine to execute when the APC fires.
ApcArgument1 - Supplies the first PVOID passed to the APC
ApcArgument2 - Supplies the second PVOID passed to the APC
ApcArgument3 - Supplies the third PVOID passed to the APC
Return Value: Returns an NT Status code indicating success or failure of the API
--*/
{
PETHREAD Thread;
NTSTATUS st;
KPROCESSOR_MODE Mode;
PKAPC Apc;

PAGED_CODE();

Mode = KeGetPreviousMode ();

// 获取 ETHREAD
st = ObReferenceObjectByHandle (ThreadHandle,
THREAD_SET_CONTEXT,
PsThreadType,
Mode,
&Thread,
NULL);
if (NT_SUCCESS (st)) {
st = STATUS_SUCCESS;
if (IS_SYSTEM_THREAD (Thread)) {
st = STATUS_INVALID_HANDLE;
} else {
// 申请 APC 内存
Apc = ExAllocatePoolWithQuotaTag (NonPagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
sizeof(*Apc),
'pasP');

if (Apc == NULL) {
st = STATUS_NO_MEMORY;
} else {
// 初始化用户 APC
KeInitializeApc (Apc,
&Thread->Tcb,
OriginalApcEnvironment, // 插入到所属进程(创建线程的那个进程)
PspQueueApcSpecialApc, // KernelRoutine , 作用是释放 APC 内存(ExFreePool)
NULL, // RundownRoutine 未指定
(PKNORMAL_ROUTINE)ApcRoutine, // 用户APC总入口 BaseDispatchAPC(3环函数)
UserMode, // 用户模式APC
ApcArgument1); // 3环APC函数

// ApcArgument2 是3环APC函数的参数
if (!KeInsertQueueApc (Apc, ApcArgument2, ApcArgument3, 0)) {
ExFreePool (Apc);
st = STATUS_UNSUCCESSFUL;
}
}
}
ObDereferenceObject (Thread);
}

return st;
}

3.3 KeInitializeApc

函数KeInitializeApc:该函数对内核对象KAPC进行初始化。

需要注意的是,KAPC->SystemArgument1KAPC->SystemArgument2是在函数KeInsertQueueApc中初始化的。

1
2
3
4
5
6
7
8
9
10
11
12
VOID __stdcall KeInitializeApc (
IN PRKAPC Apc, //APC结构体指针
IN PRKTHREAD Thread, //要插入APC的目标线程
IN KAPC_ENVIRONMENT Environment, //四种环境状态,包括所属进程,挂靠进程,当前进程(提供CR3的进程),插入时的当前进程
IN PKKERNEL_ROUTINE KernelRoutine, //不管是用户APC还是内核APC,这个函数的共同作用是释放APC
//内核APC可能会有额外的功能,如退出、挂起、恢复线程
IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,//如果是用户APC,这里是NULL;如果是要求退出线程的内核APC,这里是 PspExitApcRundown
IN PKNORMAL_ROUTINE NormalRoutine OPTIONAL, //如果是用户APC,这里是 BaseDispatchAPC(3环函数)
// 如果是内核APC,这里就是内核APC函数
IN KPROCESSOR_MODE ApcMode OPTIONAL, //1:用户模式UserMode,0:内核模式KernelMode
IN PVOID NormalContext OPTIONAL // 如果是用户APC,这里就是3环提供的APC函数的参数,内核APC为NULL
)

在函数NtQueueApcThread中,参数传递的顺序如下:

1
2
3
//NtQueueApcThread(ThreadHandle,ApcRoutine,ApcArgument1,ApcArgument2,ApcArgument3)
//KeInitializeApc(Apc,Thread,Environment,KernelRoutine,RundownRoutine,NormalRoutine,ApcMode,NormalContext)
KeInitializeApc(Kapc,Thread,0,IopDeallocateApc,0,ApcRoutine,1,ApcArgument1); //IopDeallocateApc:销毁KAPC的函数地址

函数处理过程逆向分析如下:

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
KeInitializeApc(Apc,Thread,Environment,KernelRoutine,RundownRoutine,NormalRoutine,ApcMode,NormalContext)

KAPC->Type = 0x12;
KAPC->Size = 0x30;
KAPC->Thread = Thread;
KAPC->KernelRoutine = KernelRoutine;
KAPC->RundownRoutine = RundownRoutine;
if(Environment == 2) //CurrentApcEnvironment
{
KAPC->ApcStateIndex = Thread->ApcStateIndex;
}else
{
KAPC->ApcStateIndex = Environment;
}

KAPC->NormalRoutine = NormalRoutine;
if(NormalRoutine == 0) //特殊APC
{
KAPC->ApcMode = 0; //KernelMode
KAPC->NormalContext = 0;
}else //普通APC
{
KAPC->ApcMode = ApcMode;
KAPC->NormalContext = NormalContext; //用户APC函数
}

KAPC->Inserted = FALSE;
return;

函数KeInitializeApc的功能非常简单,就是简单的对内核对象KAPC的部分成员进行初始化。但是需要说明的是这里只是初始化部分成员。

⚠️注意:当传入的参数NormalRoutine == 0时,意味着该APC函数是内核APC函数,相应的需要将KAPC->ApcModeKAPC->NormalContext都设置为0。

NtQueueApcThread只是给内核对象KAPC申请一块内存空间,KeInitializeApcKAPC部分成员进行初始化。

函数源码如下:

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
VOID __stdcall KeInitializeApc (
IN PRKAPC Apc, // APC结构体指针
IN PRKTHREAD Thread, // 要插入APC的目标线程
IN KAPC_ENVIRONMENT Environment, // 四种环境状态,包括父进程,挂靠进程,当前进程(提供CR3的进程),插入时的当前进程
IN PKKERNEL_ROUTINE KernelRoutine, // 不管是用户APC还是内核APC,这个函数的共同作用是释放APC
//内核APC可能会有额外的功能,如退出、挂起、恢复线程
IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,// 如果是用户APC,这里是NULL;如果是要求退出线程的内核APC,这里是 PspExitApcRundown
IN PKNORMAL_ROUTINE NormalRoutine OPTIONAL, // 如果是用户APC,这里是 BaseDispatchAPC(3环函数)
// 如果是内核APC,这里就是内核APC函数
IN KPROCESSOR_MODE ApcMode OPTIONAL, // 用户模式 / 内核模式
IN PVOID NormalContext OPTIONAL // 如果是用户APC,这里就是3环提供的APC函数的参数
)
/*++
Routine Description:
This function initializes a kernel APC object. The thread, kernel
routine, and optionally a normal routine, processor mode, and normal
context parameter are stored in the APC object.
分配空间,初始化KAPC结构体
Arguments:
Apc - Supplies a pointer to a control object of type APC.
Thread - Supplies a pointer to a dispatcher object of type thread.
Environment - Supplies the environment in which the APC will execute.
Valid values for this parameter are: OriginalApcEnvironment,
AttachedApcEnvironment, CurrentApcEnvironment, or InsertApcEnvironment
KernelRoutine - Supplies a pointer to a function that is to be
executed at IRQL APC_LEVEL in kernel mode.
RundownRoutine - Supplies an optional pointer to a function that is to be
called if the APC is in a thread's APC queue when the thread terminates.
NormalRoutine - Supplies an optional pointer to a function that is
to be executed at IRQL 0 in the specified processor mode. If this
parameter is not specified, then the ProcessorMode and NormalContext
parameters are ignored.
ApcMode - Supplies the processor mode in which the function specified
by the NormalRoutine parameter is to be executed.
NormalContext - Supplies a pointer to an arbitrary data structure which is
to be passed to the function specified by the NormalRoutine parameter.
Return Value: None.
--*/
{

ASSERT(Environment <= InsertApcEnvironment);

// Initialize standard control object header.
Apc->Type = ApcObject; // 0x12 内核对象类型
Apc->Size = sizeof(KAPC);

// Initialize the APC environment, thread address, kernel routine address,
// rundown routine address, normal routine address, processor mode, and
// normal context parameter. If the normal routine address is null, then
// the processor mode is defaulted to KernelMode and the APC is a special
// APC. Otherwise, the processor mode is taken from the argument list.

//typedef enum _KAPC_ENVIRONMENT {
// OriginalApcEnvironment, // 所属进程(创建线程的进程)
// AttachedApcEnvironment, // 挂靠进程
// CurrentApcEnvironment, // 当前环境,提供CR3的进程(正常状态是所属进程,挂靠状态是挂靠进程)
// InsertApcEnvironment // 插入APC时的环境
//} KAPC_ENVIRONMENT;

// ApcStateIndex 决定了挂到哪个进程的APC队列
if (Environment == CurrentApcEnvironment) {
// 如果要求的是当前状态,那么就从 Thread->ApcStateIndex 里取值,如果当前没有挂靠,则是0,;如果挂靠了,就是1.
Apc->ApcStateIndex = Thread->ApcStateIndex;

} else {

ASSERT((Environment <= Thread->ApcStateIndex) || (Environment == InsertApcEnvironment));

// 否则就默认插入到所属进程(创建线程的进程)的APC队列里
Apc->ApcStateIndex = (CCHAR)Environment;
}

Apc->Thread = Thread; // 目标线程
Apc->KernelRoutine = KernelRoutine; // 主要功能是释放APC
Apc->RundownRoutine = RundownRoutine; // 可选,退出线程时会用到
Apc->NormalRoutine = NormalRoutine; // 如果是用户APC,这里是 BaseDispatchAPC(3环函数)
// 如果是内核APC,这里就是内核APC函数
if (ARGUMENT_PRESENT(NormalRoutine)) {
Apc->ApcMode = ApcMode; // 0内核,1用户
Apc->NormalContext = NormalContext; // 内核APC:NULL;用户APC:真正的APC函数

} else {
Apc->ApcMode = KernelMode;
Apc->NormalContext = NIL; // NULL
}

Apc->Inserted = FALSE;
return;
}

3.4 KeInsertQueueApc

函数KeInsertQueueApc:插入 APC 到指定线程的APC队列,用户态和内核态分别插入对应的 APC 队列。如果 APC 对象已经在 APC 队列或者 APC 队列被禁用(例如线程正在退出),则不执行操作。否则插入 APC 对象并调用相应的函数。

1
2
3
4
5
6
BOOLEAN __stdcall KeInsertQueueApc (
IN PRKAPC Apc,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2,
IN KPRIORITY Increment
)

该函数太简单了,就不逆向分析解释了,主要做了两件事:

  1. Apc->SystemArgument1Apc->SystemArgument1进行初始化。
  2. 调用函数KiInsertQueueApc(Apc, Increment)

函数源代码如下:

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
BOOLEAN KeInsertQueueApc (
IN PRKAPC Apc, //要插入的APC对象
IN PVOID SystemArgument1, //APC函数的参数
IN PVOID SystemArgument2,
IN KPRIORITY Increment //线程优先级增量
)
/*++
Routine Description:
This function inserts an APC object into the APC queue specifed by the
thread and processor mode fields of the APC object. If the APC object
is already in an APC queue or APC queuing is disabled, then no operation
is performed. Otherwise the APC object is inserted in the specified queue
and appropriate scheduling decisions are made.

插入 APC 到指定线程的APC队列,用户态和内核态分别插入对应的 APC 队列。
如果 APC 对象已经在 APC 队列或者 APC 队列被禁用(例如线程正在退出),
则不执行操作。否则插入 APC 对象并调用相应的函数。
Arguments:
Apc - Supplies a pointer to a control object of type APC.
APC 结构
SystemArgument1, SystemArgument2 - Supply a set of two arguments that
contain untyped data provided by the executive.
传给 APC 函数的参数。
Increment - Supplies the priority increment that is to be applied if
queuing the APC causes a thread wait to be satisfied.
线程优先级增量
Return Value:
If the APC object is already in an APC queue or APC queuing is disabled,
then a value of FALSE is returned. Otherwise a value of TRUE is returned.
--*/
{
BOOLEAN Inserted;
KLOCK_QUEUE_HANDLE LockHandle;
KIRQL OldIrql;
PRKTHREAD Thread;

ASSERT_APC(Apc);
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

// Raise IRQL to SYNCH_LEVEL, acquire the thread APC queue lock, and lock the dispatcher database.
// 提升 IRQL 等级,申请 APC 锁,锁 dispatcher database
Thread = Apc->Thread;
KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock, &LockHandle);
KiLockDispatcherDatabaseAtSynchLevel();

// If APC queuing is disabled, then set inserted to FALSE. Else save
// system parameter values in APC object, and attempt to queue APC.
// 调用 KiInsertQueueApc
if (Thread->ApcQueueable == FALSE) {
Inserted = FALSE;

} else {
Apc->SystemArgument1 = SystemArgument1;
Apc->SystemArgument2 = SystemArgument2;
Inserted = KiInsertQueueApc(Apc, Increment);
}

// Unlock the dispatcher database from SYNCH_LEVEL, unlock the thread APC
// queue lock and lower IRQL to its previous value, and return whether the APC was inserted.
KiUnlockDispatcherDatabaseFromSynchLevel();
KeReleaseInStackQueuedSpinLock(&LockHandle);
return Inserted;
}

3.5 KiInsertQueueApc

函数KiInsertQueueApc:此函数将 APC 对象插入到线程的 APC 队列中。线程对象的地址、APC 队列和 APC 的类型(用户/内核)都来生自 APC 对象。

返回值:如果 APC 对象已在 APC 队列中,则不会执行任何操作,并返回函数值 FALSE。否则,APC 将插入到指定的 APC 队列中,其插入状态设置为 TRUE,并返回函数值 TRUE。

1
2
3
4
BOOLEAN FASTCALL KiInsertQueueApc (
IN PKAPC Apc, //即将要插入APC队列的APC对象
IN KPRIORITY Increment //线程优先级增量
)

函数功能逆向分析如下:

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
BOOLEAN FASTCALL KiInsertQueueApc (
IN PKAPC Apc,
IN KPRIORITY Increment
)
{
if(KAPC.Inserted == 1) //已经插入过了,直接返回
{
return FALSE;
}else
{
if(KAPC.ApcStateIndex == 3) //InsertApcEnvironment
{
KAPC->ApcStateIndex = KAPC->Thread->ApcStateIndex
}

ApcState = KAPC->Thread->ApcStatePointer[KAPC.ApcStateIndex];
if(KAPC.NormalRoutine != 0) //普通APC
{
if(KAPC.ApcMode != 0 && KAPC.KernelRoutine == PsExitSpecialApc)//Usermode
{
KAPC->Thread->ApcState.UserApcPending = 1;

//将KAPC.ApcListEntry插入到KAPC.Thread.ApcState[UserMode].ApcListHead链头
KAPC.ApcListEntry.Flink = KAPC.Thread.ApcState[UserMode].ApcListHead->Flink;
KAPC.ApcListEntry.Blink = &KAPC.Thread.ApcState[UserMode].ApcListHead;
KAPC.Thread.ApcState[UserMode].ApcListHead->Flink->Blink = &KAPC.ApcListEntry;
KAPC.Thread.ApcState[UserMode].ApcListHead->Flink = &KAPC.ApcListEntry;
}else //KAPC.ApcMode == 0 || KAPC.KernelRoutine != PsExitSpecialApc
{
//将KAPC.ApcListEntry插入到KAPC.Thread.ApcState[UserMode].ApcListHead链尾
KAPC.ApcListEntry->Flink = &KAPC.Thread.ApcState[ApcMode].ApcListHead;
KAPC.ApcListEntry->Blink = KAPC.Thread.ApcState[ApcMode].ApcListHead->Blink;
KAPC.Thread.ApcState[ApcMode].ApcListHead->Blink->Flink = &KAPC.ApcListEntry;
KAPC.Thread.ApcState[ApcMode].ApcListHead->Blink = &KAPC.ApcListEntry;
}
}else //KAPC.NormalRoutine == 0,特殊APC
{
//从队尾开始遍历 APC 队列,直到找到第一个 NormalRoutine 为空的APC
//然后将新插入的APC对象接在它的后面
esi = ListEntry = KAPC.Thread.ApcState[ApcMode].ApcListHead->Blink;
while(ListEntry != &KAPC.Thread.ApcState[ApcMode].ApcListHead)
{
ApcEntry = CONTAINING_RECORD(ListEntry, KAPC, ApcListEntry);
if(KAPC.NormalRoutine == 0) break;
ListEntry = ListEntry->Blink;
}

//将KAPC.ApcListEntry插入到KAPC.Thread.ApcState[UserMode].ApcListHead链尾
KAPC.ApcListEntry->Flink = ListEntry->Flink;
KAPC.ApcListEntry->Blink = ListEntry;
ListEntry->Flink->Blink = &KAPC.ApcListEntry;
ListEntry->Flink = &KAPC.ApcListEntry;
}

}//end else,KAPC.Inserted == 0

KAPC.Inserted == 1;

//如果新插入的APC的环境和对应线程所处的环境一致,可以尝试让apc立即执行起来
if(KAPC.ApcStateIndex == Thread.ApcStateIndex)
{
//如果在当前线程中插入内核模式 APC,并且线程未禁止特殊 APC,则调用 HalRequestSoftwareInterrupt 函数请求一个软件中断,
//中断发生之后会检查是否有内核APC待执行。
if(KAPC.ApcMode == 0) //内核APC
{
Thread.ApcState.KernelApcPending = 1;
//如果线程正在运行,则中断
if(Thread.State == Running)
{
//请求软中断间接调用KiDeliverApc,来执行APC
HalRequestSoftwareInterrupt(Thread->NextProcessor);
}
//如果是正在等待的线程,则调用 KiUnwaitThread 函数解除该线程的等待,
//KiUnwailThread 会调用 KiReadyThread 函数来唤醒该线程,线程醒来后就会交付它的 APC 对象。
else if(Thread.State == Waiting && Thread.WaitIrql == 0 && (KAPC.NormalRoutine = NULL ||
(Thread.KernelApcDisable == 0 && Thread.ApcState.KernelApcInProgress == 0))
{
KiUnwaitThread(); //修改线程状态为就绪,提升优先级
}
}else if(Thread.State == Waiting && Thread.WaitMode == 1 && (Thread.Alertable == 1 ||
Thread.ApcState[UserMode].UserApcPending == 1))
{
Thread.ApcState[UserMode].UserApcPending = 1;
KiUnwaitThread(); //修改线程状态为就绪,提升优先级
}
}//end if

return 1;
}

如果目标线程正在进行门等待,则 KilnserQueueApe 直接调

  • 普通APC的NormalRoutine是在PASSIVE_LEVEL上被执行的。

  • KernelRoutine:在内核模式下,在APC_LEVEL上执行的函数。

  • KernelRoutine、RundownRoutine、NormalRoutine。其中只有NormalRoutine才指向请求者所提供的函数,其余两个都是辅助性的。

  • NormalRoutine == NULL,则其后的NormalContextApcMode也将没有意义,会被忽略,实际也就变成了KernelMode

    所以内核APC总共两种

    • 普通内核APC:KAPC.NormalRoutine != NULL && ApcMode == KernelMode
    • 特殊内核APC:KAPC.NormalRoutine == NULL
  • 用户模式APC:KAPC.NormalRoutine != NULL && ApcMode == UserMode

函数KiInsertQueueApc功能较多,主要做了几件事:

  1. 先判断待插入的APC是特殊APC,还是普通APC。
    • 特殊APCKAPC.NormalRoutine == NULL
    • 普通APCKAPC.NormalRoutine != NULL
  2. 插入队列的方式:
    • 1)User APC which is PsExitSpecialApc:Put it at the front of the List, and set UserApcPending.
    • 2)Kernel APC with Normal Routine or User APC:Put it at the end of the List, don’t setting UserApcPending or KernelApcPending.
    • 3)Kernel APC without Normal Routine:Put it at the end of the No-Normal Routine Kernel APC list, don’t setting KernelApcPending.
  3. 插入方式:
    • 特殊APC:3)。
    • 普通APC:1)、2)。
  4. 如果新插入的APC的环境和对应线程所处的环境一致,可以尝试让APC立即执行起来。
    • 内核APC:设置KernelApcPending = 1
      • 如果线程正在处于等待状态,调用HalRequestSoftwareInterrupt产生中断然后执行APC。
      • 如果线程处于等待状态,并且满足其他相关条件,就调用KiUnwaitThread(修改线程状态为就绪,提升优先级),等待切换线程后执行APC。
    • 用户APC:满足Thread.State == Waiting && Thread.WaitMode == 1 && (Thread.Alertable == 1 || Thread.ApcState.UserApcPending == 1),则设置UserApcPending = 1,并调用KiUnwaitThread

可以参考:APC插入过程–基于Windows XP源码

函数源码如下:

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
BOOLEAN FASTCALL KiInsertQueueApc (
IN PKAPC Apc,
IN KPRIORITY Increment
)
/*++
Routine Description:
This function inserts an APC object into a thread's APC queue. The address
of the thread object, the APC queue, and the type of APC are all derived
from the APC object. If the APC object is already in an APC queue, then
no opertion is performed and a function value of FALSE is returned. Else
the APC is inserted in the specified APC queue, its inserted state is set
to TRUE, and a function value of TRUE is returned. The APC will actually
be delivered when proper enabling conditions exist.

N.B. The thread APC queue lock and the dispatcher database lock must both
be held when this routine is called.
Arguments:
Apc - Supplies a pointer to a control object of type APC.
Increment - Supplies the priority increment that is to be applied if
queuing the APC causes a thread wait to be satisfied.
Return Value:
If the APC object is already in an APC queue, then a value of FALSE is
returned. Else a value of TRUE is returned.
--*/
{

KPROCESSOR_MODE ApcMode;
PKAPC ApcEntry;
PKAPC_STATE ApcState;
BOOLEAN Inserted;
PLIST_ENTRY ListEntry;
PKTHREAD Thread;

//
// If the APC object is already in an APC queue, then set inserted to
// FALSE. Else insert the APC object in the proper queue, set the APC
// inserted state to TRUE, check to determine if the APC should be delivered
// immediately, and set inserted to TRUE.
//
// For multiprocessor performance, the following code utilizes the fact
// that kernel APC disable count is incremented before checking whether
// the kernel APC queue is nonempty.
//
// See KeLeaveCriticalRegion().
//

Thread = Apc->Thread;
if (Apc->Inserted) {
Inserted = FALSE;

} else {
//typedef enum _KAPC_ENVIRONMENT {
// OriginalApcEnvironment, // 所属进程(创建线程的进程,父进程)
// AttachedApcEnvironment, // 挂靠进程
// CurrentApcEnvironment, // 当前环境,提供CR3的进程(正常状态是所属进程,挂靠状态是挂靠进程)
// InsertApcEnvironment // 插入APC时的环境
//} KAPC_ENVIRONMENT;

// Apc->ApcStateIndex 是在 KeInitializeApc 函数内初始化的,它的值决定了插入到哪个进程的APC队列(所属进程 还是 挂靠进程)
//
// 终止线程的 PspTerminateThreadByPointer 和3环 QueueUserApc , Apc->ApcStateIndex 都是 0
// 0意味着选择所属进程,因为不挂靠时 ApcStatePointer[0] 是所属线程;挂靠时 ApcStatePointer[0] 还是所属线程的备份

if (Apc->ApcStateIndex == InsertApcEnvironment) {
// 插入前实时地从 Thread 里取
//设计 InsertApcEnvironment 也许是考虑到初始化时和插入前线程的状态可以发生改变

Apc->ApcStateIndex = Thread->ApcStateIndex;

}

ApcState = Thread->ApcStatePointer[Apc->ApcStateIndex];

//
// Insert the APC after all other special APC entries selected by
// the processor mode if the normal routine value is NULL. Else
// insert the APC object at the tail of the APC queue selected by
// the processor mode unless the APC mode is user and the address
// of the special APC routine is exit thread, in which case insert
// the APC at the front of the list and set user APC pending.
//


ApcMode = Apc->ApcMode; // 内核APC or 用户APC


if (Apc->NormalRoutine != NULL) {
// NormalRoutine 非空,就在这里插入
// NormalRoutine 是 所有用户APC函数的入口 或者 内核APC函数,取决于APC是用户模式还是内核模式

if ((ApcMode != KernelMode) && (Apc->KernelRoutine == PsExitSpecialApc)) {
// 用户APC中的一种,KernelRoutine == PsExitSpecialApc ,3环调用 QueueUserApc 就是这种情况

// 标记已插入
Thread->ApcState.UserApcPending = TRUE;

// 插入到队列头部
InsertHeadList(&ApcState->ApcListHead[ApcMode],
&Apc->ApcListEntry);

} else {
// ApcMode == KernelMode || Apc->KernelRoutine != PsExitSpecialApc,比较简单,直接插入队列尾部
InsertTailList(&ApcState->ApcListHead[ApcMode],
&Apc->ApcListEntry);
}

} else {
// NormalRoutine 是 NULL ,走这里

// 从队尾开始遍历 APC 队列
// 直到找到下一个 NormalRoutine 为空的APC
ListEntry = ApcState->ApcListHead[ApcMode].Blink;
while (ListEntry != &ApcState->ApcListHead[ApcMode]) {
ApcEntry = CONTAINING_RECORD(ListEntry, KAPC, ApcListEntry);
if (ApcEntry->NormalRoutine == NULL) {
break;
}

ListEntry = ListEntry->Blink;
}

// 插入到队列头部
InsertHeadList(ListEntry, &Apc->ApcListEntry);
}

// 插入成功
Apc->Inserted = TRUE;

//
// If the APC index from the APC object matches the APC Index of
// the thread, then check to determine if the APC should interrupt
// thread execution or sequence the thread out of a wait state.
//

// 条件成立,APC 和当前线程使用同一个进程
// 要么没有attach,APC插入了所属进程
// 要么attach了,APC插入的也是挂靠的进程
if (Apc->ApcStateIndex == Thread->ApcStateIndex) {

//
// If the processor mode of the APC is kernel, then check if
// the APC should either interrupt the thread or sequence the
// thread out of a Waiting state. Else check if the APC should
// sequence the thread out of an alertable Waiting state.
//

if (ApcMode == KernelMode) {
// 标记已插入内核APC
Thread->ApcState.KernelApcPending = TRUE;

if (Thread->State == Running) {
// 如果线程正在运行,则 APC 中断
KiRequestApcInterrupt(Thread->NextProcessor);

} else if ((Thread->State == Waiting) && // 线程阻塞(等待)
(Thread->WaitIrql == 0) &&
((Apc->NormalRoutine == NULL) ||
((Thread->KernelApcDisable == 0) &&
(Thread->ApcState.KernelApcInProgress == FALSE)))) // 没有正在执行的 APC
{

// 修改线程状态为就绪,提升优先级
KiUnwaitThread(Thread, STATUS_KERNEL_APC, Increment, NULL);
}

} else if ((Thread->State == Waiting) && // 线程处于阻塞状态
(Thread->WaitMode == UserMode) && // 用户导致的阻塞
(Thread->Alertable || Thread->ApcState.UserApcPending)) // 是否可以被APC唤醒或者已经插入,SleepEx 可以设置 Alertable
{

Thread->ApcState.UserApcPending = TRUE;
// 修改线程状态为就绪,提升优先级
KiUnwaitThread(Thread, STATUS_USER_APC, Increment, NULL);
}
}

Inserted = TRUE;
}

//
// Return whether the APC object was inserted in an APC queue.
//

return Inserted;
}

4 练习

1、创建一个进程,使用Sleep()函数让其处于等待状态,然后向其APC队列插入APC,观察是否执行。
2、如何才能让上题中的APC函数执行?— SleepEx()WaitForSingleObjectEx()