寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的復雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。
🔒 華麗的分割線 🔒
APC 結構
上一篇我們簡單介紹了APC
的概念。有些人可能對為什么線程不能被“殺掉”、“掛起”和“恢復”還是有些疑問,我們舉個極端的例子:如果不調用API,屏蔽中斷,並保證代碼不出現異常,線程將永久占用CPU,何談控制呢?所以說線程如果想“死”,一定是自己執行代碼把自己殺死,不存在“他殺”這種情況。我們可以畫個簡單的示意圖:
下面我們來看看存儲APC
的結構體:
kd> dt _KAPC
ntdll!_KAPC
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 Spare0 : Uint4B
+0x008 Thread : Ptr32 _KTHREAD
+0x00c ApcListEntry : _LIST_ENTRY
+0x014 KernelRoutine : Ptr32 void
+0x018 RundownRoutine : Ptr32 void
+0x01c NormalRoutine : Ptr32 void
+0x020 NormalContext : Ptr32 Void
+0x024 SystemArgument1 : Ptr32 Void
+0x028 SystemArgument2 : Ptr32 Void
+0x02c ApcStateIndex : Char
+0x02d ApcMode : Char
+0x02e Inserted : UChar
存在於KTHREAD
結構體的0x34
偏移的位置有一個ApcState
,如下所示:
kd> dt _KTHREAD
nt!_KTHREAD
...
+0x034 ApcState : _KAPC_STATE
...
我們看到這個存着一個結構體,如下所示:
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
ApcListHead
第一個成員ApcListHead
是個雙向鏈表的數組,一共有兩個成員,所謂的APC
就是插入到里面的,給個示意圖如下:
ApcListHead
一個成員存儲着用戶APC
,用戶APC
的函數地址位於用戶空間,在用戶空間執行;另一個成員存儲着內核APC
,內核APC
函數地址位於內核空間,在內核空間執行。
Process
線程線程所屬或者所掛靠的進程,這個在逆向線程切換的時候我們就用過。具體細節都在進程線程篇的總結與提升講過,就不再贅述了。
KernelApcInProgress
指示內核APC
是否正在執行。
KernelApcPending
指示是否有正在等待執行的內核APC
。
UserApcPending
指示是否有正在等待執行的用戶APC
。
小結
上面的介紹僅僅是對APC
的初步講解,里面所有的詳細細節將在后面的教程講解。后面會詳細介紹KAPC
這個結構體,並研究APC
是誰插入的、插入到哪里、誰執行APC
和什么時候執行APC
。本篇是對后面學習的鋪墊。
本節練習
本節的答案將會在下一節進行講解,務必把本節練習做完后看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。
俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到后面,不做練習的話容易夾生了,開始還明白,后來就真的一點都不明白了。本節練習不多,請保質保量的完成。
1️⃣ 使用現成提供的API
,自己編寫代碼向某個線程插入一個用戶APC
。
2️⃣ 分析TerminateThread
和SuspendThread
是如何實現的(從3環開始分析)。(要求:只需逆向分析到別的進程是如何控制目標進程行為,其他細節暫時不需分析,本題參考將會在正文給出。)