APC 篇—— APC 執行

寂靜的羽夏發表於2022-02-06

寫在前面

  此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。

  看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。


? 華麗的分割線 ?


QueueUserAPC 逆向分析

  我們逆向該函式的目的是把它是如何呼叫進入核心,如何初始化APC,如何插入APC。為了節約篇幅,增加可讀性,故使用經過重新命名好的偽C程式碼進行講解,你的命名可能和我的不太一樣,如果不知道型別和變數含義,可以參考WRK進行。
  QueueUserAPC這個函式在kernel32這個Dll中,我們來看看它的虛擬碼:

DWORD __stdcall QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData)
{
  NTSTATUS status; // eax
  int ContextInfo; // eax
  DWORD result; // eax
  int pvBuffer; // [esp+4h] [ebp-8h] BYREF
  int v7; // [esp+8h] [ebp-4h]

  pvBuffer = 0;
  v7 = 0;
  status = RtlQueryInformationActivationContext(
             RTL_QUERY_ACTIVATION_CONTEXT_FLAG_USE_ACTIVE_ACTIVATION_CONTEXT,
             0,
             0,
             ActivationContextBasicInformation,
             &pvBuffer,
             8u,
             0);
  if ( status < 0 )
  {
    DbgPrint(
      "SXS: %s failing because RtlQueryInformationActivationContext() returned status %08lx\n",
      "QueueUserAPC",
      status);
    result = 0;
  }
  else
  {
    ContextInfo = pvBuffer;
    if ( (v7 & 1) != 0 )
      ContextInfo = -1;
    result = NtQueueApcThread(hThread, BaseDispatchAPC, pfnAPC, dwData, ContextInfo) >= 0;
  }
  return result;
}

  RtlQueryInformationActivationContext這個名字賊長的函式不知道是幹啥的,經過查閱是查詢RTL以獲取有關當前啟用上下文的資訊,啟用上下文簡單就是manifest裡面描述資訊封裝成的結構體,對於該函式也不知道有啥用,就先略過,把重心放到NtQueueApcThread上面。
  BaseDispatchAPC是個函式,通過函式名是派發APC執行的函式,點選去看看其虛擬碼:

void __fastcall BaseDispatchAPC(struct _RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_EXTENDED *a1, void *a2, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
  PRTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_EXTENDED v5; // ecx
  int v6; // [esp+0h] [ebp-3Ch]
  int v7[5]; // [esp+Ch] [ebp-30h] BYREF
  HANDLE handle; // [esp+20h] [ebp-1Ch]
  CPPEH_RECORD ms_exc; // [esp+24h] [ebp-18h]

  v7[0] = 20;
  v7[1] = 1;
  v7[2] = 0;
  v7[3] = 0;
  v7[4] = 0;
  handle = SystemArgument2;
  if ( SystemArgument2 == -1 )
  {
    (NormalContext)(a1, a2, SystemArgument1);
  }
  else
  {
    RtlActivateActivationContextUnsafeFast(a1, a2);
    ms_exc.registration.TryLevel = 0;
    (NormalContext)(SystemArgument1, v7, SystemArgument2, v6);
    ms_exc.registration.TryLevel = -1;
    RtlDeactivateActivationContextUnsafeFast(v5);
    RtlReleaseActivationContext(handle);
  }
}

  經過分析,該函式會把我們想要執行的函式地址是NormalContext引數,傳入的引數就是引數1,啟用上下文變成引數2。我們把注意力放到該函式:

NTSTATUS __stdcall NtQueueApcThread(HANDLE ThreadHandle, PKNORMAL_ROUTINE ApcRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
  PETHREAD Thread; // ebx
  struct _KAPC *apc; // eax
  struct _KAPC *apc_1; // edi
  HANDLE v9; // [esp+14h] [ebp+8h] FORCED

  Thread = ObReferenceObjectByHandle(
             ThreadHandle,
             0x10u,
             PsThreadType,
             KeGetCurrentThread()->PreviousMode,
             &ThreadHandle,
             0);
  if ( Thread >= 0 )
  {
    Thread = 0;
    if ( (*(ThreadHandle + 584) & 0x10) != 0 )  // PETHREAD->CrossThreadFlags
    {
      Thread = STATUS_INVALID_HANDLE;
    }
    else
    {
      apc = ExAllocatePoolWithQuotaTag(8, 0x30u, 'pasP');
      apc_1 = apc;
      if ( apc )
      {
        KeInitializeApc(apc, v9, OriginalApcEnvironment, IopDeallocateApc, 0, ApcRoutine, 1, NormalContext);
        if ( !KeInsertQueueApc(apc_1, SystemArgument1, SystemArgument2, 0) )
        {
          ExFreePoolWithTag(apc_1, 0);
          Thread = STATUS_UNSUCCESSFUL;
        }
      }
      else
      {
        Thread = STATUS_NO_MEMORY;
      }
    }
    ObfDereferenceObject(v9);
  }
  return Thread;
}

  可以看出,如果執行緒控制程式碼被正常的轉化為執行緒結構體,就會初始化APC,然後插入APC,全部的流程就這些。但這個思考題並沒有完成,我們還得弄懂是如何初始化的,如何插入的。先看初始化:

PKAPC __stdcall KeInitializeApc(PKAPC Apc, PKTHREAD Thread, _KAPC_ENVIRONMENT Environment, int KernelRoutine, int RundownRoutine, int NormalRoutine, char ApcMode, PVOID NormalContext)
{
  PKAPC result; // eax
  char ApcStateIndex; // dl

  result = Apc;
  ApcStateIndex = Environment;
  Apc->Type = 0x12;
  Apc->Size = 0x30;
  if ( Environment == CurrentApcEnvironment )
    ApcStateIndex = Thread->ApcStateIndex;
  Apc->Thread = Thread;
  Apc->KernelRoutine = KernelRoutine;
  Apc->ApcStateIndex = ApcStateIndex;
  Apc->RundownRoutine = RundownRoutine;
  Apc->NormalRoutine = NormalRoutine;
  if ( NormalRoutine )
  {
    Apc->ApcMode = ApcMode;
    Apc->NormalContext = NormalContext;
  }
  else
  {
    Apc->ApcMode = 0;
    Apc->NormalContext = 0;
  }
  Apc->Inserted = 0;
  return result;
}

  初始化程式碼函式十分簡單,我就不贅述了。再來看看如何插入:

