写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看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 挂入

APC 篇——备用 APC 队列的更多相关文章

  1. APC 篇——初识 APC

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  2. APC 篇—— APC 挂入

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  3. 羽夏看Win系统内核—— APC 篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  4. 备用APC队列

     Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html 备用APC队列 占坑:这一节跟进程挂靠相关联,我们先把进程挂靠给 ...

  5. APC 篇—— APC 执行

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  6. iOS开发数据库篇—FMDB数据库队列

    iOS开发数据库篇—FMDB数据库队列 一.代码示例 1.需要先导入FMDB框架和头文件,由于该框架依赖于libsqlite库,所以还应该导入该库. 2.代码如下: // // YYViewContr ...

  7. APC 篇——总结与提升

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  8. python【第十一篇】消息队列RabbitMQ、缓存数据库Redis

    大纲 1.RabbitMQ 2.Redis 1.RabbitMQ消息队列 1.1 RabbitMQ简介 AMQP,即Advanced Message Queuing Protocol,高级消息队列协议 ...

  9. 【笔记篇】单调队列优化dp学习笔记&&luogu2569_bzoj1855股票交♂易

    DP颂 DP之神 圣洁美丽 算法光芒照大地 我们怀着 崇高敬意 跪倒在DP神殿里 你的复杂 能让蒟蒻 试图入门却放弃 在你光辉 照耀下面 AC真心不容易 dp大概是最经久不衰 亘古不化的算法了吧. 而 ...

随机推荐

  1. 【LeetCode】344. Reverse String 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 新构建字符串 原地翻转 日期 题目地址:https://lee ...

  2. 【剑指Offer】04. 二维数组中的查找 解题报告(Java & Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 解题方法 日期 题目地址:https://leetcode-cn.com/ ...

  3. Codeforces Round #358 (Div. 2) C. Alyona and the Tree

    C. Alyona and the Tree time limit per test 1 second memory limit per test 256 megabytes input standa ...

  4. SOFA 数据透析

    数据透传: 在 RPC调用中,数据的传递,是通过接口方法参数来传递的,需要接口方定义好一些参数允许传递才可以,在一些场景下,我们希望,能够更通用的传递一些参数,比如一些标识性的信息.业务方可能希望,在 ...

  5. uniapp使用uni.openDocument打开文件时,安卓打开成功,iOS打开失败【原因:打开的文件的文件名是中文】

    解决办法:使用escape进行文件名编码 uni.downloadFile({ url: url, success: function(res) { var filePath = res.tempFi ...

  6. [Atcoder Regular Contest 071 F & JZOJ5450]Neutral

    题目大意 一个无限长的序列\(a\), 需要满足 1.数列中的每一个数在\(1\)到\(n\)之间. 2.对于\(i>=n, j>=n\), \(a_i=a_j\). 3.对于\(i< ...

  7. JS常见框架汇总

    基础框架 Vue.js 官网地址 : http://cn.vuejs.org/ 官方简介 : Vue.js 是一套用于构建用户界面的渐进式框架. 框架类型 : 前端项目级框架 适用平台 : 通用 仓库 ...

  8. WPF 使用 Silk.NET 进行 DirectX 渲染入门

    本文告诉大家如何使用 dotnet 基金会新开源的 Silk.NET 库调用 DirectX 进行渲染的方法.此库是对 DirectX 的底层基础封装,用上了 dotnet 和 C# 的各个新特性,相 ...

  9. Django_通用视图(五)

    参考官网的投票系统,按照综合案例的流程创建应用polls,主要模块代码如下: test1/polls/models.py import datetime from django.db import m ...

  10. 使用用支付宝时,返回的数据中subject为中文时验签失败

    解决方法为: 来自为知笔记(Wiz)