插入、调用APC函数详解

c++

Posted by YiMiTuMi on August 12, 2021

APC插入过程分析

APC不论从3环插入还是从0环插入都会调用内核层函数:

QueueUserAPC(Kernel32.dll)             ↓
	↓				→ 用户层调用
	NtQueueApcThread(ntosker.exe)  ↑
				↓
				KeInitializeApc(分配空间,初始化KAPC结构体) ↓
				↓                                         ↓
				KeInsertQueueApc                          →   很多内核函数调用
				  ↓					  ↑
				  KiInsertQueueApc(将KAPC插入指定APC队列)  ↑

APC函数的执行与插入并不是同一个线程,即在A线程中向B线程插入一个APC,插入的动作是在A线程完成的,但什么时候执行则是由B线程决定的,所以叫“一部过程调用”。

NtQueueApcThread(ntosker.exe)

 NTSTATUS __stdcall NtQueueApcThread(HANDLE ThreadHandle, PKNORMAL_ROUTINE ApcRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
 _NtQueueApcThread@20 proc near          ; DATA XREF: .text:0040B978↑o

 AccessMode      = byte ptr -4
 ThreadHandle    = dword ptr  8
 ApcRoutine      = dword ptr  0Ch
 NormalContext   = dword ptr  10h
 SystemArgument1 = dword ptr  14h
 SystemArgument2 = dword ptr  18h

 ; FUNCTION CHUNK AT PAGE:00523983 SIZE 00000025 BYTES
				
				 //初始化堆栈
                 mov     edi, edi
                 push    ebp
                 mov     ebp, esp
                 push    ecx
                 push    ebx
                 push    esi 
				
                 mov     eax, large fs:124h  //_KPCR.CurrentThread 获取当前线程的_KTHREAD
                 mov     al, [eax+140h]      //获取先前模式
                 mov     [ebp+AccessMode], al //给参数赋值
                 xor     esi, esi             //清零esi

                 push    esi             	       //参数
                 lea     eax, [ebp+ThreadHandle]   //要插入APC线程句柄
                 push    eax             		   
                 push    dword ptr [ebp+AccessMode] //传参
                 push    _PsThreadType   				
                 push    10h             
                 push    [ebp+ThreadHandle] 
                 call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x) //根据提供的 Handle 值得到 Object!
                 mov     ebx, eax                       //返回值保存到eax,解析返回一个 _KTHREAD对象
                 cmp     ebx, esi     
                 jl      short loc_4BA123               //判断 _KTHREAD对象 是否为空


                 mov     eax, [ebp+ThreadHandle]       //获取线程句柄
                 xor     ebx, ebx                      //清空ebx
                 test    byte ptr [eax+248h], 10h      // eax + 248 =  _ETHREAD.ThreadListEntry.CrossThreadFlags 
                 jnz     loc_523983                    //判断 _ETHREAD.ThreadListEntry.CrossThreadFlags 标志位是否为 10H

                 push    edi
                 push    70617350h       ; Tag
                 push    30h 			 //分配的空间大小,_KAPC 大小为30h
                 push    8               ; PoolType
                 call    _ExAllocatePoolWithQuotaTag@12 ; ExAllocatePoolWithQuotaTag(x,x,x) //函数分配了一个_KAPC结构体大小

                 mov     edi, eax        //函数返回一个指向已分配池的指针,即_KAPC结构体指针。
                 cmp     edi, esi        
                 jz      loc_52398D      //判断指针是否为空

                 push    [ebp+NormalContext]  //内核APC: NULL, 用户APC:真正的APC函数
                 push    1
                 push    [ebp+ApcRoutine]     //先前模式
                 push    esi                  //0
                 push    offset _IopDeallocateApc@20 ; IopDeallocateApc(x,x,x,x,x) //获取释放APC函数地址
                 push    esi                  //0
                 push    [ebp+ThreadHandle]   //要插入线程句柄
                 push    edi                  //指向已分配池的指针
                 call    _KeInitializeApc@32 ; KeInitializeApc(x,x,x,x,x,x,x,x)

                 push    esi                    //0
                 push    [ebp+SystemArgument2]  //APC函数的参数
                 push    [ebp+SystemArgument1]  //APC函数的参数
                 push    edi                    //初始化好的_KAPC 结构体
                 call    _KeInsertQueueApc@16 ; KeInsertQueueApc(x,x,x,x)
                 test    al, al
                 jz      loc_523997

 loc_4BA11A:                             ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+69907↓j
                                         ; NtQueueApcThread(x,x,x,x,x)+69918↓j
                 pop     edi

 loc_4BA11B:                             ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+698FD↓j
                 mov     ecx, [ebp+ThreadHandle] ; Object
                 call    @ObfDereferenceObject@4 ; ObfDereferenceObject(x)

 loc_4BA123:                             ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+35↑j
                 pop     esi
                 mov     eax, ebx
                 pop     ebx
                 leave
                 retn    14h
 _NtQueueApcThread@20 endp

KeInitializeApc(ntosker.exe)–初始化_KAPC结构体

 __stdcall KeInitializeApc(x, x, x, x, x, x, x, x)
                 public _KeInitializeApc@32
 _KeInitializeApc@32 proc near           ; CODE XREF: NtSetTimer(x,x,x,x,x,x,x)+10D↓p
                                         ; IopCompleteRequest(x,x,x,x,x)+277↓p ...

 arg_0           = dword ptr  8
 arg_4           = dword ptr  0Ch
 arg_8           = dword ptr  10h
 arg_C           = dword ptr  14h
 arg_10          = dword ptr  18h
 arg_14          = dword ptr  1Ch
 arg_18          = byte ptr  20h
 arg_1C          = dword ptr  24h

 FUNCTION CHUNK AT .text:0040DE44 SIZE 0000000B BYTES
 FUNCTION CHUNK AT .text:0040E465 SIZE 00000011 BYTES

                 mov     edi, edi
                 push    ebp
                 mov     ebp, esp //初始化堆栈

                 mov     eax, [ebp+arg_0]     //获取第一个参数 : 指向已分配池的指针,_KAPC结构体指针
                 mov     edx, [ebp+arg_8]     //获取第三个参数 : 0
                 cmp     edx, 2
                 mov     ecx, [ebp+arg_4]     //获取第二个参数 : //要插入线程句柄

                 mov     word ptr [eax], 12h  //设置_KAPC.Type = 12h, APC默认类型为 12h
                 mov     word ptr [eax+2], 30h //设置结构体大小 _KAPC.Size = 30h
                 jz      loc_40DE44

 loc_40E2A9:                             ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)-43D↑j
                 mov     [eax+8], ecx         //保存要插入线程句柄
                 mov     ecx, [ebp+arg_C]     //获取第四的参数:释放APC函数地址
                 mov     [eax+14h], ecx       //设置释放APC的函数地址: _KAPC.KernelRoutine 

                 mov     ecx, [ebp+arg_10]    //获取第五的参数:0
                 mov     [eax+2Ch], dl        //设置挂在哪个队列 _KAPC.ApcStateIndex = 0

                 mov     [eax+18h], ecx       //_KAPC.RundownRoutine = 0

                 mov     ecx, [ebp+arg_14]    //获取参数先前模式
                 xor     edx, edx             //清零 edx
                 cmp     ecx, edx             //判断先前是否是在0环
                 mov     [eax+1Ch], ecx
                 jnz     loc_40E465           //先前模式 != 0,则运行在3环跳转
				
                 //先前运行环境为内核时
                 mov     [eax+2Dh], dl        //_KAPC.ApcMode = 0, 标志当前是内核APC为1, 用户为0
                 mov     [eax+20h], edx       //设置 _KAPC.NormalContext = 0  内核APC: NULL, 用户APC:真正的APC函数

 loc_40E2D1:                             ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+1EA↓j
                 mov     [eax+2Eh], dl        //_KAPC.Inserted = 0, 表示当前APC是否已经挂入队列。挂入前:0
                 pop     ebp
                 retn    20h                  //返回,并平衡堆栈
 _KeInitializeApc@32 endp

 //先前运行环境为用户时	
 loc_40E465:                             ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+3E↑j
                 mov     cl, [ebp+arg_18]   //获取参数1
                 mov     [eax+2Dh], cl      //_KAPC.ApcMode = 1, 标志当前是内核APC为1, 用户为0
                 mov     ecx, [ebp+arg_1C]  //获取参数 NormalContext
                 mov     [eax+20h], ecx     //设置 _KAPC.NormalContext的值为真正的APC函数  内核APC: NULL, 用户APC:真正的APC函数
                 jmp     loc_40E2D1         //返回