char __stdcall KeInsertQueueApc(PKAPC apc, PVOID SystemArgument1, PVOID SystemArgument2, KPRIORITY Increment)
{
  _KTHREAD *thread; // esi
  char res; // bl
  struct _KLOCK_QUEUE_HANDLE LockHandle; // [esp+Ch] [ebp-Ch] BYREF

  thread = apc->Thread;
  KeAcquireInStackQueuedSpinLockRaiseToSynch(&thread->ApcQueueLock, &LockHandle);
  res = 0;
  if ( thread->ApcQueueable )
  {
    apc->SystemArgument1 = SystemArgument1;
    apc->SystemArgument2 = SystemArgument2;
    res = KiInsertQueueApc(apc, Increment);
  }
  KeReleaseInStackQueuedSpinLock(&LockHandle);
  return res;
}

  這個插入函式也是挺簡單的,加了一個APC佇列自旋鎖,然後呼叫KiInsertQueueApc實現,然後再釋放。我們看看這個函式是咋實現的:

char __fastcall KiInsertQueueApc(PKAPC apc, KPRIORITY Increment)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  Inserted = apc->Inserted == 0;
  thread = apc->Thread;
  if ( !Inserted )
    return 0;
  if ( apc->ApcStateIndex == 3 )
    apc->ApcStateIndex = thread->ApcStateIndex;
  ApcState = thread->ApcStatePointer[apc->ApcStateIndex];
  ApcMode = apc->ApcMode;
  if ( apc->NormalRoutine )
  {
    if ( ApcMode && apc->KernelRoutine == PsExitSpecialApc )
    {
      thread->ApcState.UserApcPending = 1;
      v8 = &ApcState->ApcListHead[ApcMode];
      v9 = v8->Flink;
      apc->ApcListEntry.Flink = v8->Flink;
      apc->ApcListEntry.Blink = v8;
      v9->Blink = &apc->ApcListEntry;
      v8->Flink = &apc->ApcListEntry;
    }
    else
    {
      v10 = &ApcState->ApcListHead[ApcMode];
      v11 = v10->Blink;
      apc->ApcListEntry.Flink = v10;
      apc->ApcListEntry.Blink = v11;
      v11->Flink = &apc->ApcListEntry;
      v10->Blink = &apc->ApcListEntry;
    }
  }
  else
  {
    v12 = &ApcState->ApcListHead[ApcMode];
    for ( i = v12->Blink; i != v12 && i[2].Flink; i = i->Blink )
      ;
    v14 = i->Flink;
    apc->ApcListEntry.Flink = i->Flink;
    apc->ApcListEntry.Blink = i;
    v14->Blink = &apc->ApcListEntry;
    i->Flink = &apc->ApcListEntry;
  }
  ApcStateIndex = apc->ApcStateIndex;
  apc->Inserted = 1;
  if ( ApcStateIndex == thread->ApcStateIndex )
  {
    if ( ApcMode )
    {
      if ( thread->State == Waiting
        && thread->WaitMode == UserMode
        && (thread->Alertable || thread->ApcState.UserApcPending) )
      {
        thread->ApcState.UserApcPending = 1;
        KiUnwaitThread(thread, STATUS_USER_APC, Increment, 0);
      }
    }
    else
    {
      State = thread->State;
      thread->ApcState.KernelApcPending = 1;
      if ( State == 2 )
      {
        LOBYTE(thread) = 1;
        HalRequestSoftwareInterrupt(thread);
      }
      else if ( State == 5
             && !thread->WaitIrql
             && (!apc->NormalRoutine || !thread->KernelApcDisable && !thread->ApcState.KernelApcInProgress) )
      {
        KiUnwaitThread(thread, STATUS_KERNEL_APC, Increment, 0);
      }
    }
  }
  return 1;
}

  如下部分就是插入使用者APC的部分:

if ( ApcMode && apc->KernelRoutine == PsExitSpecialApc )
    {
      thread->ApcState.UserApcPending = 1;
      v8 = &ApcState->ApcListHead[ApcMode];
      v9 = v8->Flink;
      apc->ApcListEntry.Flink = v8->Flink;
      apc->ApcListEntry.Blink = v8;
      v9->Blink = &apc->ApcListEntry;
      v8->Flink = &apc->ApcListEntry;
    }
    else
    {
      v10 = &ApcState->ApcListHead[ApcMode];
      v11 = v10->Blink;
      apc->ApcListEntry.Flink = v10;
      apc->ApcListEntry.Blink = v11;
      v11->Flink = &apc->ApcListEntry;
      v10->Blink = &apc->ApcListEntry;
    }

  本思考題最終結束,當然裡面也有很多細節並沒有介紹,將在總結與提升介紹。

無法被喚醒的執行緒 APC 分析

  這個實驗應該也挺好做的,結論是執行兩個,程式碼驗證如下:

#include "stdafx.h"
#include <stdlib.h>

#define  _WIN32_WINNT 0x400
#include <windows.h>

VOID WINAPI APCProc(ULONG Param)
{
  printf("APC…… 0x%X \n",Param);
}

DWORD WINAPI ThreadProc(VOID* Param)
{
  for (int i =0 ;i<8;i++)
  {
    Sleep(1000);
    printf("Running\n");
  }
  SleepEx(1,TRUE);
  return 0;
}

int main(int argc, char* argv[])
{
  HANDLE hTread=CreateThread(NULL,NULL,ThreadProc,NULL,NULL,NULL);
  Sleep(3000);
  QueueUserAPC(APCProc,hTread,1);
  QueueUserAPC(APCProc,hTread,2);
  system("pause");
  CloseHandle(hTread);
  return 0;
}

  效果如下:

APC 篇—— APC 執行

