-
-
[原创]Windows内核学习笔记之线程(中)
-
2021-12-22 19:06 11779
-
四.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,且经过复制以后,此时用户栈的数据如下图所示
继续看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 ; } } |
[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法