KeInsertQueueApc

 ; __stdcall KeInsertQueueApc(x, x, x, x)
                 public _KeInsertQueueApc@16
 _KeInsertQueueApc@16 proc near          ; CODE XREF: ExpTimerDpcRoutine(x,x,x,x)+26↓p
                                         ; IopCompleteRequest(x,x,x,x,x)+21E↓p ...

 LockHandle      = _KLOCK_QUEUE_HANDLE ptr -0Ch
 arg_0           = dword ptr  8
 arg_4           = dword ptr  0Ch
 arg_8           = dword ptr  10h
 arg_C           = dword ptr  14h

                 mov     edi, edi
                 push    ebp
                 mov     ebp, esp
                 sub     esp, 0Ch
                 push    ebx
                 push    esi
                 push    edi
				 // 初始化堆栈

                 mov     edi, [ebp+arg_0]     //初始化好的_KAPC 结构体指针
                 mov     esi, [edi+8]         //_KAPC + 8 = _KAPC.Thread 目标线程
                 lea     ecx, [esi+0E8h]       //esi + 0E8 = _KTHREAD + 0E8 = _KTHREAD.ApcQueueLock APC队列锁
                 lea     edx, [ebp+LockHandle]  
                 call    ds:__imp_@KeAcquireInStackQueuedSpinLockRaiseToSynch@8 ; KeAcquireInStackQueuedSpinLockRaiseToSynch(x,x)

                 xor     bl, bl               //清零 bl           
                 cmp     [esi+166h], bl       //esi+166h = _KTHREAD.ApcQueueable,用于表示是否可以向线程的APC队列中插入APC,0则不能插入
                 jz      short loc_40E453

                 mov     eax, [ebp+arg_4]     //第二个参数,APC函数的参数 SystemArgument1
                 mov     edx, [ebp+arg_C]     //第四个参数,0
                 mov     [edi+24h], eax       //_KAPC.SystemArgument1 = eax = SystemArgument1
                 mov     eax, [ebp+arg_8]     //第三个参数,APC函数的参数 SystemArgument2
                 mov     ecx, edi             //ecx = _KAPC 结构体指针
                 mov     [edi+28h], eax       //_KAPC.SystemArgument2 = eax = SystemArgument2
                 call    @KiInsertQueueApc@8 ; KiInsertQueueApc(x,x)
                 mov     bl, al               //al 返回是否成功

 loc_40E453:                             ; CODE XREF: KeInsertQueueApc(x,x,x,x)+28↑j
                 lea     ecx, [ebp+LockHandle] ; LockHandle
                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)  //释放锁
                 pop     edi
                 pop     esi
                 mov     al, bl
                 pop     ebx
                 leave
                 retn    10h
 _KeInsertQueueApc@16 endp

KiInsertQueueApc

KiInsertQueueApc函数说明:

1)根据KAPC结构中的ApcStateIndex找到对应的APC队列

2)再根据KAPC结构中的ApcMode确定是用户队列还是内核队列

3)将KAPC挂到对应的队列中(挂到KAPC的ApcListEntry)

4)再根据KAPC结构中的Inserted置1,标识当前的KAPC为已插入状态

修改KAPC_STATE(_KTHREAD)结构中的KernelApcPending/UserApcPending :

若插入用户的:

6)若UserApcPending = 1, 则说明当前线程正在等待状态,并且可以被APC唤醒,调用KiUnwaitThread函数唤醒线程。

6)若UserApcPending != 1, 则说明当前线程不能被唤醒或者未在等待状态等,则直接返回成功。

若插入内核的:

7)KernelApcPending = 1

8)若线程处在就绪状态则结束。

9)线程不在就绪状态,则判断是否在等待状态,若在等待状态并且 WaitIrql = 0,则调用KiUnwaitThread函数唤醒线程。

注:当线程处在由用户引起的等待状态下,并且线程可以被唤醒会将 UserApcPending = 1,否则为0,但是APC是插入成功了。若UserApcPending = 0时当前APC并不会立即被执行只能等待其他插入的APC使UserApcPending = 1时,就可一并执行。

KiUnwaitThread函数:将当前线程从等待链表中挂到就绪链表。