APC 的執行流程

  APC的執行流程還是挺複雜的,需要大量的逆向。如果發現自己不懂的地方,請參考WRK,裡面的引數型別和變數型別都有詳細的參考。裡面有比較多未知的東西,不會的話,強烈推薦參考WRK。下面我們分如下三部分講解:合適執行APC、核心APC執行流程、使用者APC的執行流程。注意,如果真想學會,請一定要在看完何時執行APC之後的內容就不要再看了,自己完整的把KiDeliverApc這個處理APC函式完整的逆一遍,不會或者完成了後,回來再看。逆向可能需要花費3個小時左右,自己要有心理準備。

何時執行 APC

  我們都知道,APC函式的執行與插入並不是同一個執行緒,具體點說在A執行緒中向B執行緒插入一個APC,插入的動作是在A執行緒中完成的,但什麼時候執行則由B執行緒決定,所以叫“非同步過程呼叫”。核心APC函式與使用者APC函式的執行時間和執行方式也有區別,下面我們來看看執行緒是何時處理APC的:

SwapContext

  這個函式我們都很熟悉,在學習執行緒切換的時候重點研究的,我們把APC相關的東西拿出來看看:

                cmp     [esi+_KTHREAD.ApcState.KernelApcPending], 0
                jnz     short loc_46A9BD
                popf
                xor     eax, eax
                retn
; ---------------------------------------------------------------------------

loc_46A9BD:                             ; CODE XREF: SwapContext+D7↑j
                popf
                jnz     short loc_46A9C3
                mov     al, 1
                retn

  之前說過KernelApcPending是表示有沒有核心APC處理的標誌,如果是1,也就是有的話,就會使返回值置1;反之置0。也就是說,這個函式的返回值是有意義的。我們再往上一級函式看看:

; __fastcall KiSwapContext(x)
@KiSwapContext@4 proc near              ; CODE XREF: KiSwapThread()+41↑p

var_200FE4      = dword ptr -200FE4h
var_10          = dword ptr -10h
var_C           = dword ptr -0Ch
var_8           = dword ptr -8
var_4           = dword ptr -4

                sub     esp, 10h
                mov     [esp+10h+var_4], ebx
                mov     [esp+10h+var_8], esi
                mov     [esp+10h+var_C], edi
                mov     [esp+10h+var_10], ebp
                mov     ebx, ds:0FFDFF01Ch ; ebx = &_KPCR
                mov     esi, ecx        ; esi = ecx = NextReadyThread
                mov     edi, [ebx+_KPCR.PrcbData.CurrentThread] ; oldThread
                mov     [ebx+_KPCR.PrcbData.CurrentThread], esi
                mov     cl, [edi+_KTHREAD.WaitIrql]
                call    SwapContext
                mov     ebp, [esp+10h+var_10]
                mov     edi, [esp+10h+var_C]
                mov     esi, [esp+10h+var_8]
                mov     ebx, [esp+10h+var_4]
                add     esp, 10h
                retn
@KiSwapContext@4 endp

  這一級函式並沒有用到返回值,也沒有操作eax,說明就沒有用到嗎?我們再往上一級看看:

loc_429CCF:                             ; CODE XREF: KiSwapThread()+1A↑j
                mov     ecx, eax        ; 從這裡判斷出這個引數為一個執行緒結構體 ecx 傳參
                call    @KiSwapContext@4 ; KiSwapContext(x)
                test    al, al
                mov     cl, [edi+58h]
                mov     edi, [edi+54h]
                mov     esi, ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
                jz      short loc_429CF6
                mov     cl, 1           ; NewIrql
                call    esi ; KfLowerIrql(x) ; KfLowerIrql(x)
                xor     eax, eax
                push    eax             ; trapframe
                push    eax             ; unknown
                push    eax             ; CanUserMode
                call    _KiDeliverApc@12 ; KiDeliverApc(x,x,x)
                xor     cl, cl          ; NewIrql

loc_429CF6:                             ; CODE XREF: KiSwapThread()+54↑j
                call    esi ; KfLowerIrql(x) ; KfLowerIrql(x)
                mov     eax, edi
                pop     edi
                pop     esi
                retn

  看到test al, al這條彙編了嗎?如果為1的話,也就是有核心APC執行,就會繼續往下走,呼叫KiDeliverApc這個函式,也就是我們前面提到的處理APC的函式,傳遞的三個引數都是0。

KiServiceExit

  如果你比較仔細的話,這個函式你也是眼熟的,就是我們在學習系統呼叫時看到的函式。不過當時由於知識的限制沒有要求。這個函式是系統呼叫、中斷異常必經之處,我們來看看與APC相關的彙編:

                cli
                test    dword ptr [ebp+70h], 20000h
                jnz     short loc_466659
                test    byte ptr [ebp+6Ch], 1
                jz      short loc_4666B0

loc_466659:                             ; CODE XREF: _KiServiceExit+8↑j
                                        ; _KiServiceExit+63↓j
                mov     ebx, ds:0FFDFF124h
                mov     byte ptr [ebx+2Eh], 0
                cmp     byte ptr [ebx+4Ah], 0
                jz      short loc_4666B0
                mov     ebx, ebp
                mov     [ebx+44h], eax
                mov     dword ptr [ebx+50h], 3Bh ; ';'
                mov     dword ptr [ebx+38h], 23h ; '#'
                mov     dword ptr [ebx+34h], 23h ; '#'
                mov     dword ptr [ebx+30h], 0
                mov     ecx, 1          ; NewIrql
                call    ds:__imp_@KfRaiseIrql@4 ; KfRaiseIrql(x)
                push    eax
                sti
                push    ebx             ; trapframe
                push    0               ; unknown
                push    1               ; CanUserAPC
                call    _KiDeliverApc@12 ; KiDeliverApc(x,x,x)
                pop     ecx             ; NewIrql
                call    ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
                mov     eax, [ebx+44h]
                cli
                jmp     short loc_466659

  可以看出傳遞引數的不同,第一個引數是1,第二個引數是0,第三個就是所謂的TrapFrame

