首页
社区
课程
招聘
[原创]Windows内核学习笔记之线程(中)
2021-12-22 19:06 11779

[原创]Windows内核学习笔记之线程(中)

2021-12-22 19:06
11779

上篇:Windows内核学习笔记之线程(上)

四.Windows的APC机制

1.异步调用过程

普通的函数调用是阻塞的,直到子函数执行完毕和返回后,父函数才能继续执行。如果子函数所用的时间很久,那么这次函数调用就会导致线程阻塞在这个函数内部。对于典型的Windows GUI程序,如果这样的阻塞发生在UI线程中,那么便会导致界面无法更新,失去响应。为了避免这样的情况,Windows系统的很多API支持以异步的方式工作,它们所依赖的便是NT内核的异步过程调用(APC)机制。

 

APC是针对线程的,每个APC都是在特定的线程环境中运行的,每个线程都有字节特有的APC链表。同一个线程的APC也是被排队执行的。由于APC的IRQL为APC_LEVEL,会高于普通线程代码的PASSIVE_LEVEL。所以,当一个线程获得控制时,它的APC过程就会立刻被执行。这一特性使得APC非常适合于实现各种异步通知事件。例如,I/O的完成通知就是通过APC来实现的。

2.APC对象

APC是通过一种内核控制对象来表达的,称为APC对象,其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kd> dt _KAPC
nt!_KAPC
   +0x000 Type
   +0x002 Size
   +0x004 Spare0                               
   +0x008 Thread                              
   +0x00c ApcListEntry
   +0x014 KernelRoutine
   +0x018 RundownRoutine
   +0x01c NormalRoutine
   +0x020 NormalContext
   +0x024 SystemArgument1
   +0x028 SystemArgument2
   +0x02c ApcStateIndex
   +0x02d ApcMode
   +0x02e Inserted
名称 含义
Type 为KOBJECTS枚举类型的ApcObject
Size 等于KAPC结构的大小
Thread 指向此APC对象所在的线程ETHREAD
ApcListEntry APC对象被加入到线程APC链表中的节点对象
KernelRoutine 指向释放APC对象的函数指针
RundownRoutine 函数指针(可选参数),当一个线程终止时,如果它的APC链表中还有APC对象,若RundownRoutine非空,则调用它所指函数
NormalRoutine 如果是内核APC,指向要指向的函数,如果是用户APC指向了所有用户APC都会运行的函数
NormalContext 如果是内核APC该参数为NULL,如果是用户APC该参数就是真正要执行的用户APC函数
SystemArgument1 APC函数的参数
SystemArgument2 APC函数的参数
AppStateIndex 说明了APC对象的环境状态,它是KAPC_ENVIRONMENT枚举类型的成员,一旦APC对象被插入到线程的APC链表中,则ApcStateIndex指示了它位于线程ETHREAD对象的哪个APC链表中
ApcMode 为0表示这是一个内核APC,为1说明这是用户APC
Inserted 指示该APC是否已被插入到线程的APC链表中

3.APC的保存

APC对象要挂载到相应的线程对象链表中才会执行,在线程结构体ETHREAD中与APC对象比较有关联的成员有如下几个:

偏移 名称
0x034 ApcState
0x138 ApcStatePointer
0x014C SavedApcState
0x165 ApcStateIndex
 

ApcState和SaveApcState是KAPC_STATE类型的成员,ApcStatePointer是一个包含两个元素的指针数组,ApcStateIndex则是KAPC_ENVIRONMENT的枚举类型索引。当一个线程在它所属的进程中运行时,使用的APC链表是ApcState成员,此时ApcStatePointer[0]指向的就是ApcState,并且ApcStateIndex等于0。如果线程挂载(attach)到另一个进程中,那么,尚未交付的APC对象从ApcState转移到SaveApcState中,并且ApcStatePointer[0]指向的就是SaveApcState,ApcStatePointer[1]指向的就是ApcState,ApcStateIndex等于1。所以,挂载完成以后,ApcStateIndex知名了ApcState将包含的当前进程的信息,包括在新进程环境中要执行的APC对象。此后插入的APC对象都将进入到ApcState链表中。

 

等到线程回到它自己的进程(detach)时,KeDetachProcess首先让属于当前进程的APC对象,即ApcState中的APC对象被交付,然后,将SaveApcState中的APC转移到ApcState中,并且设置ApcStateIndex等于0。这样就恢复了该线程被挂载以前的状态。因此,ApcState总包含了要在当前进程环境中执行的APC对象;ApcStateIndex总是指向ApcState在指针数组ApcStatePointer中的下标值。APC对象的插入操作总数以ApcStateIndex为下标来访问数组ApcStatePointer中的元素。

 

KAPC_STATE和定义如下:

1
2
3
4
5
6
7
kd> dt _KAPC_STATE
nt!_KAPC_STATE
   +0x000 ApcListHead   
   +0x010 Process       
   +0x014 KernelApcInProgress   
   +0x015 KernelApcPending   
   +0x016 UserApcPending

KAPC_ENVIRONMENT枚举类型的定义如下:

1
2
3
4
5
6
typedef enum _KAPC_ENVIRONMENT{
    OriginalApcEnvironment,
    AttachedApcEnvironment,
    CurrentApcEnvironment,
    InsertApcEnvironment
}KAPC_ENVIRONMENT;

OriginalApcEnvironment表示原始的进程环境;AttachedApcEnvironment表示挂载以后的新进程环境;CurrentApcEnvironment表示在APC对象初始化时线程所属的进程环境;最后的InsertApcEnvironment表示在APC对象被插入时线程所属的进程环境。在APC_STATE结构中,Process域指向了一个进程对象,代表了这些APC所关联的进程,PsGetCurrentProcess函数会从该与域获取进程对象的原因就是无论是否是挂载状态,总能获取正确的进程对象

1
2
3
4
5
6
.text:0040ED86                 public _PsGetCurrentProcess@0
.text:0040ED86 _PsGetCurrentProcess@0 proc near
.text:0040ED86                 mov     eax, large fs:124h ; IoGetCurrentProcess
.text:0040ED8C                 mov     eax, [eax+_KTHREAD.ApcState.Process]
.text:0040ED8F                 retn
.text:0040ED8F _PsGetCurrentProcess@0 endp

ApcListHead数组有两个元素,分别代表了内核模式APC对象链表和用户模式APC对象链表。三个布尔变量KernelApcInProcess, KernelApcPending和UserApcPending分别表示:该线程当前正在处理一个内核APC,有内核模式APC对象正在等待被交付,以及有用户模式APC对象正在等待被交付。

4.APC机制的实现

A.系统调用NtQueueApcThread

不仅内核可以向一个线程发出APC请求,别的线程乃至目标线程自身也可以发出这样的请求。Win32 API为应用程序提供了一个函数QueueUserAPC就是用于此目的,而该函数是通过调用内核的NtQueueApcThread来实现的,第三个参数为函数BaesDispatchAPC函数地址,所以当从用户层插入APC的时候NormalRoutine被指定为BaseDispatchAPC,而用户真正要指向的函数则用第三个参数NormalContext传递进去

1
2
3
4
5
6
7
8
9
10
11
.text:7C82C0CF loc_7C82C0CF:                           ; CODE XREF: QueueUserAPC(x,x,x)+18B79↓j
.text:7C82C0CF                 push    eax             ; SystemArgument2
.text:7C82C0D0                 push    [ebp+dwData]    ; SystemArgument1
.text:7C82C0D3                 push    [ebp+pfnAPC]    ; NormalContext
.text:7C82C0D6                 push    offset _BaseDispatchAPC@12 ; NormalRoutine
.text:7C82C0DB                 push    [ebp+hThread]   ; ThreadHandle
.text:7C82C0DE                 call    ds:__imp__NtQueueApcThread@20 ; NtQueueApcThread(x,x,x,x,x)
.text:7C82C0E4                 xor     ecx, ecx
.text:7C82C0E6                 test    eax, eax
.text:7C82C0E8                 setnl   cl
.text:7C82C0EB                 mov     eax, ecx