KiInsertQueueApc:

 ; __fastcall KiInsertQueueApc(x, x)
 @KiInsertQueueApc@8 proc near           ; CODE XREF: KeInsertQueueApc(x,x,x,x)+3B↓p
                                         ; KeSuspendThread(x)+57↓p ...

 var_4           = dword ptr -4

                 mov     edi, edi
                 push    ebp
                 mov     ebp, esp
                 push    ecx

                 mov     eax, ecx                   //ecx = eax = _KAPC结构体指针
                 cmp     byte ptr [eax+2Eh], 0      //eax+2Eh = _KAPC.Inserted,表示当前APC是否已经挂入队列。挂入前:0,挂入后 1
                 mov     ecx, [eax+8]               //ecx = _KAPC.Thread = 目标线程结构体
                 mov     [ebp+var_4], edx           //将edx = 0,保存到临时变量
                 jnz     loc_446A65                 // _KAPC.Inserted != 0,则跳转,即已经挂入队列了,直接返回

                 cmp     byte ptr [eax+2Ch], 3      //_KAPC.ApcStateIndex 判断将_KAPC挂到那个队列
                 jz      loc_446A69                 //等于3时跳转, 3为:插入的时候,当前进程ApcState。 中途ApcState的值可能会发生变化
				 
				 //初始化的时候,当前进程的ApcState:
			
 loc_40E39B:                             ; CODE XREF: KiInsertQueueApc(x,x)+386F9↓j
                 cmp     dword ptr [eax+1Ch], 0      //判断_KAPC.NormalRoutine 是否为空
                 movsx   edx, byte ptr [eax+2Ch]     //eax+2Ch = _KAPC.ApcStateIndex
                 push    ebx
                 push    esi
                 push    edi                         //保存值
                                                    
                 mov     edi, [ecx+edx*4+138h]       //_KTHREAD.ApcStatePointer + 4 * edx = _KTHREAD.ApcStatePointer[ApcStateIndex] = ApcState
													 //ApcState则总是表示线程当前使用的apc状态。							

                 mov     dl, [eax+2Dh]               //获取_KAPC.ApcMode的值,用来判断是内核APC还是用户APC
                 jnz     loc_40F034                  //_KAPC.NormalRoutine != 0 则跳转,进行APC插入

                 movsx   esi, dl                     //esi = dl = _KAPC.ApcMode
                 lea     edi, [edi+esi*8]            //ApcState + esi * 8 = ApcState.ApcListHead[0或1],获取APC队列地址
                 mov     esi, [edi+4]                //ApcState.ApcListHead[0或1].Blink

 loc_40E3BF:                             ; CODE XREF: KiInsertQueueApc(x,x)+1D554↓j
                 cmp     esi, edi
                 jnz     loc_42B8C0

 loc_40E3C7:                             ; CODE XREF: KiInsertQueueApc(x,x)+1D54B↓j

				 //挂入APC队列
                 mov     ebx, [esi] 
                 lea     edi, [eax+0Ch]
                 mov     [edi], ebx
                 mov     [edi+4], esi
                 mov     [ebx+4], edi
                 mov     [esi], edi

 loc_40E3D6:                             ; CODE XREF: KiInsertQueueApc(x,x)+CE2↓j
                                         ; KiInsertQueueApc(x,x)+185D0↓j
                 movsx   edi, byte ptr [eax+2Ch]   //获取 eax+2Ch = _KAPC.ApcStateIndex,挂入那个队列(即apc插入状态)
                 mov     byte ptr [eax+2Eh], 1     //eax+2Eh = _KAPC.Inserted = 1 设置apc为已经挂入队列
                 movzx   esi, byte ptr [ecx+165h]  //ecx+165h = _KTHREAD.ApcStateIndex,获取当前线程处于正常状态还是挂靠状态

                 cmp     edi, esi     //cmp edi= _KAPC.ApcStateIndex(挂入哪个对列用户、内核),  _KTHREAD.ApcStateIndex(挂入哪个ApcState,正常、挂靠)
                 pop     edi
                 pop     esi
                 pop     ebx   //保存寄存器
                 jnz     short loc_40E408  //不相等则跳转,返回成功

                 test    dl, dl
                 jnz     loc_40F060        //dl != 0则跳转,dl = _KAPC.ApcMode = 1 ,为用户APC
				
				 //内核
                 mov     dl, [ecx+2Dh]     //_KTHREAD.State
                 cmp     dl, 2             //判断是否为  _KTHREAD.State 是否为 2  就绪状态
                 mov     byte ptr [ecx+49h], 1  //_KTHREAD._KAPC_STATE.KernelApcPending = 1
                 jnz     short loc_40E476    //_KTHREAD.State != 2, 
                 mov     cl, 1
                 call    ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)

 //成功返回
 loc_40E408:                             ; CODE XREF: KiInsertQueueApc(x,x)+71↑j
                                         ; KiInsertQueueApc(x,x)+100↓j ...
                 mov     al, 1
                 leave
                 retn
 @KiInsertQueueApc@8 endp

//返回失败
loc_446A65:                             ; CODE XREF: KiInsertQueueApc(x,x)+12↑j
                 xor     al, al
                 leave
                 retn



//插入的时候,当前线程的ApcState。 中途ApcState的值可能会发生变化
loc_446A69:                             ; CODE XREF: KiInsertQueueApc(x,x)+1C↑j
             	mov     dl, [ecx+165h]  //ecx+165h = _KTHREAD.ApcStateIndex 的值,获取目标线程状态
             	mov     [eax+2Ch], dl   //设置APC状态,eax+2Ch = _KAPC.ApcStateIndex = _KTHREAD.ApcStateIndex,
										//再将值设为用于表示是内核APC还是用户APC
             	jmp     loc_40E39B

 loc_40F034:                             ; CODE XREF: KiInsertQueueApc(x,x)+37↑j
                test    dl, dl
                jz      short loc_40F045  //dl = 0则跳转,dl = _KAPC.ApcMode = 0 ,为内核APC

				//用户APC
                cmp     dword ptr [eax+14h], offset _PsExitSpecialApc@20 ; PsExitSpecialApc(x,x,x,x,x) //_KAPC.KernelRoutine 判断是否存在
                jz      loc_426930   //_KAPC.KernelRoutine 已经赋值过了则跳转,去插入APC
				//否则则继续执行:loc_40F045,插入到内核队列
 
 //将初始化好的APC结构体插入到内核队列
 loc_40F045:                             ; CODE XREF: KiInsertQueueApc(x,x)+CBD↑j
                 movsx   ebx, dl             //ebx = dl = _KAPC.ApcMode = 0
                 lea     edi, [edi+ebx*8]    //ApcState + 0 * 8 = ApcState.ApcListHead[0],获取内核APC队列地址
                 mov     ebx, [edi+4]        //ApcState.ApcListHead.Blink
                 lea     esi, [eax+0Ch]      //获取_KAPC.ApcListEntry(初始化的KAPC)地址
				
				 //将初始化好的APC插入到指定线程内核Apc队列中最前面:
                 mov     [esi], edi          //_KAPC.ApcListEntry.Flink = _KAPC.ApcListEntry.Flink赋值为APC链表开始节点
                 mov     [esi+4], ebx        //_KAPC.ApcListEntry.Blink赋值为APC链表之前的第一个节点
                 mov     [ebx], esi          // ApcState.ApcListHead.Blink(新节点) = _KAPC.ApcListEntry.Flink(原APC队列第一个节点)  
											 //将原本第一个节点放到新插入节点的后面
                 mov     [edi+4], esi        //将头节点的下个节点地址赋值为,新插入节点的地址

                 jmp     loc_40E3D6

 //将初始化好的APC结构体插入到用户队列
 loc_426930:                             ; CODE XREF: KiInsertQueueApc(x,x)+CC6↑j
                 movsx   ebx, dl         //ebx = dl = _KAPC.ApcMode = 1
                 mov     byte ptr [ecx+4Ah], 1 //ecx+4Ah = _KTHREAD.ApcState.UserApcPending 设置为1:存在未执行的APC函数	

                 lea     edi, [edi+ebx*8]    //ApcState + 1 * 8 = ApcState.ApcListHead[1],获取用户APC队列地址
                 mov     ebx, [edi]
                 lea     esi, [eax+0Ch]     //获取_KAPC.ApcListEntry(初始化的KAPC)地址
				 
				 //将初始化好的APC插入到指定线程用户Apc队列中最前面:
                 mov     [esi], ebx
                 mov     [esi+4], edi
                 mov     [ebx+4], esi
                 mov     [edi], esi

                 jmp     loc_40E3D6

 loc_40F060:                             ; CODE XREF: KiInsertQueueApc(x,x)+75↑j
                 cmp     byte ptr [ecx+2Dh], 5    //_KTHREAD.State    5 , 判断线程是否为等待状态
                 jnz     loc_40E408  //_KTHREAD.State  !=  5 

                 cmp     byte ptr [ecx+59h], 1   //_KTHREAD.WaitMode  //判断线程是否是用户引起的等待 
                 jnz     loc_40E408  //_KTHREAD.WaitMode != 1 

                 cmp     byte ptr [ecx+164h], 0  //_KTHREAD.Alertable  //判断线程是否可以被吵醒
                 jz      loc_430284  //_KTHREAD.Alertable != 0

				 //继续走 loc_40F081

 loc_40F081:                             ; CODE XREF: KiInsertQueueApc(x,x)+21F15↓j
                 mov     byte ptr [ecx+4Ah], 1 //_KTHREAD._KAPC_STATE.UserApcPending = 1, 存在可以执行的用户函数
                 push    0
                 mov     edx, 0C0h
                 jmp     loc_40E492


 loc_430284:                             ; CODE XREF: KiInsertQueueApc(x,x)+D02↑j
                 cmp     byte ptr [ecx+4Ah], 0    //_KTHREAD._KAPC_STATE.UserApcPending, 没有可以执行的用户函数
                 jz      loc_40E408               //没有可以执行的用户函数, 成功返回
                 jmp     loc_40F081



 loc_40E492:                             ; CODE XREF: KiInsertQueueApc(x,x)+D13↓j
                 push    [ebp + var_4]   //[ebp + var_4] = 0
                 call    @KiUnwaitThread@16 ; KiUnwaitThread(x,x,x,x)   //唤醒线程函数
                 jmp     loc_40E408  //返回成功

 //内核会调
 loc_40E476:                             ; CODE XREF: KiInsertQueueApc(x,x)+85↑j
                 cmp     dl, 5                 //_KTHREAD.State    5    判断线程是否为等待状态
                 jnz     short loc_40E408      //_KTHREAD.State  !=  5  返回成功
                 cmp     byte ptr [ecx+58h], 0  //_KTHREAD.WaitIrql
                 jnz     short loc_40E408    //_KTHREAD.WaitIrql  !=  0  返回成功

                 xor     edx, edx            //清零 edx
                 cmp     [eax+1Ch], edx      //_KTHREAD.StackLimit, 判断堆栈范围是否为零 
                 jnz     loc_41A8F8          //_KTHREAD.StackLimit != 0, 跳转
				 //继续走 loc_40E48C

 loc_40E48C:                             ; CODE XREF: KiInsertQueueApc(x,x)+C58E↓j
                 push    edx
                 mov     edx, 100h
				 //继续走 loc_40E492


 //内核和用户都会调过来用来唤醒线程
 loc_40E492:                             ; CODE XREF: KiInsertQueueApc(x,x)+D13↓j
                 push    [ebp+var_4]
                 call    @KiUnwaitThread@16 ; KiUnwaitThread(x,x,x,x)
                 jmp     loc_40E408  //返回成功

 //内核会调
 loc_41A8F8:                             ; CODE XREF: KiInsertQueueApc(x,x)+10D↑j
                 cmp     [ecx+0D4h], edx   //_KTHREAD.KernelApcDisable, 判断内核ACP是否被禁用,1则禁用,edx = 0
                 jnz     loc_40E408        //内核ACP被禁用则返回
                 cmp     [ecx+48h], dl     //_KTHREAD._KAPC_STATE.KernelApcInProgress, 内核APC的程序是否正在执行, 0就是没执行。
                 jz      loc_40E48C        //_KTHREAD._KAPC_STATE.KernelApcInProgress == 0, 则去执行一下,将线程加入到就绪队列
                 jmp     loc_40E408        //返回成功