核心 APC 執行流程

  核心APC執行算是簡單的。不過為了講解方便,我們需要把KiDeliverApc的虛擬碼放上:

_KTRAP_FRAME *__stdcall KiDeliverApc(BOOLEAN CanUserAPC, int unknown, _KTRAP_FRAME *trapframe)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  if ( trapframe )
  {
    _eip = trapframe->Eip;
    if ( _eip >= ExpInterlockedPopEntrySListResume && _eip <= ExpInterlockedPopEntrySListEnd )
      trapframe->Eip = ExpInterlockedPopEntrySListResume;
  }
  currentThread = KeGetCurrentThread();
  tmpTrapFrame = currentThread->Tcb.TrapFrame;
  v5 = currentThread->Tcb.ApcState.Process;
  currentThread->Tcb.TrapFrame = trapframe;
  APCDeliverProcess = v5;
  KeAcquireInStackQueuedSpinLock(&currentThread->Tcb.ApcQueueLock, &LockHandle);
  currentThread->Tcb.ApcState.KernelApcPending = 0;
  ApcListEntry = (currentThread + offsetof(_KTHREAD, ApcState));
  while ( !IsListEmpty(ApcListEntry) )
  {
    plist = ApcListEntry->Flink;
    kpac = CONTAINING_RECORD(ApcListEntry->Flink, _KAPC, ApcListEntry);
    KernelRoutine = kpac->KernelRoutine;
    NormalRoutine = kpac->NormalRoutine;
    WaitStatus = kpac->NormalContext;
    SystemArgument1 = kpac->SystemArgument1;
    SystemArgument2 = kpac->SystemArgument2;
    if ( NormalRoutine )
    {
      if ( currentThread->Tcb.ApcState.KernelApcInProgress || currentThread->Tcb.KernelApcDisable )
        goto NoUserAPC;
      RemoveEntryList(plist);
      kpac->Inserted = 0;
      KeReleaseInStackQueuedSpinLock(&LockHandle);
      KernelRoutine(kpac, &NormalRoutine, &WaitStatus, &SystemArgument1, &SystemArgument2);
      if ( NormalRoutine )
      {
        currentThread->Tcb.ApcState.KernelApcInProgress = 1;
        KfLowerIrql(0);
        (NormalRoutine)(WaitStatus, SystemArgument1, SystemArgument2);
        LockHandle.OldIrql = KfRaiseIrql(1u);
      }
      KeAcquireInStackQueuedSpinLock(&currentThread->Tcb.ApcQueueLock, &LockHandle);
      currentThread->Tcb.ApcState.KernelApcInProgress = 0;
    }
    else
    {
      RemoveEntryList(plist);
      kpac->Inserted = 0;
      KeReleaseInStackQueuedSpinLock(&LockHandle);
      KernelRoutine(kpac, &NormalRoutine, &WaitStatus, &SystemArgument1, &SystemArgument2);
      KeAcquireInStackQueuedSpinLock(&currentThread->Tcb.ApcQueueLock, &LockHandle);
    }
  }
  P = currentThread->Tcb.ApcState.ApcListHead[UserMode].Flink;
  if ( P == &currentThread->Tcb.ApcState.ApcListHead[UserMode]
    || CanUserAPC != UserMode
    || !currentThread->Tcb.ApcState.UserApcPending )
  {
NoUserAPC:
    KeReleaseInStackQueuedSpinLock(&LockHandle);
    goto LABEL_21;
  }
  currentThread->Tcb.ApcState.UserApcPending = 0;
  kpacSTATE = &P[-2u].Blink;                    // ====如下程式碼錯誤請參考彙編===
  KernelRoutine_1 = P[1].Flink;
  NormalRoutine = P[2].Flink;
  WaitStatus = P[2].Blink;
  SystemArgument1 = P[3].Flink;
  SystemArgument2 = P[3].Blink;
  v12 = P->Flink;
  v13 = P->Blink;
  v13->Flink = v12;
  v12->Blink = v13;
  kpacSTATE[1].UserApcPending = 0;              // =======錯誤程式碼結束=======
  KeReleaseInStackQueuedSpinLock(&LockHandle);
  (KernelRoutine_1)(kpacSTATE, &NormalRoutine, &WaitStatus, &SystemArgument1, &SystemArgument2);
  if ( NormalRoutine )
    KiInitializeUserApc(unknown, trapframe, NormalRoutine, WaitStatus, SystemArgument1, SystemArgument2);
  else
    KeTestAlertThread(1);
LABEL_21:
  if ( currentThread->Tcb.ApcState.Process != APCDeliverProcess )
    KeBugCheckEx(
      5u,
      APCDeliverProcess,
      currentThread->Tcb.ApcState.Process,
      currentThread->Tcb.ApcStateIndex,
      KeGetPcr()->PrcbData.DpcRoutineActive);
  result = tmpTrapFrame;
  currentThread->Tcb.TrapFrame = tmpTrapFrame;
  return result;
}

  當然,上面的虛擬碼有錯誤,我已經用註釋標註錯誤的區域,下面我們再把彙編放上:

; _KTRAP_FRAME *__stdcall KiDeliverApc(BOOLEAN CanUserAPC, int unknown, _KTRAP_FRAME *trapframe)
                public _KiDeliverApc@12
_KiDeliverApc@12 proc near              ; CODE XREF: KiSwapThread()+5F↓p
                                        ; _KiServiceExit+53↓p ...

LockHandle      = _KLOCK_QUEUE_HANDLE ptr -28h
tmpTrapFrame    = dword ptr -1Ch
APCDeliverProcess= dword ptr -18h
KernelRoutine   = dword ptr -14h
NormalContext   = dword ptr -10h
SystemArgument1 = dword ptr -0Ch
SystemArgument2 = dword ptr -8
NormalRoutine   = dword ptr -4
CanUserAPC      = byte ptr  8
a2              = dword ptr  0Ch
trapframe       = dword ptr  10h

                mov     edi, edi
                push    ebp
                mov     ebp, esp
                sub     esp, 28h
                mov     ecx, [ebp+trapframe]
                test    ecx, ecx
                jz      short loc_426A54
                mov     edx, [ecx+_KTRAP_FRAME._Eip]
                mov     eax, offset _ExpInterlockedPopEntrySListResume@0 ; ExpInterlockedPopEntrySListResume()
                cmp     edx, eax
                jb      short loc_426A54
                cmp     edx, offset _ExpInterlockedPopEntrySListEnd@0 ; ExpInterlockedPopEntrySListEnd()
                ja      short loc_426A54
                mov     [ecx+_KTRAP_FRAME._Eip], eax

