APC机制详解
文章目錄
- APC的本質(zhì)
- APC隊列
- APC結(jié)構(gòu)
- APC相關(guān)函數(shù)
- KiServiceExit
- KiDeliveApc
- 備用APC隊列
- ApcState的含義
- 掛靠環(huán)境下的ApcState的含義
- 其他APC相關(guān)成員
- ApcStatePointer
- ApcStateIndex
- ApcStatePointer與ApcStateIndex組合尋址
- ApcQueueable
- APC掛入過程
- KAPC結(jié)構(gòu)
- 掛入流程
- KeInitializeApc
- ApcStateIndex
- KiInsertQueueApc
- 內(nèi)核APC的執(zhí)行過程
- 執(zhí)行點:線程切換
- 執(zhí)行點:系統(tǒng)調(diào)用 中斷或者異常(_KiServiceExit)
- KiDeliverApc函數(shù)分析
- 總結(jié)
- 用戶APC的執(zhí)行過程
- 執(zhí)行用戶APC時的堆棧操作
- KiDeliverApc函數(shù)分析
- KiInitializeUserApc函數(shù)分析:備份CONTEXT
- KiInitializeUserApc函數(shù)分析:堆棧圖
- KiInitializeUserApc函數(shù)分析:準(zhǔn)備用戶層執(zhí)行環(huán)境
- ntdll.KiUserApcDispatcher函數(shù)分析
- 總結(jié)
APC的本質(zhì)
線程是不能被殺死 掛起和恢復(fù)的,線程在執(zhí)行的時候自己占據(jù)著CPU,別人怎么可能控制他呢?舉個極端的例子,如果不調(diào)用API,屏蔽中斷,并保證代碼不出現(xiàn)異常,線程將永久占據(jù)CPU。所以說線程如果想結(jié)束,一定是自己執(zhí)行代碼把自己殺死,不存在別人把線程結(jié)束的情況。
那如果想改變一個線程的行為該怎么辦?可以給他提供一個函數(shù),讓他自己去調(diào)用,這個函數(shù)就是APC,即異步過程調(diào)用
APC隊列
我們現(xiàn)在需要討論是的,如果我給某一個線程提供一個函數(shù),那么這個函數(shù)掛在哪里?答案是APC隊列,先來看一下當(dāng)前線程的結(jié)構(gòu)體
kd> dt _KTHREAD ntdll!_KTHREAD +0x040 ApcState : _KAPC_STATE線程結(jié)構(gòu)體KTHREAD0x40的位置的成員是一個子結(jié)構(gòu)體ApcState,也就是APC隊列
kd> dt _KAPC_STATE nt!_KAPC_STATE+0x000 ApcListHead //2個APC隊列 用戶APC和內(nèi)核APC +0x010 Process //線程所屬進(jìn)程或者所掛靠的進(jìn)程+0x014 KernelApcInProgress //內(nèi)核APC是否正在執(zhí)行+0x015 KernelApcPending //是否有正在等待執(zhí)行的內(nèi)核APC+0x016 UserApcPending //是否有正在等待執(zhí)行的用戶APC_KAPC_STATE的第一個成員是兩個APC隊列,每一個成員都是一個雙向鏈表,這個雙向鏈表就是APC隊列。
APC一共有兩個,一個是用戶態(tài)APC隊列,一個是內(nèi)核態(tài)的APC隊列,里面存儲的都是APC函數(shù)。
你想讓線程執(zhí)行某些操作的時候,就可以提供一個函數(shù),掛到這個鏈表里,在某一個時刻,當(dāng)前線程會檢查當(dāng)前的函數(shù)列表,當(dāng)里面有函數(shù)的時候,就會去調(diào)用。這樣就相當(dāng)于改變了線程的行為。
現(xiàn)在我們知道了如果想改變線程的行為,需要提供一個函數(shù)掛到線程的APC隊列里,準(zhǔn)確的說是提供一個APC,接下里需要了解APC的結(jié)構(gòu)。
APC結(jié)構(gòu)
kd> dt _KAPC ntdll!_KAPC+0x000 Type : UChar+0x001 SpareByte0 : UChar+0x002 Size : UChar+0x003 SpareByte1 : UChar+0x004 SpareLong0 : 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其中最重要的是+0x01c NormalRoutine的這個成員,通過這個成員可以找到你提供的APC函數(shù)。
現(xiàn)在我們知道了提供APC需要遵循的格式,以及存到線程的位置,但是還有另外的問題,當(dāng)前的線程什么時候會執(zhí)行所提供的APC函數(shù)
如果想要解決這個問題,需要知道一個內(nèi)核函數(shù):KiServiceExit
APC相關(guān)函數(shù)
KiServiceExit
這個函數(shù)是系統(tǒng)調(diào)用 異常或中斷返回用戶空間的必經(jīng)之路
KiDeliveApc
負(fù)責(zé)執(zhí)行APC函數(shù)
備用APC隊列
kd> dt _KTHREAD ntdll!_KTHREAD +0x040 ApcState : _KAPC_STATE +0x170 SavedApcState : _KAPC_STATE在線程結(jié)構(gòu)體0x40的位置是APC隊列,在0x170的位置也有一個APC隊列,這兩個成員的結(jié)構(gòu)是完全一樣的
ApcState的含義
線程隊列中的APC函數(shù)都是與進(jìn)程相關(guān)聯(lián)的,具體點說:A進(jìn)程的T線程中所有的APC函數(shù),要訪問的內(nèi)存地址都是A進(jìn)程的。
但線程是可以掛靠到其他的進(jìn)程:比如A進(jìn)程的線程T,通過修改CR3,就可以訪問B進(jìn)程的地址空間,即所謂的進(jìn)程掛靠。
當(dāng)T線程掛靠B進(jìn)程后,APC隊列中存儲的仍然是原來的APC。具體點說,比如某個APC函數(shù)要讀取地址為0x12345678的數(shù)據(jù),如果此時進(jìn)行讀取,讀到的將是B進(jìn)程的地址空間,這樣邏輯就錯誤了。
為了避免混亂,在T線程掛靠B進(jìn)程時,會將ApcState中的值暫時存儲到SavedApcState中,等回到原進(jìn)程A時,再將APC隊列恢復(fù)
所以,SavedApcState又稱為備用APC隊列
掛靠環(huán)境下的ApcState的含義
在掛靠環(huán)境下,也是可以將線程APC隊列插入APC的,那這種情況下,使用的是哪個APC隊列呢?
A進(jìn)程的T線程掛靠B進(jìn)程,A是T的所屬進(jìn)程,B是T的掛靠進(jìn)程
- ApcState:B進(jìn)程相關(guān)的APC函數(shù)
- SavedApcState:A進(jìn)程相關(guān)的APC函數(shù)
在正常情況下,當(dāng)前進(jìn)程就是所屬進(jìn)程A,如果是掛靠情況下,當(dāng)前進(jìn)程就是掛靠進(jìn)程B
其他APC相關(guān)成員
ApcStatePointer
+0x168 ApcStatePointer : [2] Ptr32 _KAPC_STATE在KTHREAD結(jié)構(gòu)體的0x168的位置的成員是一個指針數(shù)組,有兩個指針,每一個指針都指向一個ApcState
為了操作方便,KTHREAD結(jié)構(gòu)體中定義了一個指針數(shù)組ApcStatePointer,長度為2。
正常情況下:
? ApcStatePointer[0]指向ApcState
? ApcStatePointer[1]指向SavedApcState
掛靠情況下:
? ApcStatePointer[0]指向SavedApcState
? ApcStatePointer[1]指向ApcState
ApcStateIndex
+0x134 ApcStateIndex : UCharApcStateIndex用來標(biāo)識當(dāng)前線程處于什么狀態(tài):0正常狀態(tài) 1掛靠狀態(tài)
ApcStatePointer與ApcStateIndex組合尋址
正常情況下,向ApcState隊列插入APC時:
? ApcStatePointer[0]指向ApcState,此時ApcStateIndex的值為0
? ApcStatePointer[ApcStateIndex]指向ApcState
掛靠情況下,向ApcState隊列中插入APC時:
? ApcStatePointer[1]指向ApcState,此時ApcStateIndex的值為1
? ApcStatePointer[ApcStateIndex]指向ApcState
總結(jié):
無論什么環(huán)境下,ApcStatePointer[ApcStateIndex]指向的都是ApcState,ApcState則總是表示線程當(dāng)前使用的APC狀態(tài)
ApcQueueable
+0x0b8 ApcQueueable : Pos 5, 1 BitApcQueueable用于表示是否可以向線程的APC隊列中插入APC。
當(dāng)線程正在執(zhí)行退出的代碼時,會將這個值設(shè)置為0,如果此時執(zhí)行插入APC的代碼,在插入函數(shù)中會判斷這個值的狀態(tài),如果為0,則插入失敗。
APC掛入過程
無論是正常狀態(tài)還是掛靠狀態(tài),都要有兩個APC隊列,一個內(nèi)核隊列,一個用戶隊列。每當(dāng)要掛入一個APC函數(shù)時,不管是用戶隊列還是內(nèi)核隊列,內(nèi)核都要準(zhǔn)備一個KAPC的數(shù)據(jù)結(jié)構(gòu),并且將這個KAPC結(jié)構(gòu)掛到相應(yīng)的APC隊列中。
KAPC結(jié)構(gòu)
kd> dt _KAPC nt!_KAPC+0x000 Type //類型 APC類型為0x12+0x002 Size //本結(jié)構(gòu)體的大小 0x30+0x004 Spare0 //未使用 +0x008 Thread //目標(biāo)線程 +0x00c ApcListEntry //APC隊列掛的位置+0x014 KernelRoutine //指向一個函數(shù)(調(diào)用ExFreePoolWithTag 釋放APC)+0x018 RundownRoutine//略 +0x01c NormalRoutine //用戶APC總?cè)肟? 或者 真正的內(nèi)核apc函數(shù)+0x020 NormalContext //內(nèi)核APC:NULL 用戶APC:真正的APC函數(shù)+0x024 SystemArgument1//APC函數(shù)的參數(shù) +0x028 SystemArgument2//APC函數(shù)的參數(shù)+0x02c ApcStateIndex //掛哪個隊列,有四個值:0 1 2 3+0x02d ApcMode //內(nèi)核APC 用戶APC+0x02e Inserted //表示本apc是否已掛入隊列 掛入前:0 掛入后 1- Type :類型。在Windows里,任何一種內(nèi)核對象都有一個編號,這個編號用來標(biāo)識你是屬于哪一種類型,APC本身也是一種內(nèi)核對象,它也有一個編號,是0x12
- Size:這個成員指的是當(dāng)前的KAPC的結(jié)構(gòu)體的大小
- Thread:每一個線程都有自己的APC隊列,這個成員指定了APC屬于哪一個線程
- ApcListEntry:APC隊列掛的位置,是一個雙向鏈表,通過這個雙向鏈表可以找到下一個APC
- KernelRoutine:指向一個函數(shù)(調(diào)用ExFreePoolWithTag 釋放APC)。當(dāng)我們的APC執(zhí)行完畢以后,當(dāng)前的KAPC本身的這塊內(nèi)存,會由KernelRoutine指定的函數(shù)來釋放
- NormalRoutine:如果當(dāng)前是內(nèi)核APC,通過這個值找到的就是真正的內(nèi)核APC函數(shù);如果當(dāng)前的APC是用戶APC,那么這個位置指向的是用戶APC總?cè)肟?#xff0c;通過這個總?cè)肟诳梢哉业剿杏脩籼峁┑腁PC函數(shù)
- NormalContext:如果當(dāng)前是內(nèi)核APC,通過這個值為空;如果當(dāng)前的APC是用戶APC,那么這個值指向的是真正的用戶APC函數(shù)
- SystemArgument1 SystemArgument2 APC函數(shù)的參數(shù)
- ApcStateIndex:當(dāng)前的APC要掛到哪個隊列
- ApcMode:當(dāng)前的APC是用戶APC還是內(nèi)核APC
- Inserted:當(dāng)前的APC結(jié)構(gòu)體是否已經(jīng)插入到APC隊列
掛入流程
KeInitializeApc
VOID KeInitializeApc (IN PKAPC Apc,//KAPC指針I(yè)N PKTHREAD Thread,//目標(biāo)線程IN KAPC_ENVIRONMENT TargetEnvironment,//0 1 2 3四種狀態(tài)IN PKKERNEL_ROUTINE KernelRoutine,//銷毀KAPC的函數(shù)地址IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,IN PKNORMAL_ROUTINE NormalRoutine,//用戶APC總?cè)肟诨蛘邇?nèi)核apc函數(shù)IN KPROCESSOR_MODE Mode,//要插入用戶apc隊列還是內(nèi)核apc隊列IN PVOID Context//內(nèi)核APC:NULL 用戶APC:真正的APC函數(shù) )KeInitializeApc函數(shù)的作用就是給當(dāng)前的KAPC結(jié)構(gòu)體賦值
ApcStateIndex
與KTHREAD(+0x134)的屬性同名,但含義不一樣:
ApcStateIndex有四個值:
- 0 原始環(huán)境->插入到當(dāng)前線程的所屬進(jìn)程APC隊列,不管是否掛靠都插入到當(dāng)前線程的所屬進(jìn)程。
- 1 掛靠環(huán)境
- 2 當(dāng)前環(huán)境->插入到當(dāng)前進(jìn)程的APC隊列,如果沒有掛靠,當(dāng)前進(jìn)程則是父進(jìn)程,如果掛靠了,當(dāng)前進(jìn)程就是掛靠進(jìn)程
- 3 插入APC時的當(dāng)前環(huán)境->線程隨時處于切換狀態(tài) 當(dāng)值為3時,在插入APC之前會判斷當(dāng)前線程是否處于掛靠狀態(tài) 再進(jìn)行APC插入
KiInsertQueueApc
內(nèi)核APC的執(zhí)行過程
APC函數(shù)的插入和執(zhí)行并不是同一個線程,具體點說:
在A線程中向B線程插入一個APC,插入的動作是在A線程中完成的,但什么時候執(zhí)行則由B線程決定。所以叫異步過程調(diào)用。
內(nèi)核APC函數(shù)與用戶APC函數(shù)的執(zhí)行時間和執(zhí)行方式也有區(qū)別。我們先來了解內(nèi)核APC的執(zhí)行過程
執(zhí)行點:線程切換
IDA打開ntkrnlpa,找到SwapContext函數(shù)
在這個函數(shù)即將執(zhí)行完成的時候,會判斷當(dāng)前是否有要執(zhí)行的內(nèi)核APC,接著將判斷的結(jié)果存到eax,然后返回
接著找到上一層函數(shù)KiSwapContext函數(shù)繼續(xù)跟進(jìn)
這個函數(shù)也沒有對APC進(jìn)行處理,而是繼續(xù)返回,繼續(xù)跟進(jìn)父函數(shù)
返回到這里,會判斷KiSwapContext的返回值,也就是判斷當(dāng)前是否有要處理的內(nèi)核APC,如果有,則調(diào)用KiDeliverApc進(jìn)行處理。
這個函數(shù)有三個參數(shù),第一個參數(shù)如果是0,就意味著KiDeliverApc在執(zhí)行的時候只會處理內(nèi)核APC,第一個參數(shù)如果是1,KiDeliverApc除了處理內(nèi)核APC以外,還會處理用戶APC
流程總結(jié):
執(zhí)行點:系統(tǒng)調(diào)用 中斷或者異常(_KiServiceExit)
找到_KiServiceExit函數(shù),這里會判斷是否有要執(zhí)行的用戶APC,如果有的話則會調(diào)用KiDeliverApc函數(shù)進(jìn)行處理,此時KiDeliverApc第一個參數(shù)為1,代表執(zhí)行用戶APC和內(nèi)核APC。
當(dāng)要執(zhí)行用戶APC之前,先要執(zhí)行內(nèi)核APC
KiDeliverApc函數(shù)分析
無論是執(zhí)行內(nèi)核APC還是執(zhí)行用戶APC都會調(diào)用KiDeliverApc函數(shù),接下來分析KiDeliverApc函數(shù)主要做了什么事情
首先這里會取出內(nèi)核APC列表,然后執(zhí)行跳轉(zhuǎn)
接著判斷第一個鏈表是否為空(內(nèi)核APC隊列),如果不為空則跳轉(zhuǎn)
跳轉(zhuǎn)以后,首先得到KACP的首地址,然后取出KACP結(jié)構(gòu)體的各個參數(shù),放到局部變量里
在這里,因為我們要處理的是內(nèi)核APC,所以NormalRoutine代表是內(nèi)核APC函數(shù)地址,這里會判斷內(nèi)核APC函數(shù)地址是否為空,不為空的話則進(jìn)行跳轉(zhuǎn)
跳轉(zhuǎn)以后,先判斷是否有正在執(zhí)行內(nèi)核APC,然后判斷是否禁用內(nèi)核APC,接著將APC從內(nèi)核隊列中摘除。
接著先調(diào)用KAPC.KernelRoutine指定的函數(shù) 釋放KAPC結(jié)構(gòu)體占用的空間
然后將ApcState.KernelApcInProgress 設(shè)置為1 標(biāo)識正在執(zhí)行內(nèi)核APC。
接著將三個參數(shù)壓入棧里,開始執(zhí)行真正的內(nèi)核APC函數(shù)
執(zhí)行完畢以后,將ApcState.KernelApcInProgress 置0,接著再次判斷內(nèi)核APC隊列,開始下一輪循環(huán)
內(nèi)核APC執(zhí)行流程總結(jié):
總結(jié)
用戶APC的執(zhí)行過程
當(dāng)產(chǎn)生系統(tǒng)調(diào)用 中斷或者異常,線程在返回用戶空間前都會調(diào)用_KiServiceExit函數(shù),在_KiServiceExit函數(shù)里會判斷是否有要執(zhí)行的用戶APC,如果有則調(diào)用KiDeliverApc函數(shù)進(jìn)行處理
執(zhí)行用戶APC時的堆棧操作
處理用戶APC要比處理內(nèi)核APC復(fù)雜的多,因為用戶APC函數(shù)要在用戶空間執(zhí)行,這里涉及到大量的換棧操作:
當(dāng)線程從用戶層進(jìn)入內(nèi)核層時,要保留原來的運行環(huán)境,比如各種寄存器 棧的位置等等,然后切換成內(nèi)核的堆棧,如果正常返回,恢復(fù)堆棧環(huán)境即可
但如果有用戶APC要執(zhí)行的話,就意味著線程要提前返回到用戶空間去執(zhí)行,而且返回的位置不是線程進(jìn)入內(nèi)核時的位置,而是返回到真正執(zhí)行APC的位置
每處理一個用戶APC就會涉及到:內(nèi)核—>用戶空間—>再回到內(nèi)核空間
執(zhí)行用戶APC最為關(guān)鍵的就是理解堆棧操作的細(xì)節(jié)
KiDeliverApc函數(shù)分析
KiDeliverApc函數(shù)會push三個參數(shù),第一個參數(shù)如果為0,代表只處理內(nèi)核APC,如果為1,代表處理用戶APC和內(nèi)核APC。
也就是說內(nèi)核APC是無論如何都會執(zhí)行的。
取出內(nèi)核APC隊列之后會再次取出用戶APC隊列,并判斷用戶APC隊列是否為空
.text:00426063 cmp [ebp+arg_0], 1接著判斷KiDeliverApc第一個參數(shù)是否為1 如果不是1 說明不處理用戶APC,直接返回
.text:00426069 cmp byte ptr [esi+4Ah], 0 ;+0x4A=UserApcPending 表示是否正在執(zhí)行用戶APC,為0說明正在執(zhí)行的用戶APC,繼續(xù)往下走
.text:0042606F mov byte ptr [esi+4Ah], 0先將UserApcPending置0,表示當(dāng)前正在執(zhí)行用戶APC
.text:00426073 lea edi, [eax-0Ch]-0xC 得到KPCR首地址
.text:00426076 mov ecx, [edi+1Ch] ; +0x1C=NormalRoutine 用戶APC總?cè)肟?.text:00426079 mov ebx, [edi+14h] ; +0x14=KernelRoutine 釋放APC的函數(shù) .text:0042607C mov [ebp+var_4], ecx .text:0042607F mov ecx, [edi+20h] ; +0x20 NormalContext 用戶APC:真正的APC函數(shù) .text:00426082 mov [ebp+var_10], ecx .text:00426085 mov ecx, [edi+24h] ; +0x24 SystemArgument1 .text:00426088 mov [ebp+var_C], ecx .text:0042608B mov ecx, [edi+28h] ; SystemArgument2接著取出KAPC結(jié)構(gòu)體的成員,放到局部變量里保存
.text:00426091 mov ecx, [eax] ; -------------------------- .text:00426093 mov eax, [eax+4] .text:00426096 mov [eax], ecx ; 鏈表操作 將用戶APC從鏈表中移除 .text:00426098 mov [ecx+4], eax ; --------------------------然后將當(dāng)前的用戶APC從鏈表中摘除
.text:004260B7 push eax .text:004260B8 push edi .text:004260B9 call ebx ; 調(diào)用KAPC.KernelRoutine 釋放KAPC結(jié)構(gòu)體內(nèi)存接著調(diào)用調(diào)用KAPC.KernelRoutine指定的函數(shù), 釋放KAPC結(jié)構(gòu)體內(nèi)存
到這里為止,用戶APC和內(nèi)核APC的處理方式就發(fā)生了變化。
如果是內(nèi)核APC這里會直接調(diào)用APC入口函數(shù),執(zhí)行內(nèi)核APC,但是用戶APC執(zhí)行的方式不一樣。當(dāng)前的堆棧處于0環(huán),而用戶APC需要在三環(huán)執(zhí)行。
.text:004260CA push [ebp+var_8] .text:004260CD push [ebp+var_C] .text:004260D0 push [ebp+var_10] .text:004260D3 push [ebp+var_4] .text:004260D6 push [ebp+arg_8] .text:004260D9 push [ebp+arg_4] .text:004260DC call _KiInitializeUserApc接著這里調(diào)用了KiInitializeUserApc函數(shù),接下來就要研究一下這個函數(shù)是如何實現(xiàn)的
用戶APC執(zhí)行流程總結(jié):
KiInitializeUserApc函數(shù)分析:備份CONTEXT
線程進(jìn)0環(huán)時,原來的運行環(huán)境(寄存器棧頂?shù)?保存到_Trap_Frame結(jié)構(gòu)體中,如果要提前返回3環(huán)去處理用戶APC,就必須修改_Trap_Frame結(jié)構(gòu)體,因為此時Trap_Frame中存儲的EIP是從三環(huán)進(jìn)零環(huán)時保存的EIP,而不是用戶APC函數(shù)的地址
比如:進(jìn)0環(huán)時的位置存儲在EIP中,現(xiàn)在要提前返回,而且返回的并不是原來的位置,那就意味著必須要修改EIP為新的返回位置,還有堆棧ESP也要修改為處理APC需要的堆棧。那原來的值怎么辦?處理完APC后該如何返回原來的位置呢?
KiInitializeUserApc要做的第一件事就是備份:
將原來_Trap_Frame的值備份到一個新的結(jié)構(gòu)體中(CONTEXT),這個功能由其子函數(shù)KeContextFromKframes來完成
找到KiInitializeUserApc函數(shù),首先調(diào)用了KeContextFromKframes,將Trap_Frame備份到Context
第一個參數(shù)ebx是Trap_Frame結(jié)構(gòu)體首地址,第三個參數(shù)ecx是CONTEXT結(jié)構(gòu)體首地址
那么問題在于CONTEXT結(jié)構(gòu)體存到哪?肯定不能存到當(dāng)前函數(shù)的局部變量里。Windows想了一個辦法,把這個結(jié)構(gòu)體和APC需要的參數(shù),直接存到三環(huán)的堆棧里
KiInitializeUserApc函數(shù)分析:堆棧圖
.text:00429EFC mov esi, [ebp+var_224] ; 2E8-224=C4 剛好是CONTEXT結(jié)構(gòu)體ESP的偏移 .text:00429F02 and esi, 0FFFFFFFCh ; 進(jìn)行4字節(jié)對齊 .text:00429F05 sub esi, eax ; 在0環(huán)直接修改3環(huán)的棧 將用戶3環(huán)的棧減0x2DC個字節(jié)首先esi是CONTEXT結(jié)構(gòu)體里ESP的偏移,也就是三環(huán)的堆棧,然后進(jìn)行4字節(jié)對齊。
接著將用戶3環(huán)的棧減0x2DC個字節(jié),此時三環(huán)的堆棧被拉伸,為什么是2DC個字節(jié)呢?
因為CONTEXT結(jié)構(gòu)體的大小加上用戶APC所需要的4個參數(shù)正好是2DC個字節(jié),如下圖:
.text:00429F16 lea edi, [esi+10h]此時的esi指向的是-2DC的位置,也就是上圖的NormalRoutine,+10降低堆棧,將指針指向SystemArgument2
.text:00429F19 mov ecx, 0B3h .text:00429F1E lea esi, [ebp+var_2E8] .text:00429F24 rep movsd這幾行代碼將CONTEXT復(fù)制到了三環(huán)的堆棧
.text:00429FAC push 4 .text:00429FAE pop ecx .text:00429FAF add eax, ecx .text:00429FB1 mov [ebp+var_2EC], eax .text:00429FB7 mov edx, [ebp+arg_C] .text:00429FBA mov [eax], edx .text:00429FBC add eax, ecx .text:00429FBE mov [ebp+var_2EC], eax .text:00429FC4 mov edx, [ebp+arg_10] .text:00429FC7 mov [eax], edx .text:00429FC9 add eax, ecx .text:00429FCB mov [ebp+var_2EC], eax .text:00429FD1 mov edx, [ebp+arg_14] .text:00429FD4 mov [eax], edx .text:00429FD6 add eax, ecx .text:00429FD8 mov [ebp+var_2EC], eax ; 修正3環(huán)堆棧棧頂接著這幾行代碼就是將APC函數(shù)執(zhí)行時需要的4個值壓入到3環(huán)的堆棧
KiInitializeUserApc函數(shù)分析:準(zhǔn)備用戶層執(zhí)行環(huán)境
當(dāng)KiInitializeUserApc將CONTEXT和執(zhí)行用戶APC所需要的4個值備份到3環(huán)的堆棧時,就開始準(zhǔn)備用戶層的執(zhí)行環(huán)境了
.text:00429F2D push 23h .text:00429F2F pop eax ; eax=0x23 .text:00429F30 mov [ebx+78h], eax ; 修改Trap_Frame中的SS .text:00429F33 mov [ebx+38h], eax ; 修改Trap_Frame中的DS .text:00429F36 mov [ebx+34h], eax ; 修改Trap_Frame中的ES .text:00429F39 mov dword ptr [ebx+50h], 3Bh ; 修改Trap_Frame中的FS .text:00429F40 and dword ptr [ebx+30h], 0 ; 修改Trap_Frame中的GS首先修改段寄存器 SS DS FS GS
.text:00429F78 mov [ebx+70h], eax ; 修改Trap_Frame中的EFLAGS接著修改EFLAGS寄存器
.text:00429F97 mov [ebx+74h], eax ; 修改Trap_Frame中的ESP .text:00429F9A mov ecx, _KeUserApcDispatcher .text:00429FA0 mov [ebx+68h], ecx ; 修改Trap_Frame中的EIP然后修改ESP和EIP。這個EIP就是執(zhí)行用戶APC時返回到3環(huán)的位置。
這個位置是固定的,是一個全局變量:KeUserApcDispatcher。這個值在系統(tǒng)啟動的時候已經(jīng)賦值好了,是3環(huán)的一個函數(shù):ntdll.KiUserApcDispatcher
然后回到3環(huán),由KiUserApcDispatcher執(zhí)行用戶APC
總結(jié):
ntdll.KiUserApcDispatcher函數(shù)分析
找到KiUserApcDispatcher函數(shù),結(jié)合上面的堆棧圖我們可以得知,esp+0x10的位置就是CONTEXT指針
此時的ESP指向的是NormalRoutine,pop eax將NormalRoutine賦值給了eax,然后call eax開始處理用戶APC的總?cè)肟?/p>
處理完用戶的APC函數(shù)之后,會調(diào)用ZwContinue,這個函數(shù)的意義在于:
總結(jié)
總結(jié)
- 上一篇: 进程线程007 进程挂靠与跨进程读写内存
- 下一篇: CPU和软件模拟异常的执行流程