在NtQueueApcThread函数中,首先将线程的先前模式保存在局部变量中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PAGE:004C2AAA ; NTSTATUS __stdcall NtQueueApcThread(HANDLE ThreadHandle, PKNORMAL_ROUTINE ApcRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
PAGE:004C2AAA _NtQueueApcThread@20 proc near          ; DATA XREF: .text:0040DAF0↑o
PAGE:004C2AAA
PAGE:004C2AAA AccessMode      = byte ptr -4
PAGE:004C2AAA ThreadHandle    = dword ptr  8
PAGE:004C2AAA ApcRoutine      = dword ptr  0Ch
PAGE:004C2AAA NormalContext   = dword ptr  10h
PAGE:004C2AAA SystemArgument1 = dword ptr  14h
PAGE:004C2AAA SystemArgument2 = dword ptr  18h
PAGE:004C2AAA
PAGE:004C2AAA ; FUNCTION CHUNK AT PAGE:0052E6D5 SIZE 00000025 BYTES
PAGE:004C2AAA
PAGE:004C2AAA                 mov     edi, edi
PAGE:004C2AAC                 push    ebp
PAGE:004C2AAD                 mov     ebp, esp
PAGE:004C2AAF                 push    ecx
PAGE:004C2AB0                 push    ebx
PAGE:004C2AB1                 push    esi
PAGE:004C2AB2                 mov     eax, large fs:124h ; 将线程对象赋给eax
PAGE:004C2AB8                 mov     al, [eax+_KTHREAD.PreviousMode]
PAGE:004C2ABE                 mov     [ebp+AccessMode], al ; 为局部变量赋值

调用函数来获得线程句柄的线程对象

1
2
3
4
5
6
7
8
9
10
11
12
PAGE:004C2AC1                 xor     esi, esi        ; esi清0
PAGE:004C2AC3                 push    esi             ; HandleInformation
PAGE:004C2AC4                 lea     eax, [ebp+Object]
PAGE:004C2AC7                 push    eax             ; Object
PAGE:004C2AC8                 push    dword ptr [ebp+AccessMode] ; AccessMode
PAGE:004C2ACB                 push    _PsThreadType   ; ObjectType
PAGE:004C2AD1                 push    10h             ; DesiredAccess
PAGE:004C2AD3                 push    [ebp+ThreadHandle] ; Handle
PAGE:004C2AD6                 call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
PAGE:004C2ADB                 mov     ebx, eax
PAGE:004C2ADD                 cmp     ebx, esi
PAGE:004C2ADF                 jl      short loc_4C2B42

对解析到的线程对象的CrossThreadFlags进行检测

1
2
3
4
PAGE:004C2AE1                 mov     eax, [ebp+Object]
PAGE:004C2AE4                 xor     ebx, ebx
PAGE:004C2AE6                 test    byte ptr [eax+_ETHREAD.___u24.CrossThreadFlags], OBJ_PERMANENT
PAGE:004C2AED                 jnz     loc_52E6D5

如果通过检测,接下来就调用函数来申请0x30大小的内存用来保存APC对象

1
2
3
4
5
6
7
8
PAGE:004C2AF3                 push    edi
PAGE:004C2AF4                 push    'pasP'          ; Tag
PAGE:004C2AF9                 push    30h             ; NumberOfBytes
PAGE:004C2AFB                 push    8               ; PoolType
PAGE:004C2AFD                 call    _ExAllocatePoolWithQuotaTag@12 ; ExAllocatePoolWithQuotaTag(x,x,x)
PAGE:004C2B02                 mov     edi, eax        ; 将申请到的内存地址赋给edi
PAGE:004C2B04                 cmp     edi, esi
PAGE:004C2B06                 jz      loc_52E6DF

通过函数来初始化APC对象

1
2
3
4
5
6
7
8
9
PAGE:004C2B0C                 push    [ebp+NormalContext] ; NormalContext
PAGE:004C2B0F                 push    1               ; ApcMode
PAGE:004C2B11                 push    [ebp+ApcRoutine] ; NormalRoutine
PAGE:004C2B14                 push    esi             ; RundownRoutine
PAGE:004C2B15                 push    offset _IopDeallocateApc@20 ; KernelRoutine
PAGE:004C2B1A                 push    esi             ; int
PAGE:004C2B1B                 push    [ebp+ThreadHandle] ; Thread
PAGE:004C2B1E                 push    edi             ; Apc
PAGE:004C2B1F                 call    _KeInitializeApc@32

将初始化完成的APC对象插入到线程中

1
2
3
4
5
6
7
PAGE:004C2B24                 push    esi             ; Increment
PAGE:004C2B25                 push    [ebp+SystemArgument2] ; SystemArgument2
PAGE:004C2B28                 push    [ebp+SystemArgument1] ; SystemArgument1
PAGE:004C2B2B                 push    edi             ; Apc
PAGE:004C2B2C                 call    _KeInsertQueueApc@16 ; KeInsertQueueApc(x,x,x,x)
PAGE:004C2B31                 test    al, al
PAGE:004C2B33                 jz      loc_52E6E9

最后退出函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PAGE:004C2B39 loc_4C2B39:                             ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+6BC3A↓j
PAGE:004C2B39                                         ; NtQueueApcThread(x,x,x,x,x)+6BC4B↓j
PAGE:004C2B39                 pop     edi
PAGE:004C2B3A
PAGE:004C2B3A loc_4C2B3A:                             ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+6BC30↓j
PAGE:004C2B3A                 mov     ecx, [ebp+ThreadHandle] ; Object
PAGE:004C2B3D                 call    @ObfDereferenceObject@4 ; ObfDereferenceObject(x)
PAGE:004C2B42
PAGE:004C2B42 loc_4C2B42:                             ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+35↑j
PAGE:004C2B42                 pop     esi
PAGE:004C2B43                 mov     eax, ebx
PAGE:004C2B45                 pop     ebx
PAGE:004C2B46                 leave
PAGE:004C2B47                 retn    14h
PAGE:004C2B47 _NtQueueApcThread@20 endp

因此可以得出结论,向一个线程插入APC对象的之前需要申请一块0x30大小的内存,这块内存用来保存要插入的APC的对象,在调用KeInitializeApc的时候,指定了KernelApc的函数地址是IopDeallocateApc,根据该函数反汇编的结果可以得知,该函数的作用就是将申请到的保存APC对象的内存空间释放掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PAGE:0049F852 ; int __stdcall IopDeallocateApc(PVOID P, int, int, int, int)
PAGE:0049F852 _IopDeallocateApc@20 proc near          ; DATA XREF: IoRaiseHardError(x,x,x)+16070↑o
PAGE:0049F852                                         ; IoRaiseInformationalHardError(x,x,x)+123↑o ...
PAGE:0049F852
PAGE:0049F852 P               = dword ptr  8
PAGE:0049F852
PAGE:0049F852                 mov     edi, edi
PAGE:0049F854                 push    ebp
PAGE:0049F855                 mov     ebp, esp
PAGE:0049F857                 push    0               ; Tag
PAGE:0049F859                 push    [ebp+P]         ; P
PAGE:0049F85C                 call    _ExFreePoolWithTag@8 ; ExFreePoolWithTag(x,x)
PAGE:0049F861                 pop     ebp
PAGE:0049F862                 retn    14h
PAGE:0049F862 _IopDeallocateApc@20 endp

在内核中,初始化APC对象是通过KeInitializeApc函数来实现的,而插入APC对象则是通过函数KeInsertQueueApc来实现的,KiDeliverApc则是用来交付插入的APC

B.APC的初始化

在KeInitializeApc函数中首先对APC对象的类型和大小赋值,判断环境是否等于2,也就是是否是在CurrentApcEnvironment环境中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:0040EBD9 ; int __stdcall KeInitializeApc(PRKAPC Apc, PRKTHREAD Thread, int, PKKERNEL_ROUTINE KernelRoutine, PKRUNDOWN_ROUTINE RundownRoutine, PKNORMAL_ROUTINE NormalRoutine, KPROCESSOR_MODE ApcMode, PVOID NormalContext)
.text:0040EBD9                 public _KeInitializeApc@32
.text:0040EBD9 _KeInitializeApc@32 proc near           ; CODE XREF: NtSetTimer(x,x,x,x,x,x,x)+12F↓p
.text:0040EBD9                                         ; IopfCompleteRequest(x,x)+11581↓p ...
.text:0040EBD9
.text:0040EBD9 Apc             = dword ptr  8
.text:0040EBD9 Thread          = dword ptr  0Ch
.text:0040EBD9 Environment     = dword ptr  10h
.text:0040EBD9 KernelRoutine   = dword ptr  14h
.text:0040EBD9 RundownRoutine  = dword ptr  18h
.text:0040EBD9 NormalRoutine   = dword ptr  1Ch
.text:0040EBD9 ApcMode         = byte ptr  20h
.text:0040EBD9 NormalContext   = dword ptr  24h
.text:0040EBD9
.text:0040EBD9                 mov     edi, edi
.text:0040EBDB                 push    ebp
.text:0040EBDC                 mov     ebp, esp
.text:0040EBDE                 mov     eax, [ebp+Apc]  ; 将APC赋给eax
.text:0040EBE1                 mov     edx, [ebp+Environment] ; 将APC环境赋给edx
.text:0040EBE4                 cmp     edx, 2          ; 判断APC环境是否为CurrentApcEnvironment
.text:0040EBE7                 mov     ecx, [ebp+Thread] ; 将线程对象赋给ecx
.text:0040EBEA                 mov     [eax+KAPC.Type], ApcObject ; 为APC类型赋值
.text:0040EBEF                 mov     [eax+KAPC.Size], 30h ; 为结构体的大小赋值
.text:0040EBF5                 jz      loc_40FB78

如果处在CurrentApcEnvironment环境中,则将dl赋值为当前线程的ApcStateIndex

1
2
3
.text:0040FB78 loc_40FB78:                             ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+1C↑j
.text:0040FB78                 mov     dl, [ecx+_KTHREAD.ApcStateIndex]
.text:0040FB7E                 jmp     loc_40EBFB

使用传入的参数为APC对象中的Thread, KernelRoutine, RundownRoutine赋值,使用dl的值为APC对象中的ApcStateIndex赋值

1
2
3
4
5
6
7
.text:0040EBFB loc_40EBFB:                             ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+FA5↓j
.text:0040EBFB                 mov     [eax+KAPC.Thread], ecx ; 为APC对象所在的线程对象KTHREAD对象赋值
.text:0040EBFE                 mov     ecx, [ebp+KernelRoutine]
.text:0040EC01                 mov     [eax+KAPC.KernelRoutine], ecx ; 为KernelRoutine对象赋值
.text:0040EC04                 mov     ecx, [ebp+RundownRoutine]
.text:0040EC07                 mov     [eax+KAPC.ApcStateIndex], dl ; 将dl赋值给ApcStateIndex
.text:0040EC0A                 mov     [eax+KAPC.RundownRoutine], ecx

判断传入的NormalRoutine是否为NULL

1
2
3
4
5
.text:0040EC0D                 mov     ecx, [ebp+NormalRoutine]
.text:0040EC10                 xor     edx, edx        ; edx清0
.text:0040EC12                 cmp     ecx, edx
.text:0040EC14                 mov     [eax+KAPC.NormalRoutine], ecx ; 判断NormalRoutine是否为NULL
.text:0040EC17                 jnz     loc_40EDEF

如果不为NULL,则会用传入的ApcMode和NormalContext为APC对象中的ApcMode和NormalContext赋值

1
2
3
4
5
6
.text:0040EDEF loc_40EDEF:                             ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+3E↑j
.text:0040EDEF                 mov     cl, [ebp+ApcMode]
.text:0040EDF2                 mov     [eax+KAPC.ApcMode], cl
.text:0040EDF5                 mov     ecx, [ebp+NormalContext]
.text:0040EDF8                 mov     [eax+KAPC.NormalContext], ecx
.text:0040EDFB                 jmp     loc_40EC23

如果为NULL,则将APC对象中的ApcMode和NormalContext赋值为寄存器edx中保存的0

1
2
.text:0040EC1D                 mov     [eax+_KAPC.ApcMode], dl
.text:0040EC20                 mov     [eax+KAPC.NormalContext], edx

将APC对象中的Inserted赋值为0,代表APC对象还未插入到APC链表中

1
2
3
4
5
.text:0040EC23 loc_40EC23:                             ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+222↓j
.text:0040EC23                 mov     [eax+KAPC.Inserted], dl ; Insert赋值为0,代表还未插入到APC链表中
.text:0040EC26                 pop     ebp
.text:0040EC27                 retn    20h
.text:0040EC27 _KeInitializeApc@32 endp

C.APC的插入

在KeInsertQueueApc中首先要先获取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
.text:0040EC2F ; int __stdcall KeInsertQueueApc(PRKAPC Apc, PVOID SystemArgument1, PVOID SystemArgument2, KPRIORITY Increment)
.text:0040EC2F                 public _KeInsertQueueApc@16
.text:0040EC2F _KeInsertQueueApc@16 proc near          ; CODE XREF: ExpTimerDpcRoutine(x,x,x,x)+30↓p
.text:0040EC2F                                         ; IopfCompleteRequest(x,x)+11528↓p ...
.text:0040EC2F
.text:0040EC2F LockHandle      = _KLOCK_QUEUE_HANDLE ptr -0Ch
.text:0040EC2F Apc             = dword ptr  8
.text:0040EC2F SystemArgument1 = dword ptr  0Ch
.text:0040EC2F SystemArgument2 = dword ptr  10h
.text:0040EC2F Increment       = dword ptr  14h
.text:0040EC2F
.text:0040EC2F                 mov     edi, edi
.text:0040EC31                 push    ebp
.text:0040EC32                 mov     ebp, esp
.text:0040EC34                 sub     esp, 0Ch
.text:0040EC37                 push    ebx
.text:0040EC38                 push    esi             ; Increment
.text:0040EC39                 push    edi             ; Apc
.text:0040EC3A                 mov     edi, [ebp+Apc]
.text:0040EC3D                 mov     esi, [edi+_KAPC.Thread]
.text:0040EC40                 lea     ecx, [esi+_KTHREAD.ApcQueueLock]
.text:0040EC46                 lea     edx, [ebp+LockHandle]
.text:0040EC49                 call    ds:__imp_@KeAcquireInStackQueuedSpinLockRaiseToSynch@8 ; KeAcquireInStackQueuedSpinLockRaiseToSynch(x,x)
.text:0040EC4F                 mov     eax, large fs:20h
.text:0040EC55                 lea     ecx, [eax+418h]
.text:0040EC5B                 call    @KeAcquireQueuedSpinLockAtDpcLevel@4

判断线程的ApcQueueable是否为0,即是否可以插入APC对象

1
2
3
.text:0040EC60                 xor     bl, bl          ; bl清0
.text:0040EC62                 cmp     [esi+_KTHREAD.ApcQueueable], bl ; 判断ApcQueueable是否为0
.text:0040EC68                 jz      short loc_40EC82

如果可以插入,就调用KiInsertQueueApc将APC对象插入到相应的线程对象中,注意在调用之前将KAPC对象的地址赋给了ecx

1
2
3
4
5
6
7
8
.text:0040EC6A                 mov     eax, [ebp+SystemArgument1]
.text:0040EC6D                 mov     edx, [ebp+Increment]
.text:0040EC70                 mov     [edi+KAPC.SystemArgument1], eax
.text:0040EC73                 mov     eax, [ebp+SystemArgument2]
.text:0040EC76                 mov     ecx, edi
.text:0040EC78                 mov     [edi+KAPC.SystemArgument2], eax
.text:0040EC7B                 call    @KiInsertQueueApc@8 ; KiInsertQueueApc(x,x)
.text:0040EC80                 mov     bl, al          ; 将返回值赋给bl

最后释放掉APC锁

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:0040EC82 loc_40EC82:                             ; CODE XREF: KeInsertQueueApc(x,x,x,x)+39↑j
.text:0040EC82                 mov     eax, large fs:20h
.text:0040EC88                 lea     ecx, [eax+418h]
.text:0040EC8E                 call    @KeReleaseQueuedSpinLockFromDpcLevel@4 ; KeReleaseQueuedSpinLockFromDpcLevel(x)
.text:0040EC93                 lea     ecx, [ebp+LockHandle] ; LockHandle
.text:0040EC96                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
.text:0040EC9C                 pop     edi
.text:0040EC9D                 pop     esi
.text:0040EC9E                 mov     al, bl          ; 用bl赋值给al作为返回值
.text:0040ECA0                 pop     ebx
.text:0040ECA1                 leave
.text:0040ECA2                 retn    10h
.text:0040ECA2 _KeInsertQueueApc@16 endp

所以正在将APC对象插入到线程对象中是通过KiInsertQueueApc来实现的,继续看该函数的实现,函数首先将ecx赋给eax,因为ecx此时保存的是APC对象的地址,所以经过赋值以后eax保存的是APC对象的地址,判断APC的ApcStateIndex是否等于3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:00402C6A ; int __cdecl KiInsertQueueApc(PKAPC Apc, KPRIORITY Increment)
.text:00402C6A @KiInsertQueueApc@8 proc near           ; CODE XREF: KeInsertQueueApc(x,x,x,x)+4C↓p
.text:00402C6A                                         ; KeSuspendThread(x)+68↓p ...
.text:00402C6A
.text:00402C6A var_Increament  = dword ptr -4
.text:00402C6A Apc             = dword ptr  8
.text:00402C6A Increment       = dword ptr  0Ch
.text:00402C6A
.text:00402C6A                 mov     edi, edi
.text:00402C6C                 push    ebp
.text:00402C6D                 mov     ebp, esp
.text:00402C6F                 push    ecx
.text:00402C70                 mov     eax, ecx
.text:00402C72                 cmp     [eax+KAPC.Inserted], 0 ; APC是否已经插入到线程中
.text:00402C76                 mov     ecx, [eax+_KAPC.Thread] ; 将线程对象赋给ecx
.text:00402C79                 mov     [ebp+var_Increament], edx
.text:00402C7C                 jnz     loc_44B3BF
.text:00402C82                 cmp     [eax+KAPC.ApcStateIndex], 3 ; ApcStateIndex是否等于3
.text:00402C86                 jz      loc_44B3C3

如果等于3则会将线程的ApcStateIndex赋给KAPC的ApcStateIndex

1
2
3
4
.text:0044B3C3 loc_44B3C3:                             ; CODE XREF: KiInsertQueueApc(x,x)+1C↑j
.text:0044B3C3                 mov     dl, [ecx+_KTHREAD.ApcStateIndex]
.text:0044B3C9                 mov     [eax+KAPC.ApcStateIndex], dl
.text:0044B3CC                 jmp     loc_402C8C

通过KAPC中的ApcStateIndex从线程对象中获取相应的APC_STATE,判断NormalRoutine是否为NULL

1
2
3
4
5
6
7
8
9
.text:00402C8C loc_402C8C:                             ; CODE XREF: KiInsertQueueApc(x,x)+48762↓j
.text:00402C8C                 cmp     [eax+KAPC.NormalRoutine], 0 ; NormalRoutine是否为NULL
.text:00402C90                 movsx   edx, [eax+KAPC.ApcStateIndex] ; 将ApcStateIndex赋给edx
.text:00402C94                 push    ebx
.text:00402C95                 push    esi
.text:00402C96                 push    edi
.text:00402C97                 mov     edi, [ecx+edx*4+_KTHREAD.ApcStatePointer] ; 将相应的KAPC_STATE赋给edi
.text:00402C9E                 mov     dl, [eax+KAPC.ApcMode] ; 将ApcMode赋给dl
.text:00402CA1                 jnz     loc_41049E

如果不为NULL,获取KAPC_STATE地址赋给edi,根据ApcMode从KAPC_STATE队列中的将上一个APC赋给esi

1
2
3
.text:00402CA7                 movsx   esi, dl         ; 将ApcMode赋给esi
.text:00402CAA                 lea     edi, [edi+esi*8] ; 将KAPC_STATE地址赋给edi
.text:00402CAD                 mov     esi, [edi+LIST_ENTRY.Blink] ; 取出队列中的上一个APC

判断esi与edi是否相等

1
2
3
.text:00402CB0 loc_402CB0:                             ; CODE XREF: KiInsertQueueApc(x,x)+4876A↓j
.text:00402CB0                 cmp     esi, edi        ; 判断上一个APC是否等于线程APC_STATE地址
.text:00402CB2                 jnz     loc_42615A

如果不相等,则判断KPAC队列的下一个KPAC对象是否为0

1
2
3
4
.text:0042615A loc_42615A:                             ; CODE XREF: KiInsertQueueApc(x,x)+48↑j
.text:0042615A                 cmp     [esi+KAPC.ApcListEntry.Blink], 0
.text:0042615E                 jz      loc_402CB8
.text:00426164                 jmp     loc_44B3D1

如果不为NULL,则将esi指向下一个KAPC,并跳转到上面的loc_402CB0执行

1
2
3
.text:0044B3D1 loc_44B3D1:                             ; CODE XREF: KiInsertQueueApc(x,x)+234FA↑j
.text:0044B3D1                 mov     esi, [esi+LIST_ENTRY.Blink]
.text:0044B3D4                 jmp     loc_402CB0

所以这个过程就是在从线程的KAPC_STATE中获取APC队列的最后一个KAPC对象,当esi执行队列中的最后一个KAPC对象的时候,就将要插入的APC对象插入到APC队列中

1
2
3
4
5
6
7
.text:00402CB8 loc_402CB8:                             ; CODE XREF: KiInsertQueueApc(x,x)+234F4↓j
.text:00402CB8                 mov     ebx, [esi+LIST_ENTRY.Flink]
.text:00402CBA                 lea     edi, [eax+_KAPC.ApcListEntry]
.text:00402CBD                 mov     [edi+LIST_ENTRY.Flink], ebx
.text:00402CBF                 mov     [edi+LIST_ENTRY.Blink], esi
.text:00402CC2                 mov     [ebx+LIST_ENTRY.Blink], edi
.text:00402CC5                 mov     [esi+LIST_ENTRY.Flink], edi

如果NormalRoutine为NULL,此时的edx保存的是ApcMode,所以会判断ApcMode是否为0,如果不为0,接着判断APC对象的KernelRoutine是否为PsExitSpecialApc

1
2
3
4
5
.text:0041049E loc_41049E:                             ; CODE XREF: KiInsertQueueApc(x,x)+37↑j
.text:0041049E                 test    dl, dl          ; 判断ApcMode是否为0
.text:004104A0                 jz      short loc_4104AF
.text:004104A2                 cmp     [eax+KAPC.KernelRoutine], offset _PsExitSpecialApc@20 ; PsExitSpecialApc(x,x,x,x,x)
.text:004104A9                 jz      loc_42950C

如果ApcMode为0或者当它不为0但是KernelRoutine不为PsExitSpecialApc的时候,将APC对象插入到线程APC队列的尾部

1
2
3
4
5
6
7
8
9
10
.text:004104AF loc_4104AF:                             ; CODE XREF: KiInsertQueueApc(x,x)+D836↑j
.text:004104AF                 movsx   ebx, dl         ; 将ApcMode赋给ebx
.text:004104B2                 lea     edi, [edi+ebx*8] ; 取出KAPC_STATE地址赋给edi
.text:004104B5                 mov     ebx, [edi+LIST_ENTRY.Blink] ; 将KAPC_STATE的上一个APC对象赋给ebx
.text:004104B8                 lea     esi, [eax+KAPC.ApcListEntry] ; 将KAPC的ApcListEntry地址赋给esi
.text:004104BB                 mov     [esi+LIST_ENTRY.Flink], edi
.text:004104BD                 mov     [esi+LIST_ENTRY.Blink], ebx
.text:004104C0                 mov     [ebx+LIST_ENTRY.Flink], esi
.text:004104C2                 mov     [edi+LIST_ENTRY.Blink], esi
.text:004104C5                 jmp     loc_402CC7

如果ApcMode不为0且APC对象的KernelRoutine为PsExitSpecialApc的时候,依然会把APC对象插入到APC队列中,只是此时的线程对象的ApcState的UserApcPending赋值为1

1
2
3
4
5
6
7
8
9
10
11
.text:0042950C loc_42950C:                             ; CODE XREF: KiInsertQueueApc(x,x)+D83F↑j
.text:0042950C                 movsx   ebx, dl         ; 将ApcMode赋给ebx
.text:0042950F                 mov     [ecx+_KTHREAD.ApcState.UserApcPending], 1
.text:00429513                 lea     edi, [edi+ebx*8]
.text:00429516                 mov     ebx, [edi+LIST_ENTRY.Flink]
.text:00429518                 lea     esi, [eax+KAPC.ApcListEntry]
.text:0042951B                 mov     [esi+LIST_ENTRY.Flink], ebx
.text:0042951D                 mov     [esi+LIST_ENTRY.Blink], edi
.text:00429520                 mov     [ebx+LIST_ENTRY.Blink], esi
.text:00429523                 mov     [edi+LIST_ENTRY.Flink], esi
.text:00429525                 jmp     loc_402CC7

当APC对象插入到线程的APC队列以后,会将APC对象的Inserted赋值为1代表APC对象插入到线程中,将KAPC的ApcStateIndex和线程的ApcStateIndex分别赋给edi和esi,判断edi和esi是否相等

1
2
3
4
5
6
7
8
9
10
.text:00402CC7 loc_402CC7:                             ; CODE XREF: KiInsertQueueApc(x,x)+D85B↓j
.text:00402CC7                                         ; KiInsertQueueApc(x,x)+268BB↓j
.text:00402CC7                 movsx   edi, [eax+KAPC.ApcStateIndex]
.text:00402CCB                 mov     [eax+KAPC.Inserted], 1
.text:00402CCF                 movzx   esi, [ecx+_KTHREAD.ApcStateIndex]
.text:00402CD6                 cmp     edi, esi
.text:00402CD8                 pop     edi
.text:00402CD9                 pop     esi
.text:00402CDA                 pop     ebx
.text:00402CDB                 jnz     short loc_402D12

如果相等继续判断ApcMode是否为0

1
2
.text:00402CDD                 test    dl, dl
.text:00402CDF                 jnz     loc_4106A4

如果为0,则会将KernelApcPending赋值为1

1
2
3
4
.text:00402CE5                 mov     dl, [ecx+_KTHREAD.State]
.text:00402CE8                 cmp     dl, 2
.text:00402CEB                 mov     [ecx+_KTHREAD.ApcState.KernelApcPending], 1
.text:00402CEF                 jnz     loc_40EEB3

如果不为0,则会继续判断线程的State, WaitMode, Alertable

1
2
3
4
5
6
7
.text:004106A4 loc_4106A4:                             ; CODE XREF: KiInsertQueueApc(x,x)+75↑j
.text:004106A4                 cmp     [ecx+_KTHREAD.State], 5
.text:004106A8                 jnz     loc_402D12
.text:004106AE                 cmp     [ecx+_KTHREAD.WaitMode], 1
.text:004106B2                 jnz     loc_402D12
.text:004106B8                 cmp     [ecx+_KTHREAD.Alertable], 0
.text:004106BF                 jz      loc_432270

如果都符合条件,就会将线程的UserApcPending赋值为1

1
2
3
4
5
.text:004106C5 loc_4106C5:                             ; CODE XREF: KiInsertQueueApc(x,x)+2F610↓j
.text:004106C5                 mov     [ecx+_KTHREAD.ApcState.UserApcPending], 1
.text:004106C9                 push    0
.text:004106CB                 mov     edx, 0C0h
.text:004106D0                 jmp     loc_40EED7

D.APC的交付

APC对象成功插入到线程链表以后,在以下的几种情况下会执行APC的交付

  • 当内核代码离开一个临界区或守护区,也就是在调用KeLevelGuardedRegion或KeLeaveCritiicalRegion时,通过KiCheckForKernelApcDeliver函数直接调用KiDeleverApc,或者调用KiRequestSoftwareInterrupt函数请求一个APC_LEVEL的软件中断。这是因为,当线程进入临界区或守护区时,内核模式APC被禁止了,所以,当离开时,KiCheckForKernelApcDelivery函数被调用,以便即使交付内核模式APC

  • 当一个线程经过一次线程切换而获得控制权时,如果内核模式APC需要被交付,则在函数KiSwapThread函数返回以前,调用KiDeliverApc函数交付该内核模式APC

  • 当系统服务或异常处理函数返回用户模式时,KiDeliverApc函数被调用以便交付用户模式APC

  • 在APC_LEVEL软件中断发生时,HAL模块中的软件中断处理函数(HalpDispatchSoftwareInterrupt)调用KIDeliverApc交付内核模式APC。当内核代码调用KeLowerIrql函数降低IRQL到PASSIVE_LEVEL时,KiDeliverApc函数也会被调用

在交付函数KiDeliverApc中,首先对局部变量进行赋值,获得APC锁,将进程对象ETHREAD中的KernelApcPending赋值为0,代表线程没有要执行的内核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
.text:00405E01 ; int __stdcall KiDeliverApc(KPROCESSOR_MODE PreviousMode, int, int)
.text:00405E01                 public _KiDeliverApc@12
.text:00405E01 _KiDeliverApc@12 proc near              ; CODE XREF: KiUnlockDispatcherDatabase(x)+CA↑p
.text:00405E01                                         ; _KiServiceExit+54↓p ...
.text:00405E01
.text:00405E01 LockHandle      = _KLOCK_QUEUE_HANDLE ptr -28h
.text:00405E01 var_TrapFrame   = dword ptr -1Ch
.text:00405E01 var_EPROCESS    = dword ptr -18h
.text:00405E01 var_KernelRoutine= dword ptr -14h
.text:00405E01 NormalContext   = dword ptr -10h
.text:00405E01 SystemArgument1 = dword ptr -0Ch
.text:00405E01 SystemArgument2 = dword ptr -8
.text:00405E01 NormalRoutine   = dword ptr -4
.text:00405E01 PreviousMode    = byte ptr  8
.text:00405E01 ExceptionFrame  = dword ptr  0Ch
.text:00405E01 TrapFrame       = dword ptr  10h
.text:00405E01
.text:00405E01                 mov     edi, edi
.text:00405E03                 push    ebp
.text:00405E04                 mov     ebp, esp
.text:00405E06                 sub     esp, 28h
.text:00405E09                 mov     ecx, [ebp+TrapFrame] ; 将TrapFrame地址赋给ecx
.text:00405E0C                 test    ecx, ecx
.text:00405E0E                 jz      short loc_405E20
.text:00405E10                 mov     edx, [ecx+68h]  ; 从TrapFrame中取出eip赋给edx
.text:00405E13                 mov     eax, offset _ExpInterlockedPopEntrySListResume@0 ; ExpInterlockedPopEntrySListResume()
.text:00405E18                 cmp     edx, eax
.text:00405E1A                 jnb     loc_41137B
.text:00405E20
.text:00405E20 loc_405E20:                             ; CODE XREF: KiDeliverApc(x,x,x)+D↑j
.text:00405E20                                         ; KiDeliverApc(x,x,x)+B580↓j ...
.text:00405E20                 push    ebx
.text:00405E21                 push    esi
.text:00405E22                 push    edi
.text:00405E23                 mov     eax, large fs:124h ; 将线程对象地址赋给eax
.text:00405E29                 mov     esi, eax        ; 将线程对象地址赋给edi
.text:00405E2B                 mov     eax, [esi+_KTHREAD.TrapFrame]
.text:00405E31                 mov     [ebp+var_TrapFrame], eax
.text:00405E34                 mov     eax, [esi+_KTHREAD.ApcState.Process]
.text:00405E37                 mov     [esi+_KTHREAD.TrapFrame], ecx
.text:00405E3D                 lea     ecx, [esi+_KTHREAD.ApcQueueLock] ; SpinLock
.text:00405E43                 lea     edx, [ebp+LockHandle] ; LockHandle
.text:00405E46                 mov     [ebp+var_EPROCESS], eax
.text:00405E49                 call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
.text:00405E4F                 mov     [esi+_KTHREAD.ApcState.KernelApcPending], 0 ; 将KernelApcPending赋值为0

判断ApcState第一个链表是否有成员,也就是是否有内核APC,loc_45E56这个地址到后面执行完内核APC以后会在跳上来,通过这种方式就能遍历链表将内核APC都执行完毕

1
2
3
4
5
6
.text:00405E53                 lea     ebx, [esi+_KTHREAD.ApcState]
.text:00405E56
.text:00405E56 loc_405E56:                             ; CODE XREF: KiDeliverApc(x,x,x)+14FDD↓j
.text:00405E56                                         ; KiDeliverApc(x,x,x)+15E53↓j ...
.text:00405E56                 cmp     [ebx+LIST_ENTRY.Flink], ebx
.text:00405E58                 jnz     loc_41BBEF

如果有内核APC,则从链表中将其取出,由于挂载到线程链表中的APC对象是通过偏移0x0C的ApcListEntry来连接的,所以需要将线程链表中获取的APC对象地址减去0x0C才可以得到APC对象的真正地址

1
2
3
.text:0041BBEF loc_41BBEF:                             ; CODE XREF: KiDeliverApc(x,x,x)+57↑j
.text:0041BBEF                 mov     eax, [ebx+LIST_ENTRY.Flink] ; 获取内核APC链表中的APC对象
.text:0041BBF1                 lea     edi, [eax-0Ch]  ; 获取APC对象的地址

将成员赋值到局部变量,判断NormalRoutine是否为NULL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:0041BBEF loc_41BBEF:                             ; CODE XREF: KiDeliverApc(x,x,x)+57↑j
.text:0041BBEF                 mov     eax, [ebx+LIST_ENTRY.Flink] ; 获取内核APC链表中的APC对象
.text:0041BBF1                 lea     edi, [eax-0Ch]  ; 获取APC对象的地址
.text:0041BBF4                 mov     ecx, [edi+KAPC.KernelRoutine]
.text:0041BBF7                 mov     [ebp+var_KernelRoutine], ecx
.text:0041BBFA                 mov     ecx, [edi+KAPC.NormalRoutine]
.text:0041BBFD                 test    ecx, ecx        ; 判断NormalRoutine是否为NULL
.text:0041BBFF                 mov     [ebp+NormalRoutine], ecx
.text:0041BC02                 mov     edx, [edi+KAPC.NormalContext]
.text:0041BC05                 mov     [ebp+NormalContext], edx
.text:0041BC08                 mov     edx, [edi+KAPC.SystemArgument1]
.text:0041BC0B                 mov     [ebp+SystemArgument1], edx
.text:0041BC0E                 mov     edx, [edi+KAPC.SystemArgument2]
.text:0041BC11                 mov     [ebp+SystemArgument2], edx
.text:0041BC14                 jnz     loc_41AD60

如果NormalRoutine为NULL,则将APC对象从链表中删除,清空Inserted成员,释放APC锁并通过调用保存在KernelRoutine中的函数来释放APC对象的内存空间,最后跳转到loc_405E56继续判断是否有下一个内核APC需要执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:0041BC1A                 mov     ecx, [eax+LIST_ENTRY.Flink] ; 将APC对象从链表中删除
.text:0041BC1C                 mov     eax, [eax+LIST_ENTRY.Blink]
.text:0041BC1F                 mov     [eax+LIST_ENTRY.Flink], ecx
.text:0041BC21                 mov     [ecx+LIST_ENTRY.Blink], eax
.text:0041BC24                 lea     ecx, [ebp+LockHandle] ; LockHandle
.text:0041BC27                 mov     [edi+KAPC.Inserted], 0 ; 将Inserted赋值为0
.text:0041BC2B                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
.text:0041BC31                 lea     eax, [ebp+SystemArgument2]
.text:0041BC34                 push    eax
.text:0041BC35                 lea     eax, [ebp+SystemArgument1]
.text:0041BC38                 push    eax
.text:0041BC39                 lea     eax, [ebp+NormalContext]
.text:0041BC3C                 push    eax
.text:0041BC3D                 lea     eax, [ebp+NormalRoutine]
.text:0041BC40                 push    eax
.text:0041BC41                 push    edi
.text:0041BC42                 call    [ebp+var_KernelRoutine]
.text:0041BC45                 lea     edx, [ebp+LockHandle] ; LockHandle
.text:0041BC48                 lea     ecx, [esi+_KTHREAD.ApcQueueLock] ; SpinLock
.text:0041BC4E                 call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
.text:0041BC54                 jmp     loc_405E56

如果NormalRoutine不为NULL,先对线程中的KernelApcInProgress和KernelApcDisable两个成员进行判断,分别判断是否有内核APC在执行,以及内核APC是否被禁止

1
2
3
4
5
.text:0041AD60 loc_41AD60:                             ; CODE XREF: KiDeliverApc(x,x,x)+15E13↓j
.text:0041AD60                 cmp     [esi+_KTHREAD.ApcState.KernelApcInProgress], 0
.text:0041AD64                 jnz     loc_405E6B
.text:0041AD6A                 cmp     [esi+_KTHREAD.KernelApcDisable], 0
.text:0041AD71                 jnz     loc_405E6B

如果有内核APC在执行或内核APC被禁止,则结束执行

1
2
3
4
.text:00405E6B loc_405E6B:                             ; CODE XREF: KiDeliverApc(x,x,x)+A6CD↓j
.text:00405E6B                                         ; KiDeliverApc(x,x,x)+A6D7↓j ...
.text:00405E6B                 lea     ecx, [ebp+LockHandle] ; LockHandle
.text:00405E6E                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4

如果通过验证,则将内核APC从链表中摘除,将Inserted赋值为0,释放APC锁

1
2
3
4
5
6
7
.text:0041AD77                 mov     ecx, [eax+LIST_ENTRY.Flink]
.text:0041AD79                 mov     eax, [eax+LIST_ENTRY.Blink]
.text:0041AD7C                 mov     [eax+LIST_ENTRY.Flink], ecx
.text:0041AD7E                 mov     [ecx+LIST_ENTRY.Blink], eax
.text:0041AD81                 lea     ecx, [ebp+LockHandle] ; LockHandle
.text:0041AD84                 mov     [edi+KAPC.Inserted], 0
.text:0041AD88                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4

调用KernelRoutine释放APC对象内存

1
2
3
4
5
6
7
8
9
10
.text:0041AD8E                 lea     eax, [ebp+SystemArgument2]
.text:0041AD91                 push    eax
.text:0041AD92                 lea     eax, [ebp+SystemArgument1]
.text:0041AD95                 push    eax
.text:0041AD96                 lea     eax, [ebp+NormalContext]
.text:0041AD99                 push    eax
.text:0041AD9A                 lea     eax, [ebp+NormalRoutine]
.text:0041AD9D                 push    eax
.text:0041AD9E                 push    edi
.text:0041AD9F                 call    [ebp+var_KernelRoutine]

再次判断NormalRoutine是否为NULL

1
2
.text:0041ADA2                 cmp     [ebp+NormalRoutine], 0
.text:0041ADA6                 jz      short loc_41ADCB

不为NULL,则将KernelApcInProgress赋值为1,代表内核APC正在执行,且通过函数降低IRQL

1
2
3
.text:0041ADA8                 xor     cl, cl          ; NewIrql
.text:0041ADAA                 mov     [esi+_KTHREAD.ApcState.KernelApcInProgress], 1
.text:0041ADAE                 call    ds:__imp_@KfLowerIrql@4

调用NormalRoutine中保存的内核APC函数

1
2
3
4
.text:0041ADB4                 push    [ebp+SystemArgument2]
.text:0041ADB7                 push    [ebp+SystemArgument1]
.text:0041ADBA                 push    [ebp+NormalContext]
.text:0041ADBD                 call    [ebp+NormalRoutine]

通过函数提高IRQL

1
2
3
.text:0041ADC0                 mov     cl, 1           ; NewIrql
.text:0041ADC2                 call    ds:__imp_@KfRaiseIrql@4 ; KfRaiseIrql(x)
.text:0041ADC8                 mov     [ebp+LockHandle.OldIrql], al

释放APC锁,将KernelApcInProgress赋值为0,代表没有内核APC函数正在执行,然后依然返回到loc_405E56处继续寻找下一个内核APC

1
2
3
4
5
6
.text:0041ADCB loc_41ADCB:                             ; CODE XREF: KiDeliverApc(x,x,x)+14FA5↑j
.text:0041ADCB                 lea     edx, [ebp+LockHandle] ; LockHandle
.text:0041ADCE                 lea     ecx, [esi+_KTHREAD.ApcQueueLock] ; SpinLock
.text:0041ADD4                 call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
.text:0041ADDA                 mov     [esi+_KTHREAD.ApcState.KernelApcInProgress], 0
.text:0041ADDE                 jmp     loc_405E56

当执行完内核APC以后,就会判断用户APC链表中是否有需要处理的APC对象

1
2
3
4
.text:00405E5E                 lea     ecx, [esi+3Ch]  ; 将用户APC链表赋给ecx
.text:00405E61                 mov     eax, [ecx+LIST_ENTRY.Flink]
.text:00405E63                 cmp     eax, ecx
.text:00405E65                 jnz     loc_4104CA

如果没有,就会释放APC锁,退出KiDeliver函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:00405E6B loc_405E6B:                             ; CODE XREF: KiDeliverApc(x,x,x)+A6CD↓j
.text:00405E6B                                         ; KiDeliverApc(x,x,x)+A6D7↓j ...
.text:00405E6B                 lea     ecx, [ebp+LockHandle] ; LockHandle
.text:00405E6E                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
.text:00405E74
.text:00405E74 loc_405E74:                             ; CODE XREF: KiDeliverApc(x,x,x)+A74A↓j
.text:00405E74                                         ; KiDeliverApc(x,x,x)+4559A↓j ...
.text:00405E74                 mov     ecx, [ebp+var_EPROCESS]
.text:00405E77                 cmp     [esi+_KTHREAD.ApcState.Process], ecx
.text:00405E7A                 jnz     loc_44B3A5
.text:00405E80                 mov     eax, [ebp+var_TrapFrame]
.text:00405E83                 pop     edi
.text:00405E84                 mov     [esi+_KTHREAD.TrapFrame], eax
.text:00405E8A                 pop     esi
.text:00405E8B                 pop     ebx
.text:00405E8C                 leave
.text:00405E8D                 retn    0Ch
.text:00405E8D _KiDeliverApc@12 endp

如果有用户APC要执行,会首先判断先前模式是否为用户模式以及用户APC是否等待交互。如果先前模式不是用户模式,或者用户APC正在等待交付,则会退出函数执行

1
2
3
4
5
.text:004104CA loc_4104CA:                             ; CODE XREF: KiDeliverApc(x,x,x)+64↑j
.text:004104CA                 cmp     [ebp+PreviousMode], 1
.text:004104CE                 jnz     loc_405E6B
.text:004104D4                 cmp     [esi+_KTHREAD.ApcState.UserApcPending], 0
.text:004104D8                 jz      loc_405E6B

将线程对象中的UserApcPending赋值为0,并为局部变量赋值

1
2
3
4
5
6
7
8
9
10
11
.text:004104DE                 mov     [esi+_KTHREAD.ApcState.UserApcPending], 0
.text:004104E2                 lea     edi, [eax-0Ch]
.text:004104E5                 mov     ecx, [edi+KAPC.NormalRoutine]
.text:004104E8                 mov     ebx, [edi+KAPC.KernelRoutine] ; 将KernelRoutine赋值给ebx
.text:004104EB                 mov     [ebp+NormalRoutine], ecx
.text:004104EE                 mov     ecx, [edi+KAPC.NormalContext]
.text:004104F1                 mov     [ebp+NormalContext], ecx
.text:004104F4                 mov     ecx, [edi+KAPC.SystemArgument1]
.text:004104F7                 mov     [ebp+SystemArgument1], ecx
.text:004104FA                 mov     ecx, [edi+KAPC.SystemArgument2]
.text:004104FD                 mov     [ebp+SystemArgument2], ecx

将APC对象从链表中摘除,APC对象的Inserted赋值为0,释放APC锁

1
2
3
4
5
6
7
.text:00410500                 mov     ecx, [eax+LIST_ENTRY.Flink]
.text:00410502                 mov     eax, [eax+LIST_ENTRY.Blink]
.text:00410505                 mov     [eax+LIST_ENTRY.Flink], ecx
.text:00410507                 mov     [ecx+LIST_ENTRY.Blink], eax
.text:0041050A                 lea     ecx, [ebp+LockHandle] ; LockHandle
.text:0041050D                 mov     [edi+KAPC.Inserted], 0
.text:00410511                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4

由于ebx此时保存的是KernelRoutine,所以接下来就是调用KernelRoutine来释放APC对象的内存

1
2
3
4
5
6
7
8
9
10
.text:00410517                 lea     eax, [ebp+SystemArgument2]
.text:0041051A                 push    eax
.text:0041051B                 lea     eax, [ebp+SystemArgument1]
.text:0041051E                 push    eax
.text:0041051F                 lea     eax, [ebp+NormalContext]
.text:00410522                 push    eax
.text:00410523                 lea     eax, [ebp+NormalRoutine]
.text:00410526                 push    eax
.text:00410527                 push    edi
.text:00410528                 call    ebx

判断NormalRoutine是否为NULL

1
2
.text:0041052A                 cmp     [ebp+NormalRoutine], 0
.text:0041052E                 jz      loc_44B394

如果为NULL,则会调用KeTestAlertThread

1
2
3
4
.text:0044B394 loc_44B394:                             ; CODE XREF: KiDeliverApc(x,x,x)+A72D↑j
.text:0044B394                 push    1
.text:0044B396                 call    _KeTestAlertThread@4 ; KeTestAlertThread(x)
.text:0044B39B                 jmp     loc_405E74

如果不为NULL,则调用KiInitializeUserApc

1
2
3
4
5
6
7
8
.text:00410534                 push    [ebp+SystemArgument2] ; SystemArgument2
.text:00410537                 push    [ebp+SystemArgument1] ; SystemArgument1
.text:0041053A                 push    [ebp+NormalContext] ; NormalContext
.text:0041053D                 push    [ebp+NormalRoutine] ; NormalRoutine
.text:00410540                 push    [ebp+TrapFrame] ; int
.text:00410543                 push    [ebp+ExceptionFrame] ; int
.text:00410546                 call    _KiInitializeUserApc@24 ; KiInitializeUserApc(x,x,x,x,x,x)
.text:0041054B                 jmp     loc_405E74

对于用户APC函数,由于该函数是在用户空间执行的,所以需要返回到用户空间,而要返回到用户空间就会导致陷阱帧(Trap_Frame)遭到破坏,为了保证程序在执行完用户APC以后依然可以正常运行,就需要将陷阱帧中的内容保存起来,所以函数最开始除了把变量赋值到局部变量中,还通过KeContextFromKFrames函数将陷阱帧中的数据保存到Context结构的局部变量var_ContextFrame中

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
.text:00410555 ; int __stdcall KiInitializeUserApc(int, int, PKNORMAL_ROUTINE NormalRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
.text:00410555 _KiInitializeUserApc@24 proc near       ; CODE XREF: KiDeliverApc(x,x,x)+A745↑p
.text:00410555
.text:00410555 ExceptionRecord = EXCEPTION_RECORD ptr -34Ch
.text:00410555 var_ETHREAD     = dword ptr -2FCh
.text:00410555 var_Length      = dword ptr -2F8h
.text:00410555 var_ExceptionFrame= dword ptr -2F4h
.text:00410555 var_TrapFrame   = dword ptr -2F0h
.text:00410555 var_UserStack   = dword ptr -2ECh
.text:00410555 var_ContextFrame= _CONTEXT ptr -2E8h
.text:00410555 var_Cookie      = dword ptr -1Ch
.text:00410555 ms_exc          = CPPEH_RECORD ptr -18h
.text:00410555 ExceptionFrame  = dword ptr  8
.text:00410555 TrapFrame       = dword ptr  0Ch
.text:00410555 NormalRoutine   = dword ptr  10h
.text:00410555 NormalContext   = dword ptr  14h
.text:00410555 SystemArgument1 = dword ptr  18h
.text:00410555 SystemArgument2 = dword ptr  1Ch
.text:00410555
.text:00410555                 push    33Ch
.text:0041055A                 push    offset stru_410698
.text:0041055F                 call    __SEH_prolog
.text:00410564                 mov     eax, ds:___security_cookie
.text:00410569                 mov     [ebp+var_Cookie], eax
.text:0041056C                 mov     eax, [ebp+ExceptionFrame]
.text:0041056F                 mov     [ebp+var_ExceptionFrame], eax
.text:00410575                 mov     ebx, [ebp+TrapFrame] ; 将TrapFrame赋值给ebx
.text:00410578                 mov     [ebp+var_TrapFrame], ebx
.text:0041057E                 test    byte ptr [ebx+72h], 2
.text:00410582                 jnz     loc_410681
.text:00410588                 mov     [ebp+var_ContextFrame.ContextFlags], 10017h
.text:00410592                 lea     ecx, [ebp+var_ContextFrame]
.text:00410598                 push    ecx
.text:00410599                 push    eax
.text:0041059A                 push    ebx
.text:0041059B                 call    _KeContextFromKframes@12

取出用户栈的栈顶指针,此时的指针指向陷阱帧的顶部,将其减去一段地址得到新的栈顶,验证栈顶是否可写

1
2
3
4
5
6
7
8
9
10
.text:004105A4                 mov     eax, 2DCh
.text:004105A9                 mov     [ebp+var_Length], eax
.text:004105AF                 mov     esi, [ebp+var_ContextFrame._Esp] ; 将用户栈指针赋给esi
.text:004105B5                 and     esi, 0FFFFFFFCh
.text:004105B8                 sub     esi, eax        ; 开辟一段栈空间保存Context和参数
.text:004105BA                 mov     [ebp+var_UserStack], esi ; 保存栈顶地址
.text:004105C0                 push    4               ; Alignment
.text:004105C2                 push    eax             ; Length
.text:004105C3                 push    esi             ; Address
.text:004105C4                 call    _ProbeForWrite@12

将局部变量var_ContextFrame中保存的陷阱帧数据赋值到新的栈顶偏移0x10处

1
2
3
4
.text:004105C9                 lea     edi, [esi+10h]  ; 栈顶+0x10的地址赋给edi
.text:004105CC                 mov     ecx, 0B3h
.text:004105D1                 lea     esi, [ebp+var_ContextFrame] ; ContextFrane肚子鼓反映给esi
.text:004105D7                 rep movsd

为陷阱帧的各项寄存器赋值,验证Iopl是否为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:004105D9                 mov     dword ptr [ebx+6Ch], 1Bh ; 为cs寄存器赋值
.text:004105E0                 push    23h
.text:004105E2                 pop     eax
.text:004105E3                 mov     [ebx+78h], eax  ; 为ss寄存器赋值
.text:004105E6                 mov     [ebx+38h], eax  ; 为ds寄存器赋值
.text:004105E9                 mov     [ebx+34h], eax  ; 为es寄存器赋值
.text:004105EC                 mov     dword ptr [ebx+50h], 3Bh ; 为fs寄存器赋值
.text:004105F3                 and     dword ptr [ebx+30h], 0 ; gs寄存器清0
.text:004105F7                 mov     ecx, [ebp+var_ContextFrame.EFlags]
.text:004105FD                 test    ecx, 20000h
.text:00410603                 jnz     loc_44B92B
.text:00410609
.text:00410609 loc_410609:                             ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+3B3DD↓j
.text:00410609                 and     ecx, 3E0DD7h
.text:0041060F                 or      ecx, 200h
.text:00410615                 mov     eax, ecx
.text:00410617
.text:00410617 loc_410617:                             ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+3B3F0↓j
.text:00410617                 mov     [ebx+70h], eax  ; 为Eflags寄存器赋值
.text:0041061A                 mov     eax, large fs:124h
.text:00410620                 mov     [ebp+var_ETHREAD], eax
.text:00410626                 cmp     [eax+_ETHREAD.Tcb.Iopl], 0
.text:0041062A                 jnz     loc_43C680

为0的时候,将新的栈顶指针赋给eax,继续陷阱帧中的寄存器赋值,此时陷阱帧的EIP被修改为ntdll.dll中的函数KeUserAcpDispatcher地址

1
2
3
4
5
6
.text:00410630 loc_410630:                             ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+2C12F↓j
.text:00410630                 mov     eax, [ebp+var_UserStack] ; 将新的栈顶指针赋给eax
.text:00410636                 mov     [ebx+74h], eax  ; 为esp赋值
.text:00410639                 mov     ecx, ds:_KeUserApcDispatcher
.text:0041063F                 mov     [ebx+68h], ecx  ; 为EIP赋值
.text:00410642                 and     dword ptr [ebx+64h], 0 ; 为ErrorCode赋值ode赋值

由于eax此时指向新的栈顶的指针,所以接下来的代码就是将4参数赋值到栈顶,这也就是为什么在复制Context的时候,复制的目的地址是新的栈顶指针偏移0x10的地址,因为最开始的0x10的空间是用来保存这四个参数的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00410649                 mov     [eax], ecx
.text:0041064B                 push    4
.text:0041064D                 pop     ecx             ; ecx赋值为4
.text:0041064E                 add     eax, ecx
.text:00410650                 mov     [ebp+var_UserStack], eax
.text:00410656                 mov     edx, [ebp+NormalContext]
.text:00410659                 mov     [eax], edx
.text:0041065B                 add     eax, ecx
.text:0041065D                 mov     [ebp+var_UserStack], eax
.text:00410663                 mov     edx, [ebp+SystemArgument1]
.text:00410666                 mov     [eax], edx
.text:00410668                 add     eax, ecx
.text:0041066A                 mov     [ebp+var_UserStack], eax
.text:00410670                 mov     edx, [ebp+SystemArgument2]
.text:00410673                 mov     [eax], edx
.text:00410675                 add     eax, ecx
.text:00410677                 mov     [ebp+var_UserStack], eax

此时已经成功修改的陷阱帧的EIP,当函数返回到用户层的时候就会执行指定的ntdll.dll中的函数KeUserAcpDispatcher,且经过复制以后,此时用户栈的数据如下图所示

 

avatar

 

继续看ntll.dll中KeUserApcDispatcher中的函数的时候,可以看到该函数首先取出栈顶指针偏移0x10的地址赋给edi,随后弹出栈顶保存的NormalRoutine,调用该函数,将edi入栈后调用ZwContinue

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:7C92E430                 public KeUserApcDispatcher
.text:7C92E430 KeUserApcDispatcher 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
.text:7C92E43A                 call    ZwContinue
.text:7C92E43F                 nop
.text:7C92E43F KiUserApcDispatcher

NormalRoutine的函数地址是在用户层调用QueueUserApc插入用户APC的时候,指定为函数BaseDispatchAPC,该函数在kernel32.dll中,而该函数最重要的就是调用由用户所指定的NormalContext中保存的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:7C82C0F6 ; void __stdcall BaseDispatchAPC(PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
.text:7C82C0F6 _BaseDispatchAPC@12 proc near           ; DATA XREF: QueueUserAPC(x,x,x)+44↑o
.text:7C82C0F6
.text:7C82C0F6 var_30          = dword ptr -30h
.text:7C82C0F6 var_2C          = dword ptr -2Ch
.text:7C82C0F6 var_28          = byte ptr -28h
.text:7C82C0F6 handle          = dword ptr -1Ch
.text:7C82C0F6 ms_exc          = CPPEH_RECORD ptr -18h
.text:7C82C0F6 NormalContext   = dword ptr  8
.text:7C82C0F6 SystemArgument1 = dword ptr  0Ch
.text:7C82C0F6 SystemArgument2 = dword ptr  10h
.text:7C82C136                 push    [ebp+SystemArgument1] ; 将参数入栈
.text:7C82C139                 call    [ebp+NormalContext] ; 调用NormalContext
.text:7C82C14A                 retn    0Ch
.text:7C82C14A _BaseDispatchAPC@12 endp

调用结束以后,加下来继续调用ZwContinue进入内核,重复上面的操作

1
2
3
4
5
6
7
8
.text:7C92D040                 public ZwContinue
.text:7C92D040 ZwContinue      proc near               ; CODE XREF: KiUserApcDispatcher+A↓p
.text:7C92D040                                         ; KiUserExceptionDispatcher+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      endp

五.线程的启动例程

在线程创建的内核函数NtCreateThread中会调用KeInitThread函数来初始化线程对象,以及线程的启动函数。

 

初始化与同步有关的各个域

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
PAGE:0049F115 ; int __stdcall KeInitThread(PKTHREAD Thread, PVOID KernelStack, int, PKSTART_ROUTINE StartRoutine, PVOID StartContext, PCONTEXT ContextFrame, PVOID Teb, PEPROCESS Process)
PAGE:0049F115 _KeInitThread@32 proc near              ; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+178↑p
PAGE:0049F115                                         ; KeInitializeThread(x,x,x,x,x,x,x,x)+1D↓p
PAGE:0049F115
PAGE:0049F115 var_20          = dword ptr -20h
PAGE:0049F115 var_19          = byte ptr -19h
PAGE:0049F115 ms_exc          = CPPEH_RECORD ptr -18h
PAGE:0049F115 Thread          = dword ptr  8
PAGE:0049F115 KernelStack     = dword ptr  0Ch
PAGE:0049F115 SystemRoutine   = dword ptr  10h
PAGE:0049F115 StartRoutine    = dword ptr  14h
PAGE:0049F115 StartContext    = dword ptr  18h
PAGE:0049F115 ContextFrame    = dword ptr  1Ch
PAGE:0049F115 Teb             = dword ptr  20h
PAGE:0049F115 Process         = dword ptr  24h
PAGE:0049F115
PAGE:0049F115                 push    10h
PAGE:0049F117                 push    offset stru_415040
PAGE:0049F11C                 call    __SEH_prolog
PAGE:0049F121                 mov     [ebp+var_19], 0
PAGE:0049F125                 mov     esi, [ebp+Thread] ; 将线程对象赋给esi
PAGE:0049F128                 mov     [esi+_KTHREAD.Header.Type], ThreadObject
PAGE:0049F12B                 mov     [esi+_KTHREAD.Header.Size], 70h
PAGE:0049F12F                 lea     eax, [esi+_KTHREAD.Header.WaitListHead]
PAGE:0049F132                 mov     [eax+LIST_ENTRY.Blink], eax
PAGE:0049F135                 mov     [eax+LIST_ENTRY.Flink], eax
PAGE:0049F137                 lea     eax, [esi+_KTHREAD.MutantListHead]
PAGE:0049F13A                 mov     [eax+LIST_ENTRY.Blink], eax
PAGE:0049F13D                 mov     [eax+LIST_ENTRY.Flink], eax
PAGE:0049F13F                 lea     eax, [esi+_KTHREAD.WaitBlock.Thread]
PAGE:0049F142                 push    4
PAGE:0049F144                 pop     ecx
PAGE:0049F145
PAGE:0049F145 loc_49F145:                             ; CODE XREF: KeInitThread(x,x,x,x,x,x,x,x)+36↓j
PAGE:0049F145                 mov     [eax], esi
PAGE:0049F147                 add     eax, 18h
PAGE:0049F14A                 dec     ecx
PAGE:0049F14B                 jnz     short loc_49F145

初始化系统服务表,APC的域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PAGE:0049F14D                 mov     edi, [ebp+Process] ; 将进程对象赋给edi
PAGE:0049F150                 mov     al, [edi+_KPROCESS.AutoAlignment]
PAGE:0049F153                 mov     [esi+_KTHREAD.AutoAlignment], al
PAGE:0049F159                 mov     [esi+_KTHREAD.EnableStackSwap], 1
PAGE:0049F160                 mov     [esi+_KTHREAD.KernelStackResident], 1
PAGE:0049F167                 mov     [esi+_KTHREAD.ServiceTable], offset _KeServiceDescriptorTable ; 为系统服务表赋值
PAGE:0049F171                 lea     eax, [esi+_KTHREAD.ApcState] ; 将Apc地址赋给eax
PAGE:0049F174                 mov     [esi+_KTHREAD.ApcStatePointer], eax ; 将eax地址赋给ApcStatePointer[0]
PAGE:0049F17A                 lea     ecx, [esi+_KTHREAD.SavedApcState] ; 将SaveApcState地址赋给ecx
PAGE:0049F180                 mov     [esi+13Ch], ecx ; 将ecx赋给ApcStatePointer[1]
PAGE:0049F186                 mov     [eax+LIST_ENTRY.Blink], eax
PAGE:0049F189                 mov     [eax+LIST_ENTRY.Flink], eax
PAGE:0049F18B                 lea     eax, [esi+3Ch]  ; 将ApcState第二个链表地址,也就是用户链表地址赋给eax
PAGE:0049F18E                 mov     [eax+LIST_ENTRY.Blink], eax
PAGE:0049F191                 mov     [eax+LIST_ENTRY.Flink], eax
PAGE:0049F193                 mov     [esi+_KTHREAD.ApcState.Process], edi
PAGE:0049F196                 mov     [esi+_KTHREAD.ApcQueueable], 1

调用KeInitializeApc初始化一个内核APC对象

1
2
3
4
5
6
7
8
9
10
11
PAGE:0049F19D                 xor     ebx, ebx        ; ebx清0
PAGE:0049F19F                 push    ebx             ; NormalContext
PAGE:0049F1A0                 push    ebx             ; ApcMode
PAGE:0049F1A1                 push    offset _KiSuspendThread@12 ; NormalRoutine
PAGE:0049F1A6                 push    offset _KiSuspendRundown@4 ; RundownRoutine
PAGE:0049F1AB                 push    offset _KiSuspendNop@20 ; KernelRoutine
PAGE:0049F1B0                 push    ebx             ; int
PAGE:0049F1B1                 push    esi             ; Thread
PAGE:0049F1B2                 lea     eax, [esi+_KTHREAD.SuspendApc]
PAGE:0049F1B8                 push    eax             ; Apc
PAGE:0049F1B9                 call    _KeInitializeApc@32

该APC对象的NormalRoutine为KiSuspendThread,该函数实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:00423E71 ; void __stdcall KiSuspendThread(PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
.text:00423E71 _KiSuspendThread@12 proc near           ; DATA XREF: KeInitThread(x,x,x,x,x,x,x,x)+8C↓o
.text:00423E71
.text:00423E71 NormalContext   = dword ptr  4
.text:00423E71 SystemArgument1 = dword ptr  8
.text:00423E71 SystemArgument2 = dword ptr  0Ch
.text:00423E71
.text:00423E71                 mov     eax, large fs:124h
.text:00423E77                 xor     ecx, ecx
.text:00423E79                 push    ecx             ; Timeout
.text:00423E7A                 push    ecx             ; Alertable
.text:00423E7B                 push    ecx             ; WaitMode
.text:00423E7C                 push    5               ; WaitReason
.text:00423E7E                 add     eax, _KTHREAD.SuspendSemaphore
.text:00423E83                 push    eax             ; Object
.text:00423E84                 call    _KeWaitForSingleObject@20 ; KeWaitForSingleObject(x,x,x,x,x)
.text:00423E89                 retn    0Ch
.text:00423E89 _KiSuspendThread@12 endp

初始化互斥体,定时器,WaitBlock,APC锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PAGE:0049F1BE                 push    2               ; Limit
PAGE:0049F1C0                 push    ebx             ; Count
PAGE:0049F1C1                 lea     eax, [esi+_KTHREAD.SuspendSemaphore]
PAGE:0049F1C7                 push    eax             ; Semaphore
PAGE:0049F1C8                 call    _KeInitializeSemaphore@12 ; KeInitializeSemaphore(x,x,x)
PAGE:0049F1CD                 lea     ebx, [esi+_KTHREAD.Timer]
PAGE:0049F1D3                 push    ebx             ; Timer
PAGE:0049F1D4                 call    _KeInitializeTimer@4 ; KeInitializeTimer(x)
PAGE:0049F1D9                 lea     eax, [esi+0B8h]
PAGE:0049F1DF                 mov     [eax+0Ch], ebx
PAGE:0049F1E2                 mov     word ptr [eax+14h], 102h
PAGE:0049F1E8                 mov     word ptr [eax+16h], 1
PAGE:0049F1EE                 add     ebx, 8
PAGE:0049F1F1                 mov     [eax+LIST_ENTRY.Flink], ebx
PAGE:0049F1F3                 mov     [eax+LIST_ENTRY.Blink], ebx
PAGE:0049F1F6                 lea     eax, [esi+_KTHREAD.ApcQueueLock]
PAGE:0049F1FC                 push    eax             ; SpinLock
PAGE:0049F1FD                 call    _KeInitializeSpinLock@4

创建内核栈

1
2
3
4
5
6
7
8
PAGE:0049F237                 mov     [esi+_KTHREAD.InitialNode], 0
PAGE:0049F23E                 mov     eax, [ebp+KernelStack]
PAGE:0049F241                 xor     edi, edi
PAGE:0049F243                 cmp     eax, edi
PAGE:0049F245                 jnz     short loc_49F25A
PAGE:0049F247                 push    edi
PAGE:0049F248                 push    edi
PAGE:0049F249                 call    _MmCreateKernelStack@8

初始化栈信息

1
2
3
4
PAGE:0049F25A                 mov     [esi+_ETHREAD.Tcb.InitialStack], eax
PAGE:0049F25D                 mov     [esi+_KTHREAD.StackBase], eax
PAGE:0049F263                 add     eax, 0FFFFD000h
PAGE:0049F268                 mov     [esi+_ETHREAD.Tcb.StackLimit], eax

调用KeInitializeContextThread初始化域特定处理器相关的执行环境

1
2
3
4
5
6
PAGE:0049F26E                 push    [ebp+ContextFrame] ; ContextFrame
PAGE:0049F271                 push    [ebp+StartContext] ; StartContext
PAGE:0049F274                 push    [ebp+StartRoutine] ; StartRoutine
PAGE:0049F277                 push    [ebp+SystemRoutine] ; int
PAGE:0049F27A                 push    esi             ; Thread
PAGE:0049F27B                 call    _KiInitializeContextThread@20

在初始化线程的两个域以后退出函数

1
2
3
4
5
6
7
8
9
10
PAGE:0049F284                 mov     [esi+_KTHREAD.State], 0
PAGE:0049F288                 mov     [esi+_KTHREAD.IdealProcessor], bl
PAGE:0049F28E                 xor     eax, eax
PAGE:0049F290
PAGE:0049F290 loc_49F290:                             ; CODE XREF: KeInitThread(x,x,x,x,x,x,x,x)+82F72↓j
PAGE:0049F290                                         ; KeInitThread(x,x,x,x,x,x,x,x)+82FB3↓j
PAGE:0049F290                 call    __SEH_epilog
PAGE:0049F295                 retn    20h
PAGE:0049F295 ; } // starts at 49F115
PAGE:0049F295 _KeInitThread@32 endp

在KeInitializeContextThread中,函数会首先判断是否传递了ContextFrame参数,如果有该参数,说明创建的是用户线程,否则就是内核线程

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
.text:00415109 ; int __stdcall KiInitializeContextThread(PKTHREAD Thread, int, PKSTART_ROUTINE StartRoutine, PVOID StartContext, PCONTEXT ContextFrame)
.text:00415109 _KiInitializeContextThread@20 proc near ; CODE XREF: KeInitThread(x,x,x,x,x,x,x,x)+166↓p
.text:00415109
.text:00415109 var_2E0         = dword ptr -2E0h
.text:00415109 var_SwitchFrame = dword ptr -2DCh
.text:00415109 var_ContextFlags= dword ptr -2D8h
.text:00415109 var_TrapFrame    = dword ptr -2D4h
.text:00415109 var_Context     = _CONTEXT ptr -2D0h
.text:00415109 var_Cookie      = dword ptr -4
.text:00415109 Thread          = dword ptr  8
.text:00415109 SystemRoutine   = dword ptr  0Ch
.text:00415109 StartRoutine    = dword ptr  10h
.text:00415109 StartContext    = dword ptr  14h
.text:00415109 ContextFrame    = dword ptr  18h
.text:00415109
.text:00415109 ; FUNCTION CHUNK AT .text:00423C15 SIZE 0000015C BYTES
.text:00415109 ; FUNCTION CHUNK AT .text:0044B8CF SIZE 0000005C BYTES
.text:00415109
.text:00415109                 mov     edi, edi
.text:0041510B                 push    ebp
.text:0041510C                 mov     ebp, esp
.text:0041510E                 sub     esp, 2E0h
.text:00415114                 mov     eax, ds:___security_cookie
.text:00415119                 push    ebx
.text:0041511A                 mov     ebx, [ebp+Thread] ; 将线程对象赋给ebx
.text:0041511D                 push    esi
.text:0041511E                 mov     esi, [ebp+ContextFrame] ; 将ContextFrame赋给esi
.text:00415121                 mov     [ebp+var_Cookie], eax
.text:00415124                 xor     edx, edx        ; edx清0
.text:00415126                 xor     eax, eax        ; eax清0
.text:00415128                 cmp     esi, edx        ; 判断ContextFrame是否存在
.text:0041512A                 push    edi
.text:0041512B                 jnz     loc_423C15

如果是用户线程,就会将传递的ContextFrame赋值给局部变量var_Context

1
2
3
4
.text:00423C15 loc_423C15:                             ; CODE XREF: KiInitializeContextThread(x,x,x,x,x)+22↑j
.text:00423C15                 mov     ecx, 0B3h
.text:00423C1A                 lea     edi, [ebp+var_Context]
.text:00423C20                 rep movsd

从线程对象中取出栈指针,并开辟一段空间用来吧保存数据

1
2
3
4
5
6
7
.text:00423C22                 mov     esi, [ebx+_ETHREAD.Tcb.InitialStack]
.text:00423C25                 sub     esi, 210h
.text:00423C2B                 mov     [ebp+var_TrapFrame], esi
.text:00423C31                 add     esi, 0FFFFFF74h
.text:00423C37                 mov     ecx, 0A7h
.text:00423C3C                 mov     edi, esi
.text:00423C3E                 rep stosd

对局部变量var_Context进行赋值

1
2
3
4
5
6
7
8
9
.text:0044B8CF loc_44B8CF:                             ; CODE XREF: KiInitializeContextThread(x,x,x,x,x)+EB48↑j
.text:0044B8CF                 mov     [ebp+var_Context.FloatSave.ControlWord], 27Fh
.text:0044B8D9                 mov     [ebp+var_Context.FloatSave.StatusWord], edx
.text:0044B8DF                 mov     [ebp+var_Context.FloatSave.TagWord], 0FFFFh
.text:0044B8E9                 mov     [ebp+var_Context.FloatSave.ErrorOffset], edx
.text:0044B8EF                 mov     [ebp+var_Context.FloatSave.ErrorSelector], edx
.text:0044B8F5                 mov     [ebp+var_Context.FloatSave.DataOffset], edx
.text:0044B8FB                 mov     [ebp+var_Context.FloatSave.DataSelector], edx
.text:0044B901                 jmp     loc_423C90

调用KeContextToKframes将局部变量var_Context内容赋值到var_TrapFrame中,该函数的作用就是根据Context结构中的内容将数据赋值到陷阱帧(TrapFrame)结构中,此时局部变量var_SwitchFrame是在TrapFrame低地址处,该变量是KSWITCHFRAME结构,用来存储的内容就包括了创建的线程的启动函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.text:00423CEE                 push    1
.text:00423CF0                 push    eax
.text:00423CF1                 lea     edi, [esi-8]
.text:00423CF4                 lea     ecx, [edi-4]
.text:00423CF7                 mov     [ebp+var_2E0], ecx
.text:00423CFD                 lea     eax, [ebp+var_Context]
.text:00423D03                 add     ecx, 0FFFFFFFCh
.text:00423D06                 push    eax
.text:00423D07                 mov     [ebp+var_TrapFrame], ecx
.text:00423D0D                 push    edx
.text:00423D0E                 add     ecx, 0FFFFFFF4h
.text:00423D11                 push    esi
.text:00423D12                 mov     [ebp+var_Context.Dr0], edx
.text:00423D18                 mov     [ebp+var_Context.Dr1], edx
.text:00423D1E                 mov     [ebp+var_Context.Dr2], edx
.text:00423D24                 mov     [ebp+var_Context.Dr3], edx
.text:00423D2A                 mov     [ebp+var_Context.Dr6], edx
.text:00423D30                 mov     [ebp+var_Context.Dr7], edx
.text:00423D36                 mov     [ebp+var_SwitchFrame], ecx
.text:00423D3C                 call    _KeContextToKframes@20

此时esi执行陷阱帧的头部,对陷阱帧中的几个寄存器进行赋值

1
2
3
.text:00423D41                 or      dword ptr [esi+78h], 3 ; ss进行或运算
.text:00423D45                 or      dword ptr [esi+38h], 3 ; ds进行或运算
.text:00423D49                 or      dword ptr [esi+34h], 3 ; es进行或运算

对陷阱帧的先前模式和线程的先前模式赋值为用户模式

1
2
3
4
5
6
.text:00423D53                 xor     eax, eax        ; eax清0
.text:00423D55                 inc     eax             ; eax加1
.text:00423D5D                 mov     [esi+48h], eax  ; PreviousMode赋值为1
.text:00423D60                 mov     [ebx+_KTHREAD.PreviousMode], al
.text:00423D66                 mov     eax, [ebp+var_SwitchFrame]
.text:00423D6C                 jmp     loc_41517B

在栈中填充SystemRoutine, StartRoutine, StartContext,指定线程的启动函数以及线程的栈指针后退出函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.text:0041517B loc_41517B:                             ; CODE XREF: KiInitializeContextThread(x,x,x,x,x)+EC63↓j
.text:0041517B                 mov     edx, [ebp+StartContext]
.text:0041517E                 mov     [edi], edx
.text:00415180                 mov     edx, [ebp+StartRoutine]
.text:00415183                 mov     [ecx], edx
.text:00415185                 mov     ecx, [ebp+SystemRoutine]
.text:00415188                 mov     edx, [ebp+var_TrapFrame]
.text:0041518E                 mov     [edx], ecx
.text:00415190                 or      dword ptr [eax], 0FFFFFFFFh
.text:00415193                 mov     ecx, [ebp+var_Cookie]
.text:00415196                 pop     edi
.text:00415197                 mov     dword ptr [eax+8], offset _KiThreadStartup@4 ; KiThreadStartup(x)
.text:0041519E                 mov     dword ptr [eax+4], 200h
.text:004151A5                 pop     esi
.text:004151A6                 mov     [ebx+_KTHREAD.KernelStack], eax
.text:004151A9                 pop     ebx
.text:004151AA                 call    sub_403063
.text:004151AF                 leave
.text:004151B0                 retn    14h
.text:004151B0 _KiInitializeContextThread@20 endp

由上内容可以知道,启动函数为KiThreadStartup,该函数就是将栈顶的SystemRoutine弹出后调用,而SystemRoutine则是在调用KeInitThread的时候指定的,如果创建的是用户线程,该参数就会是PspUserThreadStartup,如果是内核线程就会是PspSystemThreadStartup。线程启动函数被当作第一个参数传入,该函数是kernel32.dll中的BaseProcessStart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:004151B3 ; __stdcall KiThreadStartup(x)
.text:004151B3 _KiThreadStartup@4 proc near            ; DATA XREF: KiInitializeContextThread(x,x,x,x,x)+8E↑o
.text:004151B3                 xor     ebx, ebx
.text:004151B5                 xor     esi, esi
.text:004151B7                 xor     edi, edi
.text:004151B9                 xor     ebp, ebp        ; 清空寄存器
.text:004151BB                 mov     ecx, 1          ; NewIrql
.text:004151C0                 call    ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
.text:004151C6                 pop     eax             ; 将SystemRoutine赋值到eax
.text:004151C7                 call    eax
.text:004151C9                 pop     ecx
.text:004151CA                 or      ecx, ecx
.text:004151CC                 jz      short loc_4151D5
.text:004151CE                 mov     ebp, esp
.text:004151D0                 jmp     _KiServiceExit2
.text:004151D5 ; ---------------------------------------------------------------------------
.text:004151D5
.text:004151D5 loc_4151D5:                             ; CODE XREF: KiThreadStartup(x)+19↑j
.text:004151D5                 push    0Eh             ; BugCheckCode
.text:004151D7                 call    _KeBugCheck@4   ; KeBugCheck(x)
.text:004151D7 _KiThreadStartup@4

以下是函数的部分代码,根据这些代码可以指代,该函数就是将全局变量PspSytemDll中保存的函数作为APC插入到线程中,而这个被插入的函数是ntdll.dll中的LdrInitializeThunk,该函数负责dll的装入和链接。当PspUserThreadStartup函数返回以后,KiThreadStartup函数返回到用户模式,此时,PspUserThreadStartup插入的APC被交付,于是LdrInitializeThunk函数被调用。当LdrInitializeThunk返回到用户模式APC分发器时,该线程开始在用户模式下执行,调用应用程序指定的线程启动函数,此启动函数的地址已经在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
VOID
PspUserThreadStartup(
    IN PKSTART_ROUTINE StartRoutine,
    IN PVOID StartContext
    )
{
    PETHREAD Thread;
    PKAPC StartApc;
    PEPROCESS Process;
 
    MmAllowWorkingSetExpansion();
 
    Thread = PsGetCurrentThread();
 
    if ( !Thread->DeadThread && !Thread->HasTerminated ) {
 
        StartApc = ExAllocatePool(NonPagedPoolMustSucceed,(ULONG)sizeof(KAPC));
 
        ((PTEB)PsGetCurrentThread()->Tcb.Teb)->CurrentLocale = PsDefaultThreadLocaleId;
 
        KeInitializeApc(
            StartApc,
            &Thread->Tcb,
            OriginalApcEnvironment,
            PspNullSpecialApc,
            NULL,
            PspSystemDll.LoaderInitRoutine,
            UserMode,
            NULL
            );
 
        if ( !KeInsertQueueApc(StartApc,(PVOID) PspSystemDll.DllBase,NULL,0) ) {
            ExFreePool(StartApc);
        } else {
            Thread->Tcb.ApcState.UserApcPending = TRUE;
        }
    }
 
    KeLowerIrql(0);
 
 
    if ( Process->Pcb.UserTime == 0 ) {
        Process->Pcb.UserTime = 1;
    }
 
}

下篇:Windows内核学习笔记之线程(下)


[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2021-12-29 09:30 被1900编辑 ,原因:
收藏
点赞6
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回