(71)APC队列,KAPC结构,分析 TerminateThread ,QueueUserApc
一、線程是如何結束的
首先,線程是自己結束的,線程不能被其他線程殺死,考慮一種極端情況,線程屏蔽了時鐘中斷,不發生異常,那么它就能一直執行下去。
TerminateThread 函數結束其他線程的方式是向線程APC隊列添加一個APC結構,線程執行到某個時刻,會檢查自己的APC隊列,判斷要做什么工作,包括結束自己。
其他線程插入APC和本線程執行APC是異步的過程,所以APC的全稱是 APC(asynchronous procedure call)異步過程調用。
二、APC隊列
KTHREAD + 0x34 處是一個 _KAPC_STATE 結構:
kd> dt _KTHREAD ... +0x034 ApcState : _KAPC_STATE在線程切換函數 SwapContext 中,判斷當前線程和新線程是否屬于同一個進程的時候,用到了這個結構:
mov eax, [edi+_ETHREAD.Tcb.ApcState.Process] ;; 通常情況下,ApcState.Process 和 _ETHREAD.ThreadsProcess 是同一個; 但是當A進程調用API訪問B進程的內存時,ApcState.Process 存的就是B進程 cmp eax, [esi+_ETHREAD.Tcb.ApcState.Process]_KAPC_STATE 的結構是這樣的:
kd> dt _KAPC_STATE nt!_KAPC_STATE+0x000 ApcListHead //2個APC隊列 用戶APC和內核APC +0x010 Process //線程所屬或者所掛靠的進程+0x014 KernelApcInProgress //內核APC是否正在執行+0x015 KernelApcPending //是否有正在等待執行的內核APC+0x016 UserApcPending //是否有正在等待執行的用戶APCApcListHead 存儲了兩個APC隊列:
用戶APC:APC函數地址位于用戶空間,在用戶空間執行.
內核APC:APC函數地址位于內核空間,在內核空間執行.
三、KAPC結構
KAPC結構是這樣的:
kd> dt _KAPC nt!_KAPC+0x000 Type //類型 APC類型為0x12+0x002 Size //本結構體的大小 0x30+0x004 Spare0 //未使用 +0x008 Thread //目標線程 +0x00c ApcListEntry //APC隊列掛的位置+0x014 KernelRoutine //指向一個函數(調用ExFreePoolWithTag 釋放APC)+0x018 RundownRoutine//略 +0x01c NormalRoutine //用戶APC總入口 或者 真正的內核apc函數+0x020 NormalContext //內核APC:NULL 用戶APC:真正的APC函數+0x024 SystemArgument1//APC函數的參數 +0x028 SystemArgument2//APC函數的參數+0x02c ApcStateIndex //掛哪個隊列,有四個值:0 1 2 3+0x02d ApcMode //內核APC 用戶APC+0x02e Inserted //表示本apc是否已掛入隊列 掛入前:0 掛入后 1其中 NormalRoutine 用于找到其他線程提供的APC函數,但并不完全等于APC函數地址,后面的課程會介紹。
APC 函數何時被調用?
KiServiceExit函數:這個函數是系統調用、異常或中斷返回用戶空間的必經之路。
KiDeliverApc函數:負責執行APC函數
更多細節,都要在逆向后發現。
四、課后練習
1、分析TerminateThread/SuspendThread/ResumeThread是如何實現的(從3環開始分析).
證明:控制別的線程,就是通過APC實現的。
3環調用了 kernel32 的 TerminateThread ,它又調用了 ntdll 的 ZwTerminateThread ,然后就是系統調用進0環,調用號是0x101,沒什么好說的。0環調用 NtTerminateThread 函數,這個函數主要還是調用 PspTerminateThreadByPointer 做事的,篇幅原因,就不貼了,大家可以看源碼或自己逆匯編,我這里只分析 PspTerminateThreadByPointer 。
PspTerminateThreadByPointer 關鍵代碼
完整代碼在 psdelete.c 這個文件。
可以看到,這個函數調用 KeInitializeApc 構造了一個用于退出線程的 KAPC 結構,然后 KeInsertQueueApc 插入 KAPC 到線程的 APC 鏈表。
我們暫時只需要了解APC的初始化和插入調用了哪兩個函數,后續的博客我會詳細分析 KeInitializeApc 和 KeInsertQueueApc 這兩個函數,屆時也會分析參數的作用。
KeInitializeApc (ExitApc,PsGetKernelThread (Thread),OriginalApcEnvironment, // 父進程PsExitSpecialApc, // 釋放APC內存,退出當前線程PspExitApcRundown, // 釋放APC內存PspExitNormalApc, KernelMode, // 內核模式ULongToPtr (ExitStatus)); // 線程退出碼if (!KeInsertQueueApc (ExitApc, ExitApc, NULL, 2)) {// Note that we'll get here if APC queueing has been// disabled -- on the other hand, in that case, the thread// is exiting anyway.ExFreePool (ExitApc);Status = STATUS_UNSUCCESSFUL; }2、調用 QueueUserApc 向某個線程插入一個用戶APC
// 用戶APC.cpp : 定義控制臺應用程序的入口點。 //#include "stdafx.h" #include <windows.h>DWORD WINAPI MyThread(LPVOID) {int cnt = 0;while (1){SleepEx(300, TRUE);printf("%d\n", cnt++);}return 0; }void __stdcall MyApcFunc(LPVOID) {printf("執行APC函數...\n");printf("APC函數執行完成.\n"); }int _tmain(int argc, _TCHAR* argv[]) {HANDLE hThread = CreateThread(0,0,MyThread,0,0,0);Sleep(1000);if (!QueueUserAPC((PAPCFUNC)MyApcFunc,hThread,NULL)){printf("QueueUserAPC 錯誤:%d\n", GetLastError());}getchar();return 0; }3、分析 NtQueueApcThread (psctx.c)
QueueUserApc 會調用內核函數 NtQueueApcThread,下面分析其源碼。
關注我寫了注釋的部分,關鍵部分是創建 APC 和插入 APC。
NTSYSAPI NTSTATUS NTAPI NtQueueApcThread(IN HANDLE ThreadHandle, // 線程句柄,用來獲取線程結構 ETHREADIN PPS_APC_ROUTINE ApcRoutine, // Apc->NormalRoutine ,是所有用戶APC的總入口 BaseDispatchAPC(3環函數)IN PVOID ApcArgument1, // Apc->NormalContext ,3環APC函數IN PVOID ApcArgument2, // Apc->SystemArgument1 ,3環APC函數的參數IN PVOID ApcArgument3 // Apc->SystemArgument2 ,作用不明,BaseDispatchAPC 里用到了)/*++Routine Description:This function is used to queue a user-mode APC to the specified thread. The APCwill fire when the specified thread does an alertable waitArguments:ThreadHandle - Supplies a handle to a thread object. The callermust have THREAD_SET_CONTEXT access to the thread.ApcRoutine - Supplies the address of the APC routine to execute when theAPC fires.ApcArgument1 - Supplies the first PVOID passed to the APCApcArgument2 - Supplies the second PVOID passed to the APCApcArgument3 - Supplies the third PVOID passed to the APCReturn Value:Returns an NT Status code indicating success or failure of the API--*/{PETHREAD Thread;NTSTATUS st;KPROCESSOR_MODE Mode;PKAPC Apc;PAGED_CODE();Mode = KeGetPreviousMode ();// 獲取 ETHREADst = ObReferenceObjectByHandle (ThreadHandle,THREAD_SET_CONTEXT,PsThreadType,Mode,&Thread,NULL);if (NT_SUCCESS (st)) {st = STATUS_SUCCESS;if (IS_SYSTEM_THREAD (Thread)) {st = STATUS_INVALID_HANDLE;} else {// 申請 APC 內存Apc = ExAllocatePoolWithQuotaTag (NonPagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,sizeof(*Apc),'pasP');if (Apc == NULL) {st = STATUS_NO_MEMORY;} else {// 初始化用戶 APCKeInitializeApc (Apc,&Thread->Tcb,OriginalApcEnvironment, // 插入到所屬進程(創建線程的那個進程)PspQueueApcSpecialApc, // KernelRoutine , 作用是釋放 APC 內存(ExFreePool)NULL, // RundownRoutine 未指定(PKNORMAL_ROUTINE)ApcRoutine, // 用戶APC總入口 BaseDispatchAPC(3環函數)UserMode, // 用戶模式APCApcArgument1); // 3環APC函數// ApcArgument2 是3環APC函數的參數if (!KeInsertQueueApc (Apc, ApcArgument2, ApcArgument3, 0)) {ExFreePool (Apc);st = STATUS_UNSUCCESSFUL;}}}ObDereferenceObject (Thread);}return st; } 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的(71)APC队列,KAPC结构,分析 TerminateThread ,QueueUserApc的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (70)内核重载 xp sp3 x86
- 下一篇: (72)进程挂靠(attach)使用备用