loc_426A54:                             ; CODE XREF: KiDeliverApc(x,x,x)+D↑j
                                        ; KiDeliverApc(x,x,x)+19↑j ...
                push    ebx
                push    esi
                push    edi
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                mov     esi, eax
                mov     eax, [esi+_KTHREAD.TrapFrame]
                mov     [ebp+tmpTrapFrame], eax
                mov     eax, [esi+_KTHREAD.ApcState.Process]
                mov     [esi+_KTHREAD.TrapFrame], ecx
                lea     ecx, [esi+_ETHREAD.Tcb.ApcQueueLock] ; SpinLock
                lea     edx, [ebp+LockHandle] ; LockHandle
                mov     [ebp+APCDeliverProcess], eax
                call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
                mov     [esi+_KTHREAD.ApcState.KernelApcPending], 0
                lea     ebx, [esi+_KTHREAD.ApcState]
                jmp     loc_426B70
; ---------------------------------------------------------------------------

ListEmptyLoop:                          ; CODE XREF: KiDeliverApc(x,x,x)+144↓j
                mov     eax, [ebx]
                lea     edi, [eax-0Ch]
                mov     ecx, [edi+_KAPC.KernelRoutine]
                mov     [ebp+KernelRoutine], ecx
                mov     ecx, [edi+_KAPC.NormalRoutine]
                test    ecx, ecx
                mov     [ebp+NormalRoutine], ecx
                mov     edx, [edi+_KAPC.NormalContext]
                mov     [ebp+NormalContext], edx
                mov     edx, [edi+_KAPC.SystemArgument1]
                mov     [ebp+SystemArgument1], edx
                mov     edx, [edi+_KAPC.SystemArgument2]
                mov     [ebp+SystemArgument2], edx
                jnz     short NormalRoutineNotNull
                mov     ecx, [eax]      ; 開始摘掉 APC
                mov     eax, [eax+4]
                mov     [eax], ecx
                mov     [ecx+4], eax
                lea     ecx, [ebp+LockHandle] ; LockHandle
                mov     [edi+_KAPC.Inserted], 0 ; WaitListEntry
                call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
                lea     eax, [ebp+SystemArgument2]
                push    eax
                lea     eax, [ebp+SystemArgument1]
                push    eax
                lea     eax, [ebp+NormalContext]
                push    eax
                lea     eax, [ebp+NormalRoutine]
                push    eax
                push    edi
                call    [ebp+KernelRoutine]
                lea     edx, [ebp+LockHandle] ; LockHandle
                lea     ecx, [esi+_KTHREAD.ApcQueueLock] ; SpinLock
                call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
                jmp     short loc_426B70
; ---------------------------------------------------------------------------

NormalRoutineNotNull:                   ; CODE XREF: KiDeliverApc(x,x,x)+86↑j
                cmp     [esi+_KTHREAD.ApcState.KernelApcInProgress], 0
                jnz     NoUserAPC
                cmp     [esi+_KTHREAD.KernelApcDisable], 0
                jnz     NoUserAPC
                mov     ecx, [eax]
                mov     eax, [eax+_LIST_ENTRY.Blink]
                mov     [eax], ecx
                mov     [ecx+_LIST_ENTRY.Blink], eax
                lea     ecx, [ebp+LockHandle] ; LockHandle
                mov     [edi+_KAPC.Inserted], 0
                call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
                lea     eax, [ebp+SystemArgument2]
                push    eax
                lea     eax, [ebp+SystemArgument1]
                push    eax
                lea     eax, [ebp+NormalContext]
                push    eax
                lea     eax, [ebp+NormalRoutine]
                push    eax
                push    edi
                call    [ebp+KernelRoutine]
                cmp     [ebp+NormalRoutine], 0
                jz      short NormalRoutineNULL
                xor     cl, cl          ; NewIrql
                mov     [esi+_KTHREAD.ApcState.KernelApcInProgress], 1
                call    ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
                push    [ebp+SystemArgument2]
                push    [ebp+SystemArgument1]
                push    [ebp+NormalContext]
                call    [ebp+NormalRoutine]
                mov     cl, 1           ; NewIrql
                call    ds:__imp_@KfRaiseIrql@4 ; KfRaiseIrql(x)
                mov     [ebp+LockHandle.OldIrql], al

NormalRoutineNULL:                      ; CODE XREF: KiDeliverApc(x,x,x)+10A↑j
                lea     edx, [ebp+LockHandle] ; LockHandle
                lea     ecx, [esi+_ETHREAD.Tcb.ApcQueueLock] ; SpinLock
                call    ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
                mov     [esi+_KTHREAD.ApcState.KernelApcInProgress], 0

