寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。
? 華麗的分割線 ?
練習及參考
本次答案均為參考,可以與我的答案不一致,但必須成功通過。
1️⃣ 自己編寫WriteProcessMemory
函式(不使用任何DLL
,直接呼叫0環函式)並在程式碼中使用。
? 點選檢視答案 ?
話不多說,給個效果圖看看,程式碼見摺疊:
? 點選檢視程式碼 ?
```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 掛入