调用APC函数分析

在进行线程切换、系统调用、中断或者异常时都会调用执行APC,内核APC直接在0环处理,而3环的APC要返回3环处理。

KiDeliverApc内核APC函数执行流程:

1)判断第一个链表是否为空(第一个链表为内核APC链表)

2)判断KTHREAD.ApcState.KernelApcInProgress是否为1,即是否存在正在执行的APC函数

3)判断是否禁用内核APC(esi+0D4h = _KTHREAD.KernelApcDisable, 判断内核ACP是否被禁用,1则禁用)

4)将当前KAPC结构体从链表中摘除

5)执行KAPC.KernelRoutine指定的函数,释放KAPC结构体占用的空间

6)将KTHREAD.ApcState.KernelApcInProgress 设置为1,表示正在执行内核APC

7)执行真正的内核APC函数(KAPC.NormalRputine)

8)执行完毕将KTHREAD.ApcState.KernelApcInProgress改为1

9)循环判断是否存在下一个,继续执行,直到链表为空。

注:若KAPC.NormalRputine为空,则会将当前KAPC结构体从链表中摘除,执行KAPC.KernelRoutine指定的函数,释放KAPC结构体占用的空间,后循环下一个内核APC。

调用用户APC时,会先执行内核APC,但是调用内核APC时不会执行用户APC。

KiDeliverApc用户APC函数执行流程:

1)判断用户APC链表是否为空

2)判断第一个参数是为1,为1说明处理用户APC和内核APC

3)判断ApcState.UserApcPending(是否正在执行用户APC)是否为1

4)将ApcState.UserApcPending设置为0,表示正在处理用户APC链表操作 将当前APC从用户队列中拆除

5)调用函数(KAPC.KernelRoutine)释放KAPC结构体内存空间

6) 调用KiInitializeUserApc函数

KiServiceExit中调用KiDeliverApc (内核中)— 系统调用、中断或者异常中调用执行APC函数:

push    eax      //eax保存提供CR3的进程结构体_KPROCESS,挂靠即挂靠进程的CR3,否则即自己的
sti
push    ebx
push    0
push    1        //0处理内核, 1处理内核和用户APC
call    _KiDeliverApc@12 ; KiDeliverApc(x,x,x)
pop     ecx             ; NewIrql
call    ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)

KiSwapThread中调用KiDeliverApc (内核中) — 线程切换中调用执行APC函数:

loc_415ADB:                             ; CODE XREF: KiSwapThread()+46↑j
                 mov     cl, 1           ; NewIrql
                 call    esi ; KfLowerIrql(x) ; KfLowerIrql(x)
                 xor     eax, eax
                 push    eax
                 push    eax
                 push    eax      //0处理内核, 1处理内核和用户APC
                 call    _KiDeliverApc@12 ; KiDeliverApc(x,x,x)
                 xor     cl, cl
                 jmp     loc_40510B