loc_426B70:                             ; CODE XREF: KiDeliverApc(x,x,x)+5C↑j
                                        ; KiDeliverApc(x,x,x)+C2↑j
                cmp     [ebx+_KAPC_STATE.ApcListHead.Flink], ebx
                jnz     ListEmptyLoop
                lea     ecx, [esi+(_ETHREAD.Tcb.ApcState.ApcListHead.Flink+8)] ; ApcListHead[UserMode]
                mov     eax, [ecx+_LIST_ENTRY.Flink]
                cmp     eax, ecx
                jz      NoUserAPC
                cmp     [ebp+CanUserAPC], 1
                jnz     short NoUserAPC
                cmp     [esi+_KTHREAD.ApcState.UserApcPending], 0
                jz      short NoUserAPC
                mov     [esi+_KTHREAD.ApcState.UserApcPending], 0
                lea     edi, [eax-0Ch]
                mov     ecx, [edi+_KAPC.NormalRoutine]
                mov     ebx, [edi+_KAPC.KernelRoutine]
                mov     [ebp+NormalRoutine], ecx
                mov     ecx, [edi+_KAPC.NormalContext]
                mov     [ebp+NormalContext], ecx
                mov     ecx, [edi+_KAPC.SystemArgument1]
                mov     [ebp+SystemArgument1], ecx
                mov     ecx, [edi+_KAPC.SystemArgument2]
                mov     [ebp+SystemArgument2], ecx
                mov     ecx, [eax+_LIST_ENTRY.Flink]
                mov     eax, [eax+_LIST_ENTRY.Blink]
                mov     [eax+_LIST_ENTRY.Flink], ecx
                mov     [ecx+_LIST_ENTRY.Blink], eax
                lea     ecx, [ebp+LockHandle] ; LockHandle
                mov     [edi+_KAPC.Inserted], 0
                call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
                lea     eax, [ebp+SystemArgument2]
                push    eax
                lea     eax, [ebp+SystemArgument1]
                push    eax
                lea     eax, [ebp+NormalContext]
                push    eax
                lea     eax, [ebp+NormalRoutine]
                push    eax
                push    edi
                call    ebx
                cmp     [ebp+NormalRoutine], 0
                jnz     short loc_426BEC
                push    1
                call    _KeTestAlertThread@4 ; KeTestAlertThread(x)
                jmp     short loc_426C0E
; ---------------------------------------------------------------------------

loc_426BEC:                             ; CODE XREF: KiDeliverApc(x,x,x)+1B3↑j
                push    [ebp+SystemArgument2] ; SystemArgument2
                push    [ebp+SystemArgument1] ; SystemArgument1
                push    [ebp+NormalContext] ; NormalContext
                push    [ebp+NormalRoutine] ; NormalRoutine
                push    [ebp+trapframe] ; TrapFrame
                push    [ebp+a2]        ; ExceptionFrame
                call    _KiInitializeUserApc@24 ; KiInitializeUserApc(x,x,x,x,x,x)
                jmp     short loc_426C0E
; ---------------------------------------------------------------------------

NoUserAPC:                              ; CODE XREF: KiDeliverApc(x,x,x)+C8↑j
                                        ; KiDeliverApc(x,x,x)+D5↑j ...
                lea     ecx, [ebp+LockHandle] ; LockHandle
                call    ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)

loc_426C0E:                             ; CODE XREF: KiDeliverApc(x,x,x)+1BC↑j
                                        ; KiDeliverApc(x,x,x)+1D5↑j
                mov     ecx, [ebp+APCDeliverProcess]
                cmp     [esi+_KTHREAD.ApcState.Process], ecx
                jz      short loc_426C30
                mov     eax, large fs:_KPCR.PrcbData.DpcRoutineActive
                push    eax             ; BugCheckParameter4
                movzx   eax, [esi+_KTHREAD.ApcStateIndex]
                push    eax             ; BugCheckParameter3
                push    [esi+_KTHREAD.ApcState.Process] ; BugCheckParameter2
                push    ecx             ; BugCheckParameter1
                push    5               ; BugCheckCode
                call    _KeBugCheckEx@20 ; KeBugCheckEx(x,x,x,x,x)
; ---------------------------------------------------------------------------

loc_426C30:                             ; CODE XREF: KiDeliverApc(x,x,x)+1E6↑j
                mov     eax, [ebp+tmpTrapFrame]
                pop     edi
                mov     [esi+_KTHREAD.TrapFrame], eax
                pop     esi
                pop     ebx
                leave
                retn    0Ch
