??此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看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;}??该函数在kernel32.dll当中,我们通过IDA轻松定位到了该函数:
; BOOL __stdcall TerminateThread(HANDLE hThread, DWORD dwExitCode) public _TerminateThread@8_TerminateThread@8 proc near ; DATA XREF: .text:off_7C802654↑ohThread = dword ptr 8dwExitCode = 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 eaxloc_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↑oAccessMode = byte ptr -4ThreadHandle = dword ptr 8ExitStatus = 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, ediloc_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 -0Chres = dword ptr -4Thread = dword ptr 8arg_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 ebxloc_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内核代码,与之对比:
NTSTATUSPspTerminateThreadByPointer( 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实现控制其他线程,实现终止线程。
??该函数和TerminateThread函数调用流程是一样的,我们直接从内核层函数调用开始,三环的自行分析就行了。如下是内核函数:
; NTSTATUS __stdcall NtSuspendThread(HANDLE ThreadHandle, PULONG PreviousSuspendCount)_NtSuspendThread@8 proc near ; DATA XREF: .text:0042AF84↑ovar_30 = dword ptr -30hvar_2C = dword ptr -2Chvar_28 = dword ptr -28hvar_24 = dword ptr -24hAccessMode = byte ptr -20hObject = dword ptr -1Chms_exc = CPPEH_RECORD ptr -18hThreadHandle = dword ptr 8PreviousSuspendCount= 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], ebxloc_4F3B95: ; CODE XREF: NtSuspendThread(x,x)+35↑j mov eax, [esi] mov [esi], eaxloc_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泄露代码与之对比:
ULONGKeSuspendThread ( 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队列之前,我们先看看下面的结构体:
kd> dt _KTHREADnt!_KTHREAD ... +0x034 ApcState : _KAPC_STATE ... +0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE ... +0x14c SavedApcState : _KAPC_STATE ... +0x165 ApcStateIndex : UChar +0x166 ApcQueueable : UChar ...??上面展示的就是KTHREAD结构体中与APC相关的内容。下面我们来详细介绍它们的用途。
??它们存储的是一个结构体,我们上篇简单介绍过,再拿来看看:
kd> dt _KAPC_STATEntdll!_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。
??为了操作方便,_KTHREAD结构体中定义了一个指针数组ApcStatePointer ,一共两个成员。不同情况下的存储的值我们用下面的表格进行展示:
| 情况 | ApcStatePointer[0] | ApcStatePointer[1] |
|---|---|---|
| 正常情况 | ApcState | SavedApcState |
| 挂靠情况 | SavedApcState | ApcState |
??用来标识当前线程处于什么状态。如果值为0则为正常状态;如果值为1则为挂靠状态。
??用于表示是否可以向线程的APC队列中插入APC。一个线程不可能每时每刻都能被插入APC的。比如当线程正在执行退出的代码时,会将这个值设置为0 ,如果此时执行插入APC的代码(KiInsertQueueApc函数),在插入函数中会判断这个值的状态,如果为0,则插入失败。当线程为系统内核线程,没有3环的部分,如果插入3环的APC,这也是不行的。
??正常情况下,向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 挂入