KiDeliverApc 解析

 __stdcall KiDeliverApc(x, x, x)
                 public _KiDeliverApc@12
 _KiDeliverApc@12 proc near              ; CODE XREF: KiUnlockDispatcherDatabase(x)+A3↑p
                                         ; _KiServiceExit+53↑p ...

 LockHandle      = _KLOCK_QUEUE_HANDLE ptr -28h
 var_1C          = dword ptr -1Ch
 BugCheckParameter1= dword ptr -18h
 var_10          = dword ptr -10h
 var_C           = dword ptr -0Ch
 var_8           = dword ptr -8
 var_4           = dword ptr -4
 arg_0           = byte ptr  8
 arg_4           = dword ptr  0Ch
 arg_8           = dword ptr  10h

 ; FUNCTION CHUNK AT .text:0040F091 SIZE 00000086 BYTES
 ; FUNCTION CHUNK AT .text:00446A2D SIZE 00000008 BYTES
 ; FUNCTION CHUNK AT .text:00446A3A SIZE 0000000C BYTES
 ; FUNCTION CHUNK AT .text:00446A4B SIZE 0000001A BYTES

                 mov     edi, edi
                 push    ebp
                 mov     ebp, esp
                 sub     esp, 28h	 //初始化函数堆栈


                 mov     ecx, [ebp+arg_8] //函数接受的第3个参数, ecx = _Trap_Frame
										  //ebp + 10H = ebg + 返回地址 + 第一个参数 + 第二个参数 + 第三个参数
									
                 test    ecx, ecx         //判断DbgEbp是否为空
                 jz      short loc_40E304 //为零则跳转

                 mov     edx, [ecx+68h]   //获取Eip的值,_Trap_Frame.Eip (三环的)
                 mov     eax, offset _ExpInterlockedPopEntrySListResume@0 ; ExpInterlockedPopEntrySListResume() //获取当前函数地址
                 cmp     edx, eax  //判断Eip是否等于ExpInterlockedPopEntrySListResume的地址
                 jb      short loc_40E304 

                 cmp     edx, offset _ExpInterlockedPopEntrySListEnd@0 ; ExpInterlockedPopEntrySListEnd() 
									//判断Eip是否等于ExpInterlockedPopEntrySListEnd的地址
                 jbe     loc_446A2D

 loc_40E304:                             ; CODE XREF: KiDeliverApc(x,x,x)+D↑j
                                         ; KiDeliverApc(x,x,x)+19↑j ...
 
                 push    ebx             //保存ebx   
                 push    esi			 //保存esi
                 push    edi			 //保存edi

                 mov     eax, large fs:124h  //KPCR.CurrentThread 获取当前线程结构体
                 mov     esi, eax          

				 //esi = _KTHREAD

                 mov     eax, [esi+134h]    //eax = _KTHREAD + 134H = _KTHREAD.TrapFrame 当前线程3环的堆栈环境。
                 mov     [ebp+var_1C], eax  //临时保存
                 mov     eax, [esi+44h]     //_KTHREAD.ApcState.UserApcPending ,APC队列中是否存在未执行的用户函数,为1则存在。
                 mov     [esi+134h], ecx    //将新的TrapFrame 写入 _KTHREAD.TrapFrame

                 lea     ecx, [esi+0E8h]    //获取_KTHREAD.ApcQueueLock 的地址
                 lea     edx, [ebp+LockHandle]  //保存到临时变量
                 mov     [ebp+BugCheckParameter1], eax
                 call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x) //获取一个排队的自旋锁
                 mov     byte ptr [esi+49h], 0 //_KTHREAD.ApcState.KernelApcPending 将KernelApcPending置为0,即没有未执行的内核APC函数
                 lea     ebx, [esi+34h]        //获取 _KTHREAD.ApcState.ApcListHead[0].ApcListEntry 

 loc_40E33A:                             ; CODE XREF: IopCompleteRequest(x,x,x,x,x)+218↓j
                                         ; IopCompleteRequest(x,x,x,x,x)+4E95↓j ...

                 cmp     [ebx], ebx     //判断链表是否为空
                 jnz     loc_415A54     //ebx 不相等则跳转,内核APC列表不为空,跳转去执行内核API
                 lea     ecx, [esi+3Ch] //ecx = _KTHREAD.ApcListHead.[1].ApcListEntry.Flink , 获取用户内核链表的地址
                 mov     eax, [ecx]     
                 cmp     eax, ecx       //判断链表是否为空
										
                 jnz     loc_40F091     //用户APC列表不为空,则跳转

 loc_40E34F:                             ; CODE XREF: KiDeliverApc(x,x,x)+DB8↓j
                                         ; KiDeliverApc(x,x,x)+DC2↓j ...
                 lea     ecx, [ebp+LockHandle] ; LockHandle
                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)

 loc_40E358:                             ; CODE XREF: KiDeliverApc(x,x,x)+E35↓j
                                         ; KiDeliverApc(x,x,x)+38764↓j ...
                 mov     ecx, [ebp+BugCheckParameter1]
                 cmp     [esi+44h], ecx
                 jnz     loc_446A4B
                 mov     eax, [ebp+var_1C]
                 pop     edi
                 mov     [esi+134h], eax
                 pop     esi
                 pop     ebx
                 leave
                 retn    0Ch
 _KiDeliverApc@12 endp

loc_446A2D:                             ; CODE XREF: KiDeliverApc(x,x,x)+21↑j
                 mov     [ecx+68h], eax  //修改 _Trap_Frame.Eip = ExpInterlockedPopEntrySListResume
                 jmp     loc_40E304

loc_415A54

内核APC链表不为空跳转:

 loc_415A54:                             ; CODE XREF: KiDeliverApc(x,x,x)+5F↑j
				
				 此时 : ebx = _KTHREAD.ApcState.ApcListHead[0]
				
                 mov     eax, [ebx]      //eax = [_KTHREAD.ApcState.ApcListHead[0]] = _KAPC.ApcListEntry
                 lea     edi, [eax-0Ch]  //获取 _KAPC 开始的位置

				 //此时 	edi = _KAPC,下面用来读取APC的值,并保存到局部变量中			

                 mov     ecx, [edi+14h]             //获取用来销毁APC的函数地址,edi+14h(指向一个函数地址) = _KAPC.KernelRoutine
                 mov     [ebp+ms_exc.exc_ptr], ecx  //保存
                 mov     ecx, [edi+1Ch]             //内核APC函数(这是内核APC队列),edi+1Ch(指向一个函数地址) = _KAPC.NormalRoutine
                 test    ecx, ecx                   //这是内核apc,所以_KAPC.NormalRoutine保存真正的内核APC函数,判断当前函数是否为空
                 mov     [ebp+ms_exc.registration.TryLevel], ecx //保存
                 mov     edx, [edi+20h]             //内核APC: NULL, 用户APC:真正的APC函数 edi+20h(指向一个函数地址) = _KAPC.NormalContext
                 mov     [ebp+ms_exc.registration.Next], edx //保存
                 mov     edx, [edi+24h]             //获取APC函数的参数 _KAPC.SystemArgument1
                 mov     [ebp+ms_exc.registration.ExceptionHandler], edx //保存
                 mov     edx, [edi+28h]             //获取APC函数的参数 _KAPC.SystemArgument2
                 mov     [ebp+ms_exc.registration.ScopeTable], edx  //保存
                 jnz     loc_41A6B8                 //_KAPC.NormalRoutine内核APC函数不为空则跳转

				 //当内核APC函数 为空时:
				 //(注:下面是断链操作)
				
                 mov     ecx, [eax]             //[eax] = [_KAPC.ApcListEntry.Flink] 获取前一个节点(指向_KAPC)的位置 (_LIST_ENTRY)
                 mov     eax, [eax+4]           //[eax + 4] = [_KAPC.ApcListEntry.Blink] 获取后一个节点(指向_KAPC)的位置 (_LIST_ENTRY)
												
                 mov     [eax], ecx             //此时eax是后一个节点的_KAPC.ApcListEntry,[eax] = [_KAPC.ApcListEntry.Flink]
											    //将后一个节点的 Flink 赋值为 前一个节点的值(_KAPC.ApcListEntry) 

                 mov     [ecx+4], eax			//此时ecx是上一个节点的_KAPC.ApcListEntry,[ecx+4] = [_KAPC.ApcListEntry.Blink]
												//将前一个节点的 Blink 赋值为 后一个节点的值(_KAPC.ApcListEntry) 
				
				 //上面4步将内核APC函数为空的_KAPC结构体在链表中断掉。
					

                 lea     ecx, [ebp+LockHandle] //从临时变量中获取 _KTHREAD.ApcQueueLock 的地址
                 mov     byte ptr [edi+2Eh], 0 //edi+2Eh = _KAPC.Inserted 修改为 0, 即挂入前
                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
				
				 //调用函数,Push参数
                 lea     eax, [ebp+ms_exc.registration.ScopeTable] //获取APC函数的参数 _KAPC.SystemArgument2
                 push    eax
                 lea     eax, [ebp+ms_exc.registration.ExceptionHandler] //获取APC函数的参数 _KAPC.SystemArgument1
                 push    eax
                 lea     eax, [ebp+ms_exc.registration]                   //临时变量地址
                 push    eax
                 lea     eax, [ebp+ms_exc.registration.TryLevel]          //用户APC总入口 或者 真正的内核APC函数,edi+1Ch(指向一个函数地址) = _KAPC.NormalRoutine
                 push    eax
                 push    edi                                              //当前线程的 _KTHREAD
                 call    [ebp+ms_exc.exc_ptr]                             //调用用来销毁APC的函数


                 lea     edx, [ebp+LockHandle] //从临时变量中获取 _KTHREAD.ApcQueueLock 的地址
                 lea     ecx, [esi+0E8h]       // esi+0E8h = _KTHREAD.ApcQueueLock
                 call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
                 jmp     loc_40E33A                                       //再次去判断内核链表