_KiDeliverApc@12 endp

  通過程式碼分析,上面的兩處傳的引數都會處理核心APC,執行緒切換處理核心APC,另一個處理使用者和核心APC。由於此部分講解核心APC的處理,我們只看相關部分。
  由於此部分不難,我們只簡單介紹一下流程,其他細節自行檢視程式碼:

  1. 判斷第一個連結串列是否為空(連結串列地址和連結串列的內容十分相等,相等則為空,微軟就是這麼設計的)
  2. 判斷KTHREAD.ApcState.KernelApcInProgress是否為1
  3. 判斷是否禁用核心APCKTHREAD.KernelApcDisable是否為1)
  4. 將當前KAPC結構體從連結串列中摘除
  5. 執行KAPC.KernelRoutine指定的函式,釋放KAPC結構體佔用的空間
  6. KTHREAD.ApcState.KernelApcInProgress設定為1,標識正在執行核心APC
  7. 執行真正的核心APC函式(KAPC.NormalRoutine
  8. 執行完畢,將KernelApcInProgress改為0
  9. 迴圈上述過程直至裝核心APC的連結串列全部完成

  我們可以做出如下總結:

  1. 核心APC線上程切換的時候就會執行,這也就意味著,只要插入核心APC很快就會執行。
  2. 在執行使用者APC之前會先執行核心APC
  3. 核心APC在核心空間執行,不需要換棧,一個迴圈全部執行完畢。

  有些細節我還沒有強調,不過自己應該也能夠發現,這些東西我都會在下一篇總結與提升進行講解。下面我們來分析使用者APC的執行流程。

使用者 APC 執行流程

  上述虛擬碼錯誤的區域都是賦值操作,找到KAPC結構,並摘除準備執行的操作,請參考彙編,挺簡單的我就不贅述了。我們把重點放到KiInitializeUserApc這個函式上,先看其反彙編:

int __stdcall KiInitializeUserApc(_KTRAP_FRAME *ExceptionFrame, _KTRAP_FRAME *TrapFrame, PKNORMAL_ROUTINE NormalRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
  PKNORMAL_ROUTINE *_esp; // esi
  unsigned int newEflag; // eax
  int result; // eax
  _CONTEXT ContextRecord; // [esp+70h] [ebp-2E8h] BYREF
  void *v10; // [esp+33Ch] [ebp-1Ch]
  CPPEH_RECORD ms_exc; // [esp+340h] [ebp-18h]

  if ( (TrapFrame->EFlags & 0x20000) == offsetof(_KTRAP_FRAME, DbgEbp) )
  {
    ContextRecord.ContextFlags = 0x10017;
    KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextRecord);
    ms_exc.registration.TryLevel = 0;
    _esp = ((ContextRecord.Esp & 0xFFFFFFFC) - 0x2DC);
    ProbeForWrite(_esp, 0x2DCu, 4u);
    qmemcpy(_esp + 4, &ContextRecord, 0x2CCu);
    TrapFrame->SegCs = 0x1B;
    TrapFrame->HardwareSegSs = 0x23;
    TrapFrame->SegDs = 0x23;
    TrapFrame->SegEs = 0x23;
    TrapFrame->SegFs = 0x3B;
    TrapFrame->SegGs = 0;
    if ( (ContextRecord.EFlags & 0x20000) != 0 && KeI386VdmIoplAllowed )
      newEflag = KeI386EFlagsOrMaskV86 | ContextRecord.EFlags & KeI386EFlagsAndMaskV86;
    else
      newEflag = ContextRecord.EFlags & 0x3E0DD7 | 0x200;
    TrapFrame->EFlags = newEflag;
    if ( KeGetCurrentThread()->Iopl )
      BYTE1(TrapFrame->EFlags) |= 0x30u;
    TrapFrame->HardwareEsp = _esp;
    TrapFrame->Eip = KeUserApcDispatcher;
    TrapFrame->ErrCode = 0;
    *_esp = NormalRoutine;
    _esp[1] = NormalContext;
    _esp[2] = SystemArgument1;
    _esp[3] = SystemArgument2;
    ms_exc.registration.TryLevel = -1;
  }
  xHalReferenceHandler(v10);
  return result;
}

  處理使用者APC要比核心APC複雜的多,因為,使用者APC函式要在使用者空間執行的,這裡涉及到大量換棧的操作:當執行緒從使用者層進入核心層時,要保留原來的執行環境,比如各種暫存器,棧的位置等等(_Trap_Frame),然後切換成核心的堆疊,如果正常返回,恢復堆疊環境即可。但如果有使用者APC要執行的話,就意味著執行緒要提前返回到使用者空間去執行,而且返回的位置不是執行緒進入核心時的位置,而是返回到其他的位置,每處理一個使用者APC都會涉及到:核心到使用者空間再回到核心空間。
  執行緒進0環時,原來的執行環境(暫存器棧頂等)儲存到_Trap_Frame結構體中,如果要提前返回3環去處理使用者APC,就必須要修改_Trap_Frame結構體。比如:進0環時的位置儲存在EIP中,現在要提前返回,而且返回的並不是原來的位置,那就意味著必須要修改EIP為新的返回位置。還有堆疊ESP,也要修改為處理APC需要的堆疊。那原來的值怎麼辦呢?處理完APC後該如何返回原來的位置呢?
  KiInitializeUserApc要做的第一件事就是備份,將原來_Trap_Frame的值備份到一個新的結構體中(CONTEXT),這個功能由其子函式KeContextFromKframes來完成。我們可以點進去看看:

