生活随笔
收集整理的這篇文章主要介紹了
Windows APC学习笔记(二)—— 挂入过程执行过程
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
Windows APC學(xué)習(xí)筆記(二)—— 掛入過(guò)程&執(zhí)行過(guò)程
- 基礎(chǔ)知識(shí)
- 掛入過(guò)程
- KeInitializeApc
- ApcStateIndex
- KiInsertQueueApc
- Alertable
- 執(zhí)行過(guò)程
- 內(nèi)核APC執(zhí)行過(guò)程
- 執(zhí)行點(diǎn):線程切換
- SwapContext
- KiServiceExit
- KiDeliverApc
- 總結(jié)
- 用戶APC執(zhí)行過(guò)程
- 執(zhí)行用戶APC時(shí)的堆棧操作
- KiDeliverApc
- KiInitializeUserApc函數(shù)分析:備份CONTEXT
- ntdll.KiUserApcDispatcher
- 總結(jié)
基礎(chǔ)知識(shí)
無(wú)論是正常狀態(tài)還是掛靠狀態(tài),都有兩個(gè)APC隊(duì)列,一個(gè)內(nèi)核隊(duì)列,一個(gè)用戶隊(duì)列每當(dāng)要掛入一個(gè)APC函數(shù)時(shí),不管是內(nèi)核APC還是用戶APC,內(nèi)核都要準(zhǔn)備一個(gè)KAPC的數(shù)據(jù)結(jié)構(gòu),并且將這個(gè)KAPC結(jié)構(gòu)掛到相應(yīng)的APC隊(duì)列中
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
掛入過(guò)程
用戶層調(diào)用:QueueUserAPC(kernel32.dll)→NtQueueApcThread(ntosker.exe)→內(nèi)核層很多內(nèi)核函數(shù)調(diào)用:KeInitializeApc(分配空間 初始化KAPC結(jié)構(gòu)體)→KeInsertQueueApc→KiInsertQueueApc(將KAPC插入指定APC隊(duì)列)
KeInitializeApc
描述:這個(gè)函數(shù)只做了一件事,那就是給當(dāng)前KAPC結(jié)構(gòu)體賦值,KAPC結(jié)構(gòu)體的空間在NtQueueApcThread中分配
VOID
KeInitializeApc
(IN PKAPC Apc
, IN PKTHREAD Thread
, IN KAPC_ENVIRONMENT TargetEnvironment
, IN PKKERNEL_ROUTINE KernelRoutine
, IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL
,IN PKNORMAL_ROUTINE NormalRoutine
, IN KPROCESSOR_MODE Mode
, IN PVOID Context
)
ApcStateIndex
與 KTHREAD(+0x165) 的屬性同名,但含義不一樣:
ApcStateIndex 有四個(gè)值:
0:原始環(huán)境; 1:掛靠環(huán)境 2:當(dāng)前環(huán)境 3:插入APC時(shí)的當(dāng)前環(huán)境
正常情況下:ApcStatePointer[0] 指向 ApcState;ApcStatePointer[1] 指向 SavedApcState
掛靠情況下:ApcStatePointer[0] 指向 SavedApcState;ApcStatePointer[1] 指向 ApcState初始化的時(shí)候,獲取當(dāng)前進(jìn)程的ApcStateIndex,放入KAPC中插入的時(shí)候,APC環(huán)境可能會(huì)發(fā)生變化;當(dāng)真正執(zhí)行插入的時(shí)候,再執(zhí)行一次判斷(獲取ApcStateIndex,放入Kapc中);真正執(zhí)行插入APC的函數(shù)為KiInsertQueueApc
KiInsertQueueApc
步驟:
根據(jù)KAPC結(jié)構(gòu)中的ApcStateIndex找到對(duì)應(yīng)的APC隊(duì)列再根據(jù)KAPC結(jié)構(gòu)中的ApcMode確定是用戶隊(duì)列還是內(nèi)核隊(duì)列將KAPC掛到對(duì)應(yīng)的隊(duì)列中(掛到KAPC的ApcListEntry處)再根據(jù)KAPC結(jié)構(gòu)中的Inserted置1,標(biāo)識(shí)當(dāng)前的KAPC為已插入狀態(tài)修改KAPC_STATE結(jié)構(gòu)中的KernelApcPending/UserApcPending
Alertable
描述:
Alertable=0,UserApcPending = 0:當(dāng)前插入的APC函數(shù)未必有機(jī)會(huì)執(zhí)行Alertable=1 ,UserApcPending = 1:將目標(biāo)線程喚醒(從等待鏈表中摘出來(lái),并掛到調(diào)度鏈表)
kd> dt _KTHREAD
ntdll!_KTHREAD
…
+0x164 Alertable : UChar
…
可通過(guò)以下兩個(gè)函數(shù)手動(dòng)設(shè)置Alertable:
DWORD SleepEx(DWORD dwMilliseconds, // time-out intervalBOOL bAlertable // early completion option
);
DWORD WaitForSingleObjectEx(HANDLE hHandle, // handle to objectDWORD dwMilliseconds, // time-out intervalBOOL bAlertable // alertable option
);
執(zhí)行過(guò)程
描述:
APC函數(shù)的執(zhí)行與插入并不是同一個(gè)線程,具體點(diǎn)說(shuō):
在A線程中向B線程插入一個(gè)APC,插入的動(dòng)作是在A線程中完成的,但什么時(shí)候執(zhí)行則由B線程決定!,所以叫“異步過(guò)程調(diào)用”內(nèi)核APC函數(shù)與用戶APC函數(shù)的執(zhí)行時(shí)間和執(zhí)行方式也有區(qū)別
內(nèi)核APC執(zhí)行過(guò)程
執(zhí)行點(diǎn):線程切換
SwapContext 判斷是否有內(nèi)核APCKiSwapThreadKiDeliverApc 執(zhí)行內(nèi)核APC函數(shù)
SwapContext
返回前:
返回后:
KiServiceExit
KiDeliverApc
執(zhí)行過(guò)程:
判斷第一個(gè)鏈表是否為空判斷KTHREAD.ApcState.KernelApcInProgress是否為1判斷是否禁用內(nèi)核APC(KTHREAD.KernelApcDisable是否為1)將當(dāng)前KAPC結(jié)構(gòu)體從鏈表中摘除執(zhí)行KAPC.KernelRoutine指定的函數(shù) 釋放KAPC結(jié)構(gòu)體占用的空間將KTHREAD.ApcState.KernelApcInProgress設(shè)置為1 標(biāo)識(shí)正在執(zhí)行內(nèi)核APC執(zhí)行真正的內(nèi)核APC函數(shù)(KAPC.NormalRoutine)執(zhí)行完畢 將KernelApcInProgress改為0循環(huán)
反匯編:
總結(jié)
執(zhí)行點(diǎn)一:線程切換的時(shí)候,這就意味著只要插入內(nèi)核APC很快就會(huì)執(zhí)行;
執(zhí)行點(diǎn)二:R0返回到R3時(shí)如果有用戶APC需要處理時(shí),一定會(huì)先處理內(nèi)核APC內(nèi)核APC在內(nèi)核空間執(zhí)行,不需要換棧,一個(gè)循環(huán)全部執(zhí)行完畢
用戶APC執(zhí)行過(guò)程
執(zhí)行用戶APC時(shí)的堆棧操作
處理用戶APC要比內(nèi)核APC復(fù)雜的多,因?yàn)?#xff0c;用戶APC函數(shù)要在用戶空間執(zhí)行的,這里涉及到大量換棧的操作:
當(dāng)線程從用戶層進(jìn)入內(nèi)核層時(shí),要保留原來(lái)的運(yùn)行環(huán)境,比如各種寄存器,棧的位置等等 (_Trap_Frame),然后切換成內(nèi)核的堆棧,如果正常返回,恢復(fù)堆棧環(huán)境即可。但如果有用戶APC要執(zhí)行的話,就意味著線程要提前返回到用戶空間去執(zhí)行,而且返回的位置不是線程進(jìn)入內(nèi)核時(shí)的位置,而是返回到其他的位置,每處理一個(gè)用戶APC都會(huì)涉及到:
內(nèi)核–>用戶空間–>再回到內(nèi)核空間堆棧的操作比較復(fù)雜,如果不了解堆棧的操作細(xì)節(jié)不可能理解用戶APC是如何執(zhí)行的
KiDeliverApc
執(zhí)行過(guò)程:
判斷用戶APC鏈表是否為空判斷第一個(gè)參數(shù)是為1判斷ApcState.UserApcPending是否為1將ApcState.UserApcPending設(shè)置為0鏈表操作 將當(dāng)前APC從用戶隊(duì)列中拆除調(diào)用函數(shù)(KAPC.KernelRoutine)釋放KAPC結(jié)構(gòu)體內(nèi)存空間調(diào)用KiInitializeUserApc函數(shù)
反匯編:
KiInitializeUserApc函數(shù)分析:備份CONTEXT
描述:
線程進(jìn)0環(huán)時(shí),原來(lái)的運(yùn)行環(huán)境(寄存器棧頂?shù)?保存到 _Trap_Frame 結(jié)構(gòu)體中,如果要提前返回3環(huán)去處理用戶APC,就必須要修改 _Trap_Frame 結(jié)構(gòu)體:比如:進(jìn)0環(huán)時(shí)的位置存儲(chǔ)在EIP中,現(xiàn)在要提前返回,而且返回的并不是原來(lái)的位置,那就意味著必須要修改EIP為新的返回位置。還有堆棧ESP,也要修改為處理APC需要的堆棧。那原來(lái)的值怎么辦呢?處理完APC后該如何返回原來(lái)的位置呢?KiInitializeUserApc要做的第一件事就是備份:
將原來(lái) _Trap_Frame 的值備份到一個(gè)新的結(jié)構(gòu)體中(CONTEXT),這個(gè)功能由其子函數(shù)KeContextFromKframes來(lái)完成
反匯編:
一、備份CONTEXT
二、備份局部變量
三、準(zhǔn)備用戶層需要的環(huán)境
段寄存器:SS DS FS GS修改EFLAGS寄存器修改ESP修改EIP
ntdll.KiUserApcDispatcher
描述:
當(dāng)用戶在3環(huán)調(diào)用QueueUserAPC函數(shù)來(lái)插入APC時(shí),不需要提供
NormalRoutine,這個(gè)參數(shù)是在QueueUserAPC內(nèi)部指定的:Kernel.BaseDispatchAPCZwContinue函數(shù)的意義: 返回內(nèi)核,如果還有用戶APC,重復(fù)上面的執(zhí)行過(guò)程如果沒(méi)有需要執(zhí)行的用戶APC,會(huì)將CONTEXT賦值給 Trap_Frame 結(jié)構(gòu)體,就像從來(lái)沒(méi)有修改過(guò)一樣;
ZwContinue后面的代碼不會(huì)執(zhí)行,線程從哪里進(jìn)0環(huán)仍然會(huì)從哪里回去
反匯編:
總結(jié)
內(nèi)核APC在線程切換時(shí)執(zhí)行,不需要換棧,比較簡(jiǎn)單,一個(gè)循環(huán)執(zhí)行完畢用戶APC在系統(tǒng)調(diào)用、中斷或異常返回3環(huán)前會(huì)進(jìn)行判斷,如果有要執(zhí)行的用戶APC,再執(zhí)行用戶APC執(zhí)行前會(huì)先執(zhí)行內(nèi)核APC
總結(jié)
以上是生活随笔為你收集整理的Windows APC学习笔记(二)—— 挂入过程执行过程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。