loc_41A6B8 执行内核APC函数

 loc_41A6B8:                             ; CODE XREF: IopCompleteRequest(x,x,x,x,x)+1D8↑j
 ; __unwind { // __SEH_prolog

				 //此时 esi = _KTHREAD

                 cmp     byte ptr [esi+48h], 0     //判断 内核APC的程序是否正在执行 KTHREAD.ApcState.KernelApcInProgress
                 jnz     loc_40E34F                //横在执行则跳转
                 cmp     dword ptr [esi+0D4h], 0  //esi+0D4h = _KTHREAD.KernelApcDisable, 判断内核ACP是否被禁用 
                 jnz     loc_40E34F               //被禁用则跳转
				 
				 //(注:下面是断链操作)
                 mov     ecx, [eax]               //[eax] = [_KAPC.ApcListEntry.Flink] 获取前一个节点(指向_KAPC)的位置 (_LIST_ENTRY)
                 mov     eax, [eax+4]			  //[eax + 4] = [_KAPC.ApcListEntry.Blink] 获取后一个节点(指向_KAPC)的位置 (_LIST_ENTRY)

                 mov     [eax], ecx               //此时eax是后一个节点的_KAPC.ApcListEntry,[eax] = [_KAPC.ApcListEntry.Flink]
												  //将后一个节点的 Flink 赋值为 前一个节点的值(_KAPC.ApcListEntry) 

                 mov     [ecx+4], eax             //此时ecx是上一个节点的_KAPC.ApcListEntry,[ecx+4] = [_KAPC.ApcListEntry.Blink]
												  //将前一个节点的 Blink 赋值为 后一个节点的值(_KAPC.ApcListEntry)
				 //上面4步将当前内核APC函数_KAPC结构体在链表中断掉。
				
                 lea     ecx, [ebp+LockHandle]    //从临时变量中获取 _KTHREAD.ApcQueueLock 的地址

				 //此时 	edi = _KAPC					

                 mov     byte ptr [edi+2Eh], 0   //edi+2Eh = _KAPC.Inserted 修改为 0, 即挂入前
                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
				 
				 //调用销毁内核API函数,Push参数
                 lea     eax, [ebp+ms_exc.registration.ScopeTable] //获取APC函数的参数 _KAPC.SystemArgument2
                 push    eax
                 lea     eax, [ebp+ms_exc.registration.ExceptionHandler] //获取APC函数的参数 _KAPC.SystemArgument1
                 push    eax
                 lea     eax, [ebp+ms_exc.registration]                  //临时变量地址
                 push    eax
                 lea     eax, [ebp+ms_exc.registration.TryLevel] //用户APC总入口 或者 真正的内核APC函数,edi+1Ch(指向一个函数地址) = _KAPC.NormalRoutine
                 push    eax
                 push    edi                                     //当前线程的 _KTHREAD
                 call    [ebp+ms_exc.exc_ptr]    //调用用来销毁APC的函数

                 cmp     [ebp+ms_exc.registration.TryLevel], 0   //判断函数地址是否为空
                 jz      short loc_41A723                        //函数地址为空则跳转

                 xor     cl, cl                                  
                 mov     byte ptr [esi+48h], 1                   //将内核API程序设置为正在执行状态

                 call    ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
				 
				 //调用内核API函数 
                 push    [ebp+ms_exc.registration.ScopeTable]         //Push  APC函数的参数 _KAPC.SystemArgument2
                 push    [ebp+ms_exc.registration.ExceptionHandler]   //Push  APC函数的参数 _KAPC.SystemArgument1
                 push    [ebp+ms_exc.registration.Next]               //内核APC: NULL, 用户APC:真正的APC函数 edi+20h(指向一个函数地址) = _KAPC.NormalContext
                 call    [ebp+ms_exc.registration.TryLevel]           //执行 真正的内核APC函数,_KAPC.NormalRoutine 

                 mov     cl, 1           ; NewIrql
                 call    ds:__imp_@KfRaiseIrql@4 ; KfRaiseIrql(x)
                 mov     [ebp+LockHandle.OldIrql], al

loc_41A723:                             ; CODE XREF: IopCompleteRequest(x,x,x,x,x)+4E5D↑j
                 lea     edx, [ebp+LockHandle] ; LockHandle
                 lea     ecx, [esi+0E8h] ; SpinLock
                 call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
                 mov     byte ptr [esi+48h], 0                        // KTHREAD.ApcState.KernelApcInProgress 置为 0 即没有正在执行的
                 jmp     loc_40E33A                                   //再次去判断内核链表  

loc_40F091 执行用户APC函数

loc_40F091:                             ; CODE XREF: KiDeliverApc(x,x,x)+6C↑j
                 cmp     [ebp+arg_0], 1    //Ebp减8即push进函数的第一个参数,所以判断是否要调用用户APC
                 jnz     loc_40E34F
				
				 //esi = _KTHREAD					

                 cmp     byte ptr [esi+4Ah], 0  //esi+4Ah = _KTHREAD.ApcState.UserApcPending, 判断APC队列中是否存在未执行的用户函数,为1则存在。
                 jz      loc_40E34F             //不存在未执行的用户函数则结束

                 mov     byte ptr [esi+4Ah], 0  //将esi+4Ah = _KTHREAD.ApcState.UserApcPending 设置为0, 防止再次进来

				 //此时eax为_KAPC结构体的_KAPC.ApcListEntry	
				 
                 lea     edi, [eax-0Ch]         //_KAPC结构体的开头位置

				 //此时edi 保存 _KAPC结构体的开头位置

                 mov     ecx, [edi+1Ch]         //_KAPC的_KAPC.NormalRoutine,用户APC总入口,因为这是用户调用(这是用户APC队列)
                 mov     ebx, [edi+14h]			//_KAPC的_KAPC.KernelRoutine ,用于获取释放Apc的函数

                 mov     [ebp+var_4], ecx       //保存到临时变量
                 mov     ecx, [edi+20h]         //_KAPC的_KAPC.NormalContext 用户APC:真正的APC函数(这是用户APC队列)
                 mov     [ebp+var_10], ecx      //保存到临时变量
                 mov     ecx, [edi+24h]         //_KAPC的_KAPC.SystemArgument1 APC函数参数
                 mov     [ebp+var_C], ecx       //保存到临时变量
                 mov     ecx, [edi+28h]         //_KAPC的_KAPC.SystemArgument2 APC函数参数
                 mov     [ebp+var_8], ecx       //保存到临时变量
				 
				 //将当前_KAPC结构体从队列中断去,即断链
                 mov     ecx, [eax]
                 mov     eax, [eax+4]
                 mov     [eax], ecx
                 mov     [ecx+4], eax
				 

                 lea     ecx, [ebp+LockHandle]   //从临时变量中获取_KTHREAD.ApcQueueLock 的地址
                 mov     byte ptr [edi+2Eh], 0
                 call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
				 
				 //销毁APC函数调用
                 lea     eax, [ebp+var_8]     //_KAPC的_KAPC.SystemArgument2 APC函数参数
                 push    eax
                 lea     eax, [ebp+var_C]     //_KAPC的_KAPC.SystemArgument1 APC函数参数
                 push    eax
                 lea     eax, [ebp+var_10]    //_KAPC的_KAPC.NormalContext 用户APC:真正的APC函数(这是用户APC队列)
                 push    eax
                 lea     eax, [ebp+var_4]     //_KAPC的_KAPC.NormalRoutine,用户APC总入口,因为这是用户调用(这是用户APC队列)
                 push    eax                  //_KAPC.ApcListEntry
                 push    edi                  //_KAPC结构体
                 call    ebx                  //调用用于释放_KAPC的函数
                 cmp     [ebp+var_4], 0       //判断KiDeliverApc函数的返回地址
                 jz      loc_446A3A           

                 push    [ebp+var_8]          //_KAPC的_KAPC.SystemArgument2 APC函数参数
                 push    [ebp+var_C]          //_KAPC的_KAPC.SystemArgument1 APC函数参数
                 push    [ebp+var_10]         //_KAPC的_KAPC.NormalContext 用户APC:真正的APC函数(这是用户APC队列)
                 push    [ebp+var_4]          //_KAPC的_KAPC.NormalRoutine,用户APC总入口,因为这是用户调用(这是用户APC队列)

                 push    [ebp+arg_8]          //KiDeliverApc函数接受的第3个参数, _Trap_Frame
                 push    [ebp+arg_4]          //KiDeliverApc函数接受的第4个参数, 保存提供CR3的进程结构体_KPROCESS,挂靠即挂靠进程的CR3,否则即自己的
                 call    _KiInitializeUserApc@24 ; KiInitializeUserApc(x,x,x,x,x,x)
                 jmp     loc_40E358

用户: _KiInitializeUserApc 解析

线程进0环时,原来的运行环境(寄存器栈顶等)保存到_Trap_Frame结构体中,如果要提前返回3环去处理用户APC,就必须要修改_Trap_Frame结构体

比如:进0环时的位置存储在EIP中,现在要提前返回,而且返回的并不是原来的位置,那就意味着必须要修改EIP为新的返回位置。还有堆栈ESP,也要修改为处理APC需要的堆栈。因为处理完APC后要返回原来的位置继续执行,即处理完APC后若要重新返回三环所在的位置,就要从之前_Trap_Frame结构体中保存的EIP返回,所以KiInitializeUserApc要做的第一件事就是备份:将原来的_Trap_Frame结构体的值备份到一个新的结构体中(CONTEXT),这个功能有其子函数KeContextFromKframes完成。

注:_Trap_Frame结构体中EIP所在位置,即返回三环执行的位置。

准备用户层执行环境:

Windows想了一个办法,把Context结构体和APC需要的参数,直接存到三环的堆栈里如图(即context + APC4个参数 = 2DC):

2DC:	NormalRoutione    用户APC总入口
2D8:	NormalContext     真正的APC函数
2D4:	SystemArgument1   参数
2D0:	SystemArgument2   参数

2CC:	0x00000000   //context结构体
		0x00000000
		0x00000000
		0x00000000
		0x00000000
		0x00000000
		...
		0x11111111  //此为原用户空间堆栈
		0x11111111
		0x11111111    
		0x11111111
		0x11111111
		0x11111111
		...

windows之所以将Context保存到3环,是方便三环去读写Context的值,在返回三环时会将context的值原封不动的还给0环。

_KiInitializeUserApc:

 __stdcall KiInitializeUserApc(x, x, x, x, x, x)
 _KiInitializeUserApc@24 proc near       ; CODE XREF: KiDeliverApc(x,x,x)+E30↑p

 ExceptionRecord = _EXCEPTION_RECORD ptr -34Ch
 var_2FC         = dword ptr -2FCh
 var_2F8         = dword ptr -2F8h
 var_2F4         = dword ptr -2F4h
 BugCheckParameter3= dword ptr -2F0h
 var_2EC         = dword ptr -2ECh
 var_2E8         = dword ptr -2E8h
 var_228         = dword ptr -228h
 var_224         = dword ptr -224h
 var_1C          = dword ptr -1Ch
 ms_exc          = CPPEH_RECORD ptr -18h
 arg_0           = dword ptr  8
 arg_4           = dword ptr  0Ch
 arg_8           = dword ptr  10h
 arg_C           = dword ptr  14h
 arg_10          = dword ptr  18h
 arg_14          = dword ptr  1Ch

 ; FUNCTION CHUNK AT .text:0042EBAF SIZE 00000009 BYTES
 ; FUNCTION CHUNK AT .text:00446D2F SIZE 0000001F BYTES
 ; FUNCTION CHUNK AT .text:00446D53 SIZE 00000012 BYTES
 ; FUNCTION CHUNK AT .text:00446D6A SIZE 0000002E BYTES

 __unwind { // __SEH_prolog
                 push    33Ch
                 push    offset stru_40F338
                 call    __SEH_prolog

                 mov     eax, ds:___security_cookie

                 mov     [ebp+var_1C], eax       //eax保存到临时变量
                 mov     eax, [ebp+arg_0]        //eax = 保存提供CR3的进程结构体_KPROCESS,挂靠即挂靠进程的CR3,否则即自己的

				 //此时 eax = _KPROCESS			
                 mov     [ebp+var_2F4], eax      //保存到临时变量

                 mov     ebx, [ebp+arg_4]        //第3个参数, _Trap_Frame
                 mov     [ebp+BugCheckParameter3], ebx  //保存到临时变量

                 test    byte ptr [ebx+72h], 2
                 jnz     loc_40F328

                 mov     [ebp+var_2E8], 10017h    //10017h保存到临时变量 context地址,即向下占用C8h个字节
                 lea     ecx, [ebp+var_2E8]       //获取当前临时变量的地址
                 push    ecx                      //将地址当作参数
                 push    eax                      //参数 _KPROCESS
                 push    ebx                      //参数 _Trap_Frame
                 call    _KeContextFromKframes@12 ; KeContextFromKframes(x,x,x) //将_Trap_Frame保存到context中

                 and     [ebp+ms_exc.registration.TryLevel], 0

                 mov     eax, 2DCh
                 mov     [ebp+var_2F8], eax
                 mov     esi, [ebp+var_224]  //获取 context中的变量,
											 //var_2E8 - var_224 = 2E8 - 224 = C4 = _CONTEXT.Esp 这个ESP是指向三环原来栈顶的位置

                 and     esi, 0FFFFFFFCh     //对Esp进行4字节对齐
                 sub     esi, eax            //提升三环堆栈2DCh,用来保存Context和APC需要的4个参数的地址

				 //esi保存三环提升后堆栈栈顶

                 mov     [ebp+var_2EC], esi  //保存到临时变量中 var_2E8 向下 C8h个字节为 context结构体

                 push    4               ; Alignment      //对齐
                 push    eax             ; Length        //2DCh 大小 
                 push    esi             ; Address      //三环提升后堆栈栈顶
                 call    _ProbeForWrite@12 ; ProbeForWrite(x,x,x)
		
				 //赋值context到三环		
		
                 lea     edi, [esi+10h]      //esi + 10 = 三环Context的位置      
                 mov     ecx, 0B3h
                 lea     esi, [ebp+var_2E8]  //临时变量Context的位置,也是0环准备给三环赋值的context
                 rep movsd                   //esi移动到edi,即给三环Context赋值

                 mov     dword ptr [ebx+6Ch], 1Bh  //_KTrap_Frame.SegCs
                 push    23h 
                 pop     eax                       //eax = 23h
                 mov     [ebx+78h], eax            //赋值_KTrap_Frame.HardwareSegSs
                 mov     [ebx+38h], eax            //赋值_KTrap_Frame.SegDs
                 mov     [ebx+34h], eax            //赋值_KTrap_Frame.SegEs
                 mov     dword ptr [ebx+50h], 3Bh  //赋值_KTrap_Frame.SegFs
                 and     dword ptr [ebx+30h], 0    //赋值_KTrap_Frame.SegGs
                 mov     ecx, [ebp+var_228]        //获取var_2E8 - var_228 = 2E8 - 228 = C0 = _CONTEXT.EFlag 
                 test    ecx, 20000h         
                 jnz     loc_446D2F

 loc_40F2B0:                             ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+37B3A↓j
                 and     ecx, 3E0DD7h
                 or      ecx, 200h
                 mov     eax, ecx

 loc_40F2BE:                             ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+37B4D↓j
                 mov     [ebx+70h], eax    //前面修正了Flage的值,赋值_KTrap_Frame.EFlags
                 mov     eax, large fs:124h  //fs:[124] = _KPCR._KPCB + 4 = _KPCR._KPCB._KTHREAD
                 mov     [ebp+var_2FC], eax  //临时保存
                 cmp     byte ptr [eax+30h], 0 //eax+30h = _KTHREAD.Iopl, 判断特权级别
                 jnz     loc_42EBAF  

 loc_40F2D7:                             ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+1F9B7↓j
                 mov     eax, [ebp+var_2EC] //ebp+var_2EC 保存三环栈顶
                 mov     [ebx+74h], eax     //_KTrap_Frame.HardwareEsp 赋值栈顶

                 mov     ecx, ds:_KeUserApcDispatcher  //全局变量,值在系统启动的时候已经赋值好了,是3环的一个函数:
													   //ntdll.KiUserApcDispatcher,回到3环,由KiUserApcDispatcher执行用户APC
								

  修改EIP         mov     [ebx+68h], ecx                //修改_Trap_Frame.EIP,让其返回三环执行点为ntdll.KiUserApcDispatcher

                 and     dword ptr [ebx+64h], 0        //修改_Trap_Frame.ErrCode 为 0   

				 //将NormalRoutione、NormalContext、SystemArgument1 、SystemArgument2 四个值放到三环堆栈中
   
                 mov     ecx, [ebp+arg_8]              //_KAPC的_KAPC.NormalRoutine,用户APC总入口,因为这是用户调用(这是用户APC队列)
                 mov     [eax], ecx                    //将当前值保存到三环栈顶的位置,ESP0 = _KAPC.NormalRoutine
                 push    4
                 pop     ecx                           //ecx = 4
				 					

                 add     eax, ecx                      //eax 指向 三环栈顶的下一个元素,即ESP3 + 4
                 mov     [ebp+var_2EC], eax            //保存到临时变量
                 mov     edx, [ebp+arg_C]              //_KAPC的_KAPC.NormalContext 用户APC:真正的APC函数(这是用户APC队列
												       //_KiInitializeUserApc参数
                 mov     [eax], edx                    //保存到 ESP3 + 4 = _KAPC.NormalContext

                 add     eax, ecx                      //ESP3 + 8
                 mov     [ebp+var_2EC], eax			   //保存到临时变量
                 mov     edx, [ebp+arg_10]             //_KAPC的_KAPC.SystemArgument1 APC函数参数
                 mov     [eax], edx                    //保存到 ESP3 + 8 = _KAPC.SystemArgument1

                 add     eax, ecx                      //ESP3 + 0Ch
                 mov     [ebp+var_2EC], eax            //保存到临时变量
                 mov     edx, [ebp+arg_14]			   //_KAPC的_KAPC.SystemArgument2 APC函数参数
                 mov     [eax], edx                    //保存到 ESP3 + 12 = _KAPC.SystemArgument2

                 add     eax, ecx                      //ESP3 + 10H
                 mov     [ebp+var_2EC], eax            //保存到临时变量

 loc_40F324:                             ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+37B97↓j
                 or      [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh

 loc_40F328:                             ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+2D↑j
                 mov     ecx, [ebp+var_1C]
                 call    sub_40D361
                 call    __SEH_epilog
                 retn    18h
 ; } // starts at 40F1FC
 _KiInitializeUserApc@24 endp

__SEH_epilog

 __SEH_epilog    proc near               ; CODE XREF: CcWorkerThread(x):loc_40E060↓p
                                         ; CcLazyWriteScan()+1B8↓p ...
                 mov     ecx, [ebp-10h]
                 mov     large fs:0, ecx
                 pop     ecx
                 pop     edi
                 pop     esi
                 pop     ebx
                 leave
                 push    ecx
                 retn
 __SEH_epilog    endp

ntdll.KiUserApcDispatcher(三环函数)

当用户在3环调用QueueUserAPC函数来插入APC时,不需要提供 NormalRoutione,这个参数是在QueueUserAPC内部指定的: kernel32.BaseDispatchAPC,即eax = kernel32.BaseDispatchAPC。

 ; __stdcall KiUserApcDispatcher(x, x, x, x, x)
                 public _KiUserApcDispatcher@20
 _KiUserApcDispatcher@20 proc near       ; DATA XREF: .text:off_7C923428↑o

 arg_C           = byte ptr  10h

                 lea     edi, [esp+arg_C]    //esp + 10h = context结构体, 这是3环的堆栈
                 pop     eax                 //获取 NormalRoutione 用户APC总入口
                 call    eax                 //执行 NormalRoutione 
                 push    1
                 push    edi
                 call    _ZwContinue@8   ; ZwContinue(x,x)
                 nop
 _KiUserApcDispatcher@20 endp ; sp-analysis failed

ntdll.ZwContinue(三环函数)

1)返回内核,在内核中再次判断是否还有用户APC,重复上面修改堆栈切换到用户执行的过程。

2)如果没有需要执行的用户APC,会将CONTEXT赋值给Trap_Frame结构体。即相当于没有改过。ZwContinue后面的代码不会执行,线程从哪进0环仍然会从哪里回去。