void __stdcall KeContextFromKframes(_KTRAP_FRAME *TrapFrame, _KTRAP_FRAME *ExceptionFrame, _CONTEXT *ContextRecord)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v3 = TrapFrame;
  NewIrql = KeGetCurrentIrql();
  if ( !NewIrql )
    NewIrql = KfRaiseIrql(1u);
  v4 = ContextRecord->ContextFlags;
  v5 = 0xFFFF;
  if ( (ContextRecord->ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL )
  {
    ContextRecord->Ebp = TrapFrame->Ebp;
    ContextRecord->Eip = TrapFrame->Eip;
    v6 = TrapFrame->SegCs;
    if ( (v6 & 0xFFF8) == 0 && (TrapFrame->EFlags & 0x20000) == 0 )
      v6 = TrapFrame->TempSegCs;
    ContextRecord->SegCs = v6;
    ContextRecord->EFlags = TrapFrame->EFlags;
    ContextRecord->SegSs = KiSegSsFromTrapFrame(TrapFrame);
    ContextRecord->Esp = KiEspFromTrapFrame(TrapFrame);
  }
  if ( (v4 & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS )
  {
    if ( (TrapFrame->EFlags & 0x20000) != 0 )
    {
      ContextRecord->SegGs = v5 & TrapFrame->V86Gs;
      ContextRecord->SegFs = v5 & TrapFrame->V86Fs;
      ContextRecord->SegEs = v5 & TrapFrame->V86Es;
      v7 = TrapFrame->V86Ds;
    }
    else
    {
      if ( TrapFrame->SegCs == 8 )
      {
        TrapFrame->SegGs = 0;
        TrapFrame->SegFs = 0x30;
        TrapFrame->SegEs = 0x23;
        TrapFrame->SegDs = 0x23;
      }
      ContextRecord->SegGs = v5 & TrapFrame->SegGs;
      ContextRecord->SegFs = v5 & TrapFrame->SegFs;
      ContextRecord->SegEs = v5 & TrapFrame->SegEs;
      v7 = TrapFrame->SegDs;
    }
    ContextRecord->SegDs = v5 & v7;
  }
  v8 = ContextRecord->ContextFlags;
  if ( (ContextRecord->ContextFlags & 0x10002) == 65538 )
  {
    ContextRecord->Edi = TrapFrame->Edi;
    ContextRecord->Esi = TrapFrame->Esi;
    ContextRecord->Ebx = TrapFrame->Ebx;
    ContextRecord->Ecx = TrapFrame->Ecx;
    ContextRecord->Edx = TrapFrame->Edx;
    ContextRecord->Eax = TrapFrame->Eax;
  }
  if ( (v8 & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS
    && (TrapFrame->SegCs & 1) != 0
    && KeI386NpxPresent )
  {
    KiFlushNPXState(0);
    qmemcpy(ContextRecord->ExtendedRegisters, &TrapFrame[1], sizeof(ContextRecord->ExtendedRegisters));
    v3 = TrapFrame;
  }
  if ( (ContextRecord->ContextFlags & 0x10008) == 65544 && (v3->SegCs & 1) != 0 )
  {
    v9 = &v3[1].DbgEbp;
    if ( KeI386NpxPresent )
    {
      if ( KeI386FxsrPresent == 1 )
      {
        v9 = v16;
        KiFlushNPXState(v16);
      }
      else
      {
        KiFlushNPXState(0);
      }
      ContextRecord->FloatSave.ControlWord = *v9;
      ContextRecord->FloatSave.StatusWord = v9[1];
      ContextRecord->FloatSave.TagWord = v9[2];
      ContextRecord->FloatSave.ErrorOffset = v9[3];
      ContextRecord->FloatSave.ErrorSelector = v9[4];
      ContextRecord->FloatSave.DataOffset = v9[5];
      ContextRecord->FloatSave.DataSelector = v9[6];
      ContextRecord->FloatSave.Cr0NpxState = v3[4].Eip;
      for ( i = 0; i < 0x50; ++i )
        ContextRecord->FloatSave.RegisterArea[i] = *(v9 + i + 28);
    }
    else if ( KiEm87StateToNpxFrame(&ContextRecord->FloatSave) )
    {
      ContextRecord->FloatSave.Cr0NpxState = v3[4].Eip;
    }
    else
    {
      ContextRecord->ContextFlags &= 0xFFFFFFF7;
    }
  }
  if ( (ContextRecord->ContextFlags & CONTEXT_DEBUG_REGISTERS) == CONTEXT_DEBUG_REGISTERS )
  {
    v11 = (v3->SegCs & 1) == 0;
    v12 = KeGetCurrentThread()->DebugActive;
    v15[0] = v12;
    if ( (!v11 || (v3->EFlags & 0x20000) != 0) && v12 )
    {
      ContextRecord->Dr0 = v3->Dr0;
      ContextRecord->Dr1 = v3->Dr1;
      ContextRecord->Dr2 = v3->Dr2;
      ContextRecord->Dr3 = v3->Dr3;
      ContextRecord->Dr6 = v3->Dr6;
      v13 = KiUpdateDr7(v3->Dr7, v15);
    }
    else
    {
      v13 = 0;
      ContextRecord->Dr0 = 0;
      ContextRecord->Dr1 = 0;
      ContextRecord->Dr2 = 0;
      ContextRecord->Dr3 = 0;
      ContextRecord->Dr6 = 0;
    }
    ContextRecord->Dr7 = v13;
  }
  if ( !NewIrql )
    KfLowerIrql(0);
  xHalReferenceHandler(v16[30]);
}

  備份完畢後,我就可以隨心所欲改了。_esp = ((ContextRecord.Esp & 0xFFFFFFFC) - 0x2DC)這個就是所謂的提棧操作,為構造的新Context提供空間,ContextRecord.Esp & 0xFFFFFFFC就是讓記憶體實現4位元組對齊。將內部的段暫存器相關改為3環的環境,我們重點看下面的程式碼:

TrapFrame->HardwareEsp = _esp;
TrapFrame->Eip = KeUserApcDispatcher;
TrapFrame->ErrCode = 0;
*_esp = NormalRoutine;
_esp[1] = NormalContext;
_esp[2] = SystemArgument1;
_esp[3] = SystemArgument2;

  可以看出,這幾個操作就是修改EIP並壓棧的操作,KeUserApcDispatcher是一個全域性變數,這個是3環的函式,在系統啟動完畢後就會賦值,我們在WinDbg看看是何方神聖:

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
……

Failed to get VadRoot
PROCESS 89b51b28  SessionId: 0  Cid: 05f0    Peb: 7ffde000  ParentCid: 05d4
    DirBase: 12fc01c0  ObjectTable: e17efb00  HandleCount: 353.
    Image: explorer.exe

……

kd> .process 89b51b28
ReadVirtual: 89b51b40 not properly sign extended
Implicit process is now 89b51b28
WARNING: .cache forcedecodeuser is not enabled
kd> u 7c92e430
ntdll!KiUserApcDispatcher:
7c92e430 8d7c2410        lea     edi,[esp+10h]
7c92e434 58              pop     eax
7c92e435 ffd0            call    eax
7c92e437 6a01            push    1
7c92e439 57              push    edi
7c92e43a e801ecffff      call    ntdll!NtContinue (7c92d040)
7c92e43f 90              nop
ntdll!KiUserCallbackDispatcher:
7c92e440 83c404          add     esp,4

  可以看出這個函式就是KiUserApcDispatcher,在ntdll.dll當中,我們用IDA開啟看看:

; __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]
                pop     eax
                call    eax
                push    1
                push    edi
                call    _ZwContinue@8   ; ZwContinue(x,x)
                nop
_KiUserApcDispatcher@20 endp ; sp-analysis failed

  esp + 10h就是構造出來的_Trap_Frame結構體的地址。我們畫個堆疊圖就能明白了:

APC 篇—— APC 執行

  當使用者在3環呼叫QueueUserAPC函式來插入APC時,不需要提供NormalRoutine,這個引數是在QueueUserAPC內部指定的,是BaseDispatchAPC這個函式,可以自行翻閱我分析的過程。
  我們下面再看看ZwContinue函式,它的作用如下:

  1. 返回核心,如果還有使用者APC,重複上面的執行過程。
  2. 如果沒有需要執行的使用者APC,會將CONTEXT賦值給Trap_Frame結構體。就像從來沒有修改過一樣。ZwContinue後面的程式碼不會執行,執行緒從哪裡進0環仍然會從哪裡回去。

  我們繼續做出如下總結:

  1. 核心APC線上程切換時執行,不需要換棧,比較簡單,一個迴圈執行完畢。
  2. 使用者APC在系統呼叫、中斷或異常返回3環前會進行判斷,如果有要執行的使用者APC,再執行。
  3. 使用者APC執行前會先執行核心APC。

  到此,與APC執行相關的知識,就到此結束了。

下一篇

  APC 篇——總結與提升

相關文章