APC 篇——備用 APC 佇列

寂靜的羽夏發表於2022-01-26

寫在前面

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

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

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


? 華麗的分割線 ?


練習及參考

本次答案均為參考,可以與我的答案不一致,但必須成功通過。

1️⃣ 自己編寫WriteProcessMemory函式(不使用任何DLL,直接呼叫0環函式)並在程式碼中使用。

? 點選檢視答案 ?


  話不多說,給個效果圖看看,程式碼見摺疊:

APC 篇——備用 APC 佇列


? 點選檢視程式碼 ?
```cpp
#include "stdafx.h"
#include <stdlib.h>

#define  _WIN32_WINNT 0x400 //解決 QueueUserAPC 函式未定義
#include <windows.h>

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

DWORD WINAPI ThreadProc(VOID* Param)
{
    for (int i =0 ;i<100;i++)
    {
        SleepEx(1000,TRUE); //思考為什麼?
        //Sleep(1000);
        printf("Running\n");
    }
    return 0;
}

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

TerminateThread 分析

  該函式在kernel32.dll當中,我們通過IDA輕鬆定位到了該函式:

; BOOL __stdcall TerminateThread(HANDLE hThread, DWORD dwExitCode)
                public _TerminateThread@8
_TerminateThread@8 proc near            ; DATA XREF: .text:off_7C802654↑o
hThread         = dword ptr  8
dwExitCode      = dword ptr  0Ch

; FUNCTION CHUNK AT .text:7C8449A4 SIZE 00000016 BYTES
                mov     edi, edi
                push    ebp
                mov     ebp, esp
                cmp     [ebp+hThread], 0
                jz      loc_7C8449A4
                push    [ebp+dwExitCode] ; ExitStatus
                push    [ebp+hThread]   ; ThreadHandle
                call    ds:__imp__NtTerminateThread@8 ; NtTerminateThread(x,x)
                test    eax, eax
                jl      loc_7C8449AD
                xor     eax, eax
                inc     eax

loc_7C81CB49:                           ; CODE XREF: TerminateThread(x,x)+27E92↓j
                pop     ebp
                retn    8
_TerminateThread@8 endp

  發現該函式會呼叫NtTerminateThread,這個函式來自ntdll.dll當中,如下所示:

; Exported entry 349. NtTerminateThread
; Exported entry 1158. ZwTerminateThread

; =============== S U B R O U T I N E =======================================


; __stdcall ZwTerminateThread(x, x)
                public _ZwTerminateThread@8
_ZwTerminateThread@8 proc near          ; CODE XREF: LdrpGenericExceptionFilter(x,x)+90FD↓p
                                        ; RtlQueryProcessDebugInformation(x,x,x)+10B↓p ...
                mov     eax, 102h       ; NtTerminateThread
                mov     edx, 7FFE0300h
                call    dword ptr [edx]
                retn    8
_ZwTerminateThread@8 endp

  可以看出,這個開始進入系統核心了,我們根據PCHunter很容易定義到核心函式為NtTerminateThread,如下所示:

; NTSTATUS __stdcall NtTerminateThread(HANDLE ThreadHandle, NTSTATUS ExitStatus)
_NtTerminateThread@8 proc near          ; DATA XREF: .text:0042AF94↑o

AccessMode      = byte ptr -4
ThreadHandle    = dword ptr  8
ExitStatus      = dword ptr  0Ch

                mov     edi, edi
                push    ebp
                mov     ebp, esp
                push    ecx
                push    ebx
                push    esi
                push    edi
                xor     edi, edi
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                cmp     [ebp+ThreadHandle], edi
                mov     esi, eax
                jnz     short loc_4F1E4F
                mov     eax, [esi+_ETHREAD.Tcb.ApcState.Process]
                cmp     [eax+_EPROCESS.ActiveThreads], 1
                jnz     short loc_4F1E8B
                mov     eax, 0C00000DBh
                jmp     short loc_4F1EAA
; ---------------------------------------------------------------------------

loc_4F1E4F:                             ; CODE XREF: NtTerminateThread(x,x)+16↑j
                cmp     [ebp+ThreadHandle], 0FFFFFFFEh
                jz      short loc_4F1E8B
                mov     al, [esi+_ETHREAD.Tcb.PreviousMode]
                push    0               ; HandleInformation
                mov     [ebp+AccessMode], al
                lea     eax, [ebp+ThreadHandle]
                push    eax             ; Object
                push    dword ptr [ebp+AccessMode] ; AccessMode
                push    _PsThreadType   ; ObjectType
                push    1               ; DesiredAccess
                push    [ebp+ThreadHandle] ; Handle
                call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
                mov     edi, eax
                test    edi, edi
                jl      short loc_4F1EA8
                mov     ebx, [ebp+ThreadHandle]
                cmp     ebx, esi
                jnz     short loc_4F1E96
                mov     ecx, ebx        ; Object
                call    @ObfDereferenceObject@4 ; ObfDereferenceObject(x)

loc_4F1E8B:                             ; CODE XREF: NtTerminateThread(x,x)+22↑j
                                        ; NtTerminateThread(x,x)+2F↑j
                push    [ebp+ExitStatus] ; int
                push    esi             ; Response
                call    _PspTerminateThreadByPointer@8 ; PspTerminateThreadByPointer(x,x)
                jmp     short loc_4F1EA8
; ---------------------------------------------------------------------------

loc_4F1E96:                             ; CODE XREF: NtTerminateThread(x,x)+5E↑j
                push    [ebp+ExitStatus] ; int
                push    ebx             ; Response
                call    _PspTerminateThreadByPointer@8 ; PspTerminateThreadByPointer(x,x)
                mov     ecx, ebx        ; Object
                mov     edi, eax
                call    @ObfDereferenceObject@4 ; ObfDereferenceObject(x)

loc_4F1EA8:                             ; CODE XREF: NtTerminateThread(x,x)+57↑j
                                        ; NtTerminateThread(x,x)+70↑j
                mov     eax, edi

loc_4F1EAA:                             ; CODE XREF: NtTerminateThread(x,x)+29↑j
                pop     edi
                pop     esi
                pop     ebx
                leave
                retn    8
_NtTerminateThread@8 endp

  我們要求是分析該函式是怎樣終止別的執行緒的,故里面的細節不要仔細分析,我們看到該函式呼叫了關鍵函式PspTerminateThreadByPointer

? 點選檢視反彙編 ?
; int __stdcall PspTerminateThreadByPointer(PETHREAD Thread, int a2)
_PspTerminateThreadByPointer@8 proc near
                                        ; CODE XREF: PspUserThreadStartup(x,x)+A9↑p
                                        ; PspSystemThreadStartup(x,x)+3B↑p ...

Interval        = _LARGE_INTEGER ptr -0Ch
res             = dword ptr -4
Thread          = dword ptr  8
arg_4           = dword ptr  0Ch

                mov     edi, edi
                push    ebp
                mov     ebp, esp
                sub     esp, 0Ch
                or      dword ptr [ebp+Interval+4], 0FFFFFFFFh
                push    esi
                push    edi
                mov     edi, [ebp+Thread]
                lea     esi, [edi+248h]
                test    byte ptr [esi], 40h
                mov     dword ptr [ebp+Interval], 0FFF0BDC0h
                jz      short loc_4F1B3C
                mov     eax, [edi+220h]
                add     eax, 174h
                push    eax             ; BugCheckParameter3
                push    edi             ; Response
                push    offset aTerminatingCri ; "Terminating critical thread 0x%p (in %s"...
                call    _PspCatchCriticalBreak@12 ; PspCatchCriticalBreak(x,x,x)

loc_4F1B3C:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+21↑j
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                cmp     edi, eax
                jnz     short loc_4F1B54
                xor     eax, eax
                inc     eax
                lock or [esi], eax
                push    [ebp+arg_4]
                call    _PspExitThread@4 ; PspExitThread(x)

loc_4F1B54:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+42↑j
                test    byte ptr [esi], 10h
                jz      short loc_4F1B63
                mov     eax, 0C0000022h
                jmp     loc_4F1BF6
; ---------------------------------------------------------------------------

loc_4F1B63:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+55↑j
                push    ebx
                xor     ebx, ebx
                mov     [ebp+res], ebx
                mov     esi, 'xEsP'
                jmp     short loc_4F1B7B
; ---------------------------------------------------------------------------

loc_4F1B70:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+86↓j
                lea     eax, [ebp+Interval]
                push    eax             ; Interval
                push    ebx             ; Alertable
                push    ebx             ; WaitMode
                call    _KeDelayExecutionThread@12 ; KeDelayExecutionThread(x,x,x)

loc_4F1B7B:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+6C↑j
                push    esi             ; Tag
                push    30h ; '0'       ; NumberOfBytes
                push    ebx             ; PoolType
                call    _ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)
                mov     edi, eax
                cmp     edi, ebx
                jz      short loc_4F1B70
                mov     ecx, [ebp+Thread]
                xor     edx, edx
                inc     edx
                add     ecx, 248h
                mov     eax, [ecx]

loc_4F1B98:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+9E↓j
                mov     esi, eax
                or      esi, edx
                lock cmpxchg [ecx], esi
                jnz     short loc_4F1B98
                test    dl, al
                jnz     short loc_4F1BEB
                push    [ebp+arg_4]
                push    ebx
                push    offset _PspExitNormalApc@12 ; PspExitNormalApc(x,x,x)
                push    offset _ExFreeCallBack@4 ; ExFreeCallBack(x)
                push    offset _PsExitSpecialApc@20 ; PsExitSpecialApc(x,x,x,x,x)
                push    ebx
                push    [ebp+Thread]
                push    edi
                call    _KeInitializeApc@32 ; KeInitializeApc(x,x,x,x,x,x,x,x)
                push    2
                push    ebx
                push    edi
                push    edi
                call    _KeInsertQueueApc@16 ; KeInsertQueueApc(x,x,x,x)
                test    al, al
                jnz     short loc_4F1BE1
                push    ebx             ; Tag
                push    edi             ; P
                call    _ExFreePoolWithTag@8 ; ExFreePoolWithTag(x,x)
                mov     [ebp+res], 0C0000001h
                jmp     short loc_4F1BF2
; ---------------------------------------------------------------------------

loc_4F1BE1:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+CD↑j
                push    [ebp+Thread]
                call    _KeForceResumeThread@4 ; KeForceResumeThread(x)
                jmp     short loc_4F1BF2
; ---------------------------------------------------------------------------

loc_4F1BEB:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+A2↑j
                push    ebx             ; Tag
                push    edi             ; P
                call    _ExFreePoolWithTag@8 ; ExFreePoolWithTag(x,x)

loc_4F1BF2:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+DD↑j
                                        ; PspTerminateThreadByPointer(x,x)+E7↑j
                mov     eax, [ebp+res]
                pop     ebx

loc_4F1BF6:                             ; CODE XREF: PspTerminateThreadByPointer(x,x)+5C↑j
                pop     edi
                pop     esi
                leave
                retn    8
_PspTerminateThreadByPointer@8 endp

  為了方便閱讀,用F5翻譯為虛擬碼,如下所示:

int __stdcall PspTerminateThreadByPointer(PETHREAD Thread, int a2)
{
  volatile signed __int32 *v2; // esi
  bool v3; // zf
  PVOID buffer; // edi
  union _LARGE_INTEGER Interval; // [esp+8h] [ebp-Ch] BYREF
  int res; // [esp+10h] [ebp-4h]

  v2 = &Thread[1].WaitBlock[1];
  v3 = (Thread[1].WaitBlock[1].WaitListEntry.Flink & 0x40) == 0;
  Interval.QuadPart = -1000000i64;
  if ( !v3 )
    PspCatchCriticalBreak(
      "Terminating critical thread 0x%p (in %s)\n",
      Thread,
      &Thread[1].WaitListEntry.Flink[46].Blink);
  if ( Thread == KeGetCurrentThread() )
  {
    _InterlockedOr(v2, 1u);
    PspExitThread(a2);
  }
  if ( (*v2 & 0x10) != 0 )
    return 0xC0000022;
  res = 0;
  while ( 1 )
  {
    buffer = ExAllocatePoolWithTag(NonPagedPool, 0x30u, 'xEsP');
    if ( buffer )
      break;
    KeDelayExecutionThread(0, 0, &Interval);
  }
  if ( (_InterlockedOr(&Thread[1].WaitBlock[1], 1u) & 1) != 0 )
  {
    ExFreePoolWithTag(buffer, 0);
  }
  else
  {
    KeInitializeApc(buffer, Thread, 0, PsExitSpecialApc, ExFreeCallBack, PspExitNormalApc, 0, a2);
    if ( KeInsertQueueApc(buffer, buffer, 0, 2) )
    {
      KeForceResumeThread(Thread);
    }
    else
    {
      ExFreePoolWithTag(buffer, 0);
      res = 0xC0000001;
    }
  }
  return res;
}

  根據虛擬碼,我們可以看出如果發現結束的執行緒是自身執行緒,就會呼叫PspExitThread結束自己,如果不是自己,就會使用KeInitializeApc初始化APC,然後呼叫KeInsertQueueApc插入APC。如下是洩露的NT3.5核心程式碼,與之對比:

? 點選檢視程式碼 ?
NTSTATUS
PspTerminateThreadByPointer(
    IN PETHREAD Thread,
    IN NTSTATUS ExitStatus
    )

/*++

Routine Description:

    This function causes the specified thread to terminate.

Arguments:

    ThreadHandle - Supplies a referenced pointer to the thread to terminate.

    ExitStatus - Supplies the exit status associated with the thread.

Return Value:

    TBD

--*/

{

    PKAPC ExitApc;

    PAGED_CODE();

    if ( Thread == PsGetCurrentThread() ) {
        ObDereferenceObject(Thread);
        PspExitThread(ExitStatus);

        //
        // Never Returns
        //

    } else {
        ExitApc = ExAllocatePool(NonPagedPool,(ULONG)sizeof(KAPC));

        if ( !ExitApc ) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        KeInitializeApc(
            ExitApc,
            &Thread->Tcb,
            OriginalApcEnvironment,
            PsExitSpecialApc,
            NULL,
            PspExitNormalApc,
            KernelMode,
            (PVOID) ExitStatus
            );


        if ( !KeInsertQueueApc(ExitApc,ExitApc,NULL, 2) ) {
            ExFreePool(ExitApc);

            return STATUS_UNSUCCESSFUL;
        }
    }

    return STATUS_SUCCESS;
}

  可以看出,TerminateThread這個函式是通過APC實現控制其他執行緒,實現終止執行緒。

SuspendThread 分析

  該函式和TerminateThread函式呼叫流程是一樣的,我們直接從核心層函式呼叫開始,三環的自行分析就行了。如下是核心函式:

; NTSTATUS __stdcall NtSuspendThread(HANDLE ThreadHandle, PULONG PreviousSuspendCount)
_NtSuspendThread@8 proc near            ; DATA XREF: .text:0042AF84↑o

var_30          = dword ptr -30h
var_2C          = dword ptr -2Ch
var_28          = dword ptr -28h
var_24          = dword ptr -24h
AccessMode      = byte ptr -20h
Object          = dword ptr -1Ch
ms_exc          = CPPEH_RECORD ptr -18h
ThreadHandle    = dword ptr  8
PreviousSuspendCount= dword ptr  0Ch

; __unwind { // __SEH_prolog
                push    20h
                push    offset stru_402FE8
                call    __SEH_prolog
                xor     ebx, ebx
                mov     [ebp+ms_exc.registration.TryLevel], ebx
                mov     eax, large fs:124h
                mov     [ebp+var_30], eax
                mov     al, [eax+140h]
                mov     [ebp+AccessMode], al
                mov     esi, [ebp+PreviousSuspendCount]
                cmp     al, bl
                jz      short loc_4F3B99
                cmp     esi, ebx
                jz      short loc_4F3B99
                mov     eax, _MmUserProbeAddress
                cmp     esi, eax
                jb      short loc_4F3B95
                mov     [eax], ebx

loc_4F3B95:                             ; CODE XREF: NtSuspendThread(x,x)+35↑j
                mov     eax, [esi]
                mov     [esi], eax

loc_4F3B99:                             ; CODE XREF: NtSuspendThread(x,x)+28↑j
                                        ; NtSuspendThread(x,x)+2C↑j
                or      [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
                push    ebx             ; HandleInformation
                lea     eax, [ebp+Object]
                push    eax             ; Object
                push    dword ptr [ebp+AccessMode] ; AccessMode
                push    _PsThreadType   ; ObjectType
                push    2               ; DesiredAccess
                push    [ebp+ThreadHandle] ; Handle
                call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
                cmp     eax, ebx
                jl      short loc_4F3C15
                lea     eax, [ebp+var_24]
                push    eax
                push    [ebp+Object]
                call    _PsSuspendThread@8 ; PsSuspendThread(x,x)
                mov     edi, eax
                mov     ecx, [ebp+Object] ; Object
                call    @ObfDereferenceObject@4 ; ObfDereferenceObject(x)
                mov     [ebp+ms_exc.registration.TryLevel], 1
                cmp     esi, ebx
                jz      short loc_4F3BF5
                mov     eax, [ebp+var_24]
                mov     [esi], eax
                jmp     short loc_4F3BF5
; ---------------------------------------------------------------------------

loc_4F3BE1:                             ; DATA XREF: .text:stru_402FE8↑o
                mov     eax, [ebp+ms_exc.exc_ptr]
                mov     eax, [eax]
                mov     eax, [eax]
                mov     [ebp+var_28], eax
                xor     eax, eax
                inc     eax
                retn
; ---------------------------------------------------------------------------

loc_4F3BEF:                             ; DATA XREF: .text:stru_402FE8↑o
                mov     esp, [ebp+ms_exc.old_esp]
                mov     edi, [ebp+var_28]

loc_4F3BF5:                             ; CODE XREF: NtSuspendThread(x,x)+7C↑j
                                        ; NtSuspendThread(x,x)+83↑j
                or      [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
                mov     eax, edi
                jmp     short loc_4F3C15
; ---------------------------------------------------------------------------

loc_4F3BFD:                             ; DATA XREF: .text:stru_402FE8↑o
                mov     eax, [ebp+ms_exc.exc_ptr]
                mov     eax, [eax]
                mov     eax, [eax]
                mov     [ebp+var_2C], eax
                xor     eax, eax
                inc     eax
                retn
; ---------------------------------------------------------------------------

loc_4F3C0B:                             ; DATA XREF: .text:stru_402FE8↑o
                mov     esp, [ebp+ms_exc.old_esp]
                or      [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
                mov     eax, [ebp+var_2C]

loc_4F3C15:                             ; CODE XREF: NtSuspendThread(x,x)+5B↑j
                                        ; NtSuspendThread(x,x)+9F↑j
                call    __SEH_epilog
                retn    8
; } // starts at 4F3B5C
_NtSuspendThread@8 endp

  我們注意到,該函式呼叫了PsSuspendThread函式來實現功能,點選去看看,為了方便閱讀,我直接放上它的虛擬碼:

unsigned int __stdcall PsSuspendThread(PETHREAD Thread, _DWORD *PreviousSuspendCount)
{
  unsigned int v2; // esi
  int v4; // [esp+18h] [ebp-1Ch]

  v2 = 0;
  v4 = 0;
  if ( Thread == KeGetCurrentThread() )
  {
    v4 = KeSuspendThread(Thread);
    goto LABEL_10;
  }
  if ( ExAcquireRundownProtection(&Thread[1].WaitBlock[0].WaitListEntry.Blink) )
  {
    if ( (Thread[1].WaitBlock[1].WaitListEntry.Flink & 1) == 0 )
    {
      v4 = KeSuspendThread(Thread);
      if ( (Thread[1].WaitBlock[1].WaitListEntry.Flink & 1) == 0 )
      {
LABEL_8:
        ExReleaseRundownProtection(&Thread[1].WaitBlock[0].WaitListEntry.Blink);
        goto LABEL_10;
      }
      KeForceResumeThread(Thread);
      v4 = 0;
    }
    v2 = 0xC000004B;
    goto LABEL_8;
  }
  v2 = 0xC000004B;
LABEL_10:
  if ( PreviousSuspendCount )
    *PreviousSuspendCount = v4;
  return v2;
}

  而這個函式又呼叫KeSuspendThread函式進行實現,我們直接看看其虛擬碼:

int __stdcall KeSuspendThread(PETHREAD Thread)
{
  int res; // edi
  struct _KLOCK_QUEUE_HANDLE LockHandle; // [esp+Ch] [ebp-Ch] BYREF

  KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock, &LockHandle);
  res = Thread->SuspendCount;
  if ( res == 0x7F )
  {
    KeReleaseInStackQueuedSpinLock(&LockHandle);
    ExRaiseStatus(0xC000004A);
  }
  if ( Thread->ApcQueueable == 1 )
  {
    ++Thread->SuspendCount;
    if ( !res && !Thread->FreezeCount && !KiInsertQueueApc(&Thread->SuspendApc, 0) )
      --Thread->SuspendSemaphore.Header.SignalState;
  }
  KeReleaseInStackQueuedSpinLock(&LockHandle);
  return res;
}

  我們可以看出,SuspendThread這個函式也是通過APC實現控制其他執行緒,實現掛起執行緒,如下同是NT3.5洩露程式碼與之對比:

? 點選檢視程式碼 ?
ULONG
KeSuspendThread (
    IN PKTHREAD Thread
    )

/*++

Routine Description:

    This function suspends the execution of a thread. If the suspend count
    overflows the maximum suspend count, then a condition is raised.

Arguments:

    Thread  - Supplies a pointer to a dispatcher object of type thread.

Return Value:

    The previous suspend count.

--*/

{

    ULONG OldCount;
    KIRQL OldIrql;

    ASSERT_THREAD(Thread);

    //
    // Raise IRQL to dispatcher level and lock dispatcher database.
    //

    KiLockDispatcherDatabase(&OldIrql);

    //
    // Capture the current suspend count.
    //

    OldCount = Thread->SuspendCount;

    //
    // If the suspend count is at its maximum value, then unlock dispatcher
    // database, lower IRQL to its previous value, and raise an error
    // condition.
    //

    if (OldCount == MAXIMUM_SUSPEND_COUNT) {

        //
        // Unlock the dispatcher database and raise an exception.
        //

        KiUnlockDispatcherDatabase(OldIrql);
        ExRaiseStatus(STATUS_SUSPEND_COUNT_EXCEEDED);
    }

    //
    // Increment the suspend count. If the thread was not previously suspended,
    // then queue the thread's suspend APC.
    //

    Thread->SuspendCount += 1;
    if ((OldCount == 0) && (Thread->FreezeCount == 0)) {
        if (KiInsertQueueApc(&Thread->SuspendApc, RESUME_INCREMENT) == FALSE) {
            Thread->SuspendSemaphore.Header.SignalState -= 1;
        }
    }

    //
    // Unlock dispatcher database and lower IRQL to its previous
    // value.
    //

    KiUnlockDispatcherDatabase(OldIrql);

    //
    // Return the previous suspend count.
    //

    return OldCount;
}

備用 APC 佇列

  在上一篇中我們講過,如果想讓執行緒做什麼事情,就給它的APC佇列裡面掛一個APC。在介紹備用APC佇列之前,我們先看看下面的結構體:

kd> dt _KTHREAD
nt!_KTHREAD
    ...
    +0x034 ApcState         : _KAPC_STATE
    ...
    +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
    ...
    +0x14c SavedApcState    : _KAPC_STATE
    ...
    +0x165 ApcStateIndex    : UChar
    +0x166 ApcQueueable     : UChar
    ...

  上面展示的就是KTHREAD結構體中與APC相關的內容。下面我們來詳細介紹它們的用途。

ApcState 與 SavedApcState

  它們儲存的是一個結構體,我們上篇簡單介紹過,再拿來看看:

kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x010 Process          : Ptr32 _KPROCESS
   +0x014 KernelApcInProgress : UChar
   +0x015 KernelApcPending : UChar
   +0x016 UserApcPending   : UChar

  這個結構體儲存了它的“養父”是誰以及APC相關的資訊。有了ApcState了,為啥還要有SavedApcState?我們可以考慮一個事情,執行緒APC佇列中的APC函式都是與程式相關聯的,具體點說:A程式的T執行緒中的所有APC函式,要訪問的記憶體地址都是A程式的。但執行緒是可以掛靠到其他的程式:比如A程式的執行緒T,通過修改Cr3(改為B程式的頁目錄基址),就可以訪問B程式地址空間,即所謂“程式掛靠”。
  當T執行緒掛靠B程式後,APC佇列中儲存的卻仍然是原來的APC。具體點說,比如某個APC函式要讀取一個地址為0x12345678的資料,如果此時進行讀取,讀到的將是B程式的地址空間,這樣邏輯就錯誤了。為了避免混亂,在T執行緒掛靠B程式時,會將ApcState中的值暫時儲存到SavedApcState中,等回到原程式A時,再將APC佇列恢復,這就是所謂的備用APC佇列。
  當執行緒是否處於掛靠環境下,ApcState的意義是不一樣的。我們設個條件:A程式的T執行緒掛靠B程式,A是T的所屬程式,B是T的掛靠程式。那麼如果處於掛靠環境,ApcState儲存是就是B程式相關的APC函式,而SavedApcState儲存的是A程式相關的APC函式。在正常情況下,當前程式就是所屬程式A,如果是掛靠情況下,當前程式就是掛靠程式B。

ApcStatePointer

  為了操作方便,_KTHREAD結構體中定義了一個指標陣列ApcStatePointer ,一共兩個成員。不同情況下的儲存的值我們用下面的表格進行展示:

情況 ApcStatePointer[0] ApcStatePointer[1]
正常情況 ApcState SavedApcState
掛靠情況 SavedApcState ApcState

ApcStateIndex

  用來標識當前執行緒處於什麼狀態。如果值為0則為正常狀態;如果值為1則為掛靠狀態。

ApcQueueable

  用於表示是否可以向執行緒的APC佇列中插入APC。一個執行緒不可能每時每刻都能被插入APC的。比如當執行緒正在執行退出的程式碼時,會將這個值設定為0 ,如果此時執行插入APC的程式碼(KiInsertQueueApc函式),在插入函式中會判斷這個值的狀態,如果為0,則插入失敗。當執行緒為系統核心執行緒,沒有3環的部分,如果插入3環的APC,這也是不行的。

ApcStatePointer 與 ApcStateIndex 組合定址

  正常情況下,向ApcState佇列中插入APC時,ApcStatePointer[0]指向ApcState,此時ApcStateIndex的值為0,ApcStatePointer[ApcStateIndex]指向ApcState
  掛靠情況下,向ApcState佇列中插入APC時,ApcStatePointer[1]指向ApcState,此時ApcStateIndex的值為1,ApcStatePointer[ApcStateIndex]指向ApcState
  於是我們可以下一個結論:無論什麼環境下,ApcStatePointer[ApcStateIndex]指向的都是ApcState,也就是該值總是表示執行緒當前使用的APC狀態。

本節練習

本節的答案將會在下一節進行講解,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。

  俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習不多,請保質保量的完成,本篇參考將會在正文給出。

1️⃣ 分析NtReadVirtualMemory在掛靠時如何備份和恢復APC佇列的。

下一篇

  APC 篇—— APC 掛入

相關文章