__stdcall ZwContinue(x, x)
                 public _ZwContinue@8
 _ZwContinue@8   proc near               ; CODE XREF: KiUserApcDispatcher(x,x,x,x,x)+A↓p
                                         ; KiUserExceptionDispatcher(x,x)+17↓p ...
                 mov     eax, 20h ; ' '  ; NtContinue
                 mov     edx, 7FFE0300h
                 call    dword ptr [edx]
                 retn    8
 _ZwContinue@8   endp

Context结构体

typedef struct _CONTEXT
{
    DWORD           ContextFlags    // -|               +00h
    DWORD           Dr0             //  |               +04h
    DWORD           Dr1             //  |               +08h
    DWORD           Dr2             //  >调试寄存器     +0Ch
    DWORD           Dr3             //  |               +10h
    DWORD           Dr6             //  |               +14h
    DWORD           Dr7             // -|               +18h

    FLOATING_SAVE_AREA FloatSave;   //浮点寄存器区      +1Ch~~~88h

    DWORD           SegGs           //-|                +8Ch
    DWORD           SegFs           // |\段寄存器       +90h
    DWORD           SegEs           // |/               +94h
    DWORD           SegDs           //-|                +98h

    DWORD           Edi             //________          +9Ch
    DWORD           Esi             // |  通用          +A0h
    DWORD           Ebx             // |   寄           +A4h
    DWORD           Edx             // |   存           +A8h
    DWORD           Ecx             // |   器           +ACh
    DWORD           Eax             //_|___组_          +B0h

    DWORD           Ebp             //++++++            +B4h
    DWORD           Eip             // |控制            +B8h
    DWORD           SegCs           // |寄存            +BCh
    DWORD           EFlag           // |器组            +C0h
    DWORD           Esp             // |                +C4h
    DWORD           SegSs           //++++++            +C8h

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;

刺槐 – 友谊