翻開
翻開小Win的菜單握巢,APC赫然在目...
做工講究晕鹊,味道不錯,是小Win的熱門菜暴浦,我們點一來嘗嘗溅话!
吃了可以做很多事情...
- APC注入
- APC注入
- APC注入
- ...
細節(jié)來自于
ReactOS
源碼分析。
如果對這個發(fā)神經(jīng)的文風(fēng)有任何不適肉渴,請諒解公荧,因為我確實神經(jīng)了
來一份APC
ring3這么做的
點APC的正確姿勢是使用QueueUserApc
,不走尋常路的也可以使用NtQueueApcThread
DWORD WINAPI QueueUserApc(PARCFUNC pfnApc, HANDLE hThread, ULONG_PTR dwData);
{
NtQueueApcThread(hThread, IntCallUserApc, pfnApc, dwData, NULL);
}
NTSTATUS NTAPI NtQueueApcThread(IN HANDLE ThreadHandle,
IN PKNORMAL_ROUTINUE ApcRoutine,
IN PVOID NormalContext, //pfnApc
IN PVOID SystemArgument1, //dwData
IN PVOID SystemArgument2
);
也就是QueueUserApc內(nèi)部是NtQueueApcThread做的同规,兩者區(qū)別不大,當然,使用后者可以字節(jié)加點調(diào)料(不使用IntCallUserApc券勺、換成自己的函數(shù)绪钥,函數(shù)參數(shù)也可以有三個了,而PARCFUNC只有一個參數(shù))关炼。
小Win默認是通過統(tǒng)一的接口IntCallUserApc來調(diào)用的顧客指定的Apc函數(shù)程腹。
static void CALLBACK
IntCallUserApc(PVOID Function, PVOID dwData, PVOID Arg3)
{
((PAPCFUNC)Function)(dwData);
}
ring0這么做的
NtQueueApcThread經(jīng)過系統(tǒng)調(diào)用進入到ring0,一般人是看不到了...儒拂,我也是一般人來著寸潦,下面努力變成二班的...。
1. 創(chuàng)建APC對象
進了NtQueueApcThread社痛,先通過KeInitializeApc初始化一個Apc對象
/* Initialize the APC */
KeInitializeApc(Apc,
&Thread->Tcb, //KTHREAD
OriginalApcEnvironment,
PspQueueApcSpecialApc,
NULL,
ApcRoutine,
UserMode,
NormalContext);
APC對象結(jié)構(gòu)定義如下:
typedef struct _KAPC {
UCHAR Type; //類型ApcObject
UCHAR SpareByte0;
UCHAR Size; //APC結(jié)構(gòu)體大小
UCHAR SpareByte1;
ULONG SpareLong0;
struct _KTHREAD *Thread; //當前線程的KTHREAD
LIST_ENTRY ApcListEntry; //當前線程的APC鏈表
PKKERNEL_ROUTINE KernelRoutine; //
PKRUNDOWN_ROUTINE RundownRoutine; //
PKNORMAL_ROUTINE NormalRoutine; //
PVOID NormalContext; //用戶定義的Apc函數(shù)
PVOID SystemArgument1; //用戶Apc函數(shù)的參數(shù)
PVOID SystemArgument2;//
CCHAR ApcStateIndex; //Apc狀態(tài)
KPROCESSOR_MODE ApcMode; //Apc所處的Mode见转,UserMode/KernelMode
BOOLEAN Inserted; //是否已經(jīng)被插入隊列
} KAPC, *PKAPC, *RESTRICTED_POINTER PRKAPC;
根據(jù)KeInitializeApc傳入?yún)?shù),Apc被賦值如下:
Apc->KernelRoutine = PspQueueApcSpecialApc;
Apc->RundownRoutine = NULL;
Apc->NormalRoutine = ApcRoutine;//如果使用QueueUserApc蒜哀,其實就是IntCallUserApc
Apc->NormalContext = NormalContext;//pfnApc;//用戶指定的Apc函數(shù)
Apc->Type = ApcObject;
//如果參數(shù)指定的是CurrentApcEnvironment斩箫,直接賦值Thread->ApcStateIndex
Apc->ApcStateIndex = Thread->ApcStateIndex;
//不是則
Apc->ApcStateIndex = OriginalApcEnvironment;//
//如果參數(shù)ApcRoutine不是NULL
Apc->ApcMode = Mode;
Apc->NormalContext = Context;
//是NULL
Apc->ApcMode = KernelMode;
Apc->NormalContext = NULL;
Apc->Inserted = False;
其中關(guān)于ApcStateIndex有4中值,如下:
// APC Environment Types
//
typedef enum _KAPC_ENVIRONMENT
{
OriginalApcEnvironment,//0
AttachedApcEnvironment,//1
CurrentApcEnvironment,//2
InsertApcEnvironment
} KAPC_ENVIRONMENT;
Apc->KernelRoutine總是有值的撵儿,被賦值為PspQueueApcSpecialApc乘客,用于Apc結(jié)束時候釋放Apc對象內(nèi)存
VOID
NTAPI
PspQueueApcSpecialApc(IN PKAPC Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2)
{
/* Free the APC and do nothing else */
ExFreePool(Apc);
}
2. 插入APC隊列
通過KeInsertQueueApc
插入隊列,在隊列中等待被上菜...
KeInsertQueueApc(Apc,
SystemArgument1,
SystemArgument2,
IO_NO_INCREMENT))
- 確認Apc未被插入淀歇,Thread->ApcQueueable為真
- Apc->Inserted = True
- 然后通過
KiInsertQueueApc
插入隊列易核,可能通過軟中斷或者喚醒線程得到執(zhí)行Apc的機會
VOID
FASTCALL
KiInsertQueueApc(IN PKAPC Apc,
IN KPRIORITY PriorityBoost)
{
if (Apc->ApcStateIndex == InsertApcEnvironment)
{
Apc->ApcStateIndex = Thread->ApcStateIndex;
}
//PKAPC_STATE ApcStatePointer[2];//說明ApcStateIndex只能是
//OriginalApcEnvironment,//0
//AttachedApcEnvironment,//1
//從Thread的ApcStatePointer取出對應(yīng)的ApcState
ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];
ApcMode = Apc->ApcMode;
ASSERT(Apc->Inserted == TRUE);
/* 插入隊列的三種方式:
* 1) Kernel APC with Normal Routine or User APC = Put it at the end of the List
* 2) User APC which is PsExitSpecialApc = Put it at the front of the List
* 3) Kernel APC without Normal Routine = Put it at the end of the No-Normal Routine Kernel APC list
*/
//PsExitSpecialApc
if (Thread->ApcStateIndex == Apc->ApcStateIndex)
{
if(當前線程 ) {
if(KernelMode) {
Thread->ApcState.KernelApcPending = TRUE;
if (!Thread->SpecialApcDisable)
{
//中斷線程當前執(zhí)行六?浪默?
/* They're not, so request the interrupt */
HalRequestSoftwareInterrupt(APC_LEVEL);
}
}
}
else {
if(KernelMode) {
Thread->ApcState.KernelApcPending = TRUE;
if (Thread->State == Running) HalRequestSoftwareInterrupt(APC_LEVEL);
else if(一堆條件){
KiUnwaitThread(Thread, Status, PriorityBoost);//喚醒線程
}
} else {
if ((Thread->State == Waiting) &&
(Thread->WaitMode == UserMode) &&
((Thread->Alertable) || //
(Thread->ApcState.UserApcPending)))
{
/* Set user-mode APC pending */
Thread->ApcState.UserApcPending = TRUE;
Status = STATUS_USER_APC;
KiUnwaitThread(Thread, Status, PriorityBoost);//喚醒線程
}
}
}
}
}
先不管Apc是怎么得到執(zhí)行的牡直,來看看KAPC_STATE
typedef struct _KAPC_STATE
{
LIST_ENTRY ApcListHead[2];//UserMode/KernelMode的兩個鏈表
struct _KPROCESS *Process;
BOOLEAN KernelApcInProgress;
BOOLEAN KernelApcPending; //等待執(zhí)行
BOOLEAN UserApcPending; //等待執(zhí)行
} KAPC_STATE, *PKAPC_STATE, *RESTRICTED_POINTER PRKAPC_STATE;
其中ApcListHead保存了線程的兩個Apc鏈表,分別對應(yīng)UserMode和KernelMode浴鸿。
Thread->ApcState表示當前需要執(zhí)行的ApcState井氢,可能是掛靠進程的
Thread->SavedApcState表示掛靠后保存的當前線程的ApcState,
KTHREAD的ApcStatePointer[2]字段保存了兩個ApcState的指針
具體看下面的代碼
KeAttachProcess->
VOID
NTAPI
KiAttachProcess(IN PKTHREAD Thread,
IN PKPROCESS Process,
IN PKLOCK_QUEUE_HANDLE ApcLock,
IN PRKAPC_STATE SavedApcState //&Thread->SavedApcThread
)
{
/* Swap the APC Environment */
KiMoveApcState(&Thread->ApcState, SavedApcState); //把當前ApcState保存到SavedApcState
/* Reinitialize Apc State */
InitializeListHead(&Thread->ApcState.ApcListHead[KernelMode]);
InitializeListHead(&Thread->ApcState.ApcListHead[UserMode]);
Thread->ApcState.Process = Process;
Thread->ApcState.KernelApcInProgress = FALSE;
Thread->ApcState.KernelApcPending = FALSE;
Thread->ApcState.UserApcPending = FALSE;
/* Update Environment Pointers if needed*/
if (SavedApcState == &Thread->SavedApcState)
{
Thread->ApcStatePointer[OriginalApcEnvironment] = &Thread->
SavedApcState;//
Thread->ApcStatePointer[AttachedApcEnvironment] = &Thread->ApcState;
Thread->ApcStateIndex = AttachedApcEnvironment; //index變成了AttachedApcEnvironment
}
來一個結(jié)構(gòu)圖
上菜吃飯
Apc已經(jīng)點了岳链,什么時候才能端上來呢花竞?我們接著看...
Apc投遞
線程wait、線程切換到應(yīng)用層掸哑、線程被掛起等约急,一旦線程有空隙了,windows就會把apc隊列順便執(zhí)行一遍
搜索NormalRoutine
和KernelRoutine
字段苗分,找到KiDeliverApc
厌蔽,這個函數(shù)是具體分發(fā)Apc的函數(shù)
VOID
NTAPI
KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame)
* @remarks First, Special APCs are delivered, followed by Kernel-Mode APCs and
* User-Mode APCs. Note that the TrapFrame is only valid if the
* delivery mode is User-Mode.
* Upon entry, this routine executes at APC_LEVEL.
那在哪里調(diào)用的KiDeliverApc的呢,找到多處
//hal\halx86\generic\irq.S
.globl _HalpApcInterrupt2ndEntry
.func HalpApcInterrupt2ndEntry]
//hal\halx86\generic\irql.c
VOID HalpLowerIrql(KIRQL NewIrql)摔癣;
//暫時忽略上面兩個了
//ke\i386\trap.s
.func KiServiceExit
_KiServiceExit:
/* Disable interrupts */
cli
/* Check for, and deliver, User-Mode APCs if needed */
CHECK_FOR_APC_DELIVER 1 //
/* Exit and cleanup */
TRAP_EPILOG FromSystemCall, DoRestorePreviousMode, DoNotRestoreSegments, DoNotRestoreVolatiles, DoRestoreEverything
.endfunc
根據(jù)《windows內(nèi)核情景分析》介紹, 執(zhí)行用戶APC的時機在從內(nèi)核返回用戶空間的途中(可能是系統(tǒng)調(diào)用奴饮、中斷纬向、異常處理之后需要返回用戶空間)
也就是肯定會經(jīng)過_KiServiceExit
,那就跟著來看看吧戴卜。
-
CHECK_FOR_APC_DELIVER
宏 檢查是不是需要投遞Apc逾条,具體檢查trapframe是不是指向返回用戶模式的,是則繼續(xù)檢查用戶模式Apc是否需要投遞投剥。
參數(shù):ebp = PKTRAP_FRAME
师脂,PreserveEax
- trap_frame.Eflags == EFLAGS_V86_MASK,運行在V86模式江锨,不檢查是否是用戶模式的trap_frame
- trap_frame.Segcs != 1(KernelMode)吃警,表示是用戶模式
- kthread = PCR[KPCR_CURRENT_THREAD],kthread.alerted = 0啄育,置為不可喚醒
- kthread->ApcState.UserApcPending 是FALSE酌心,啥也不做,TRUE才進行投遞
- 如果PreserveEax=1灸撰,保存eax谒府,保存一些IRQL提升會清除的信息到trap_frame,fs浮毯,ds完疫,es,gs
- 提示irql到APC_LEVEL
- 調(diào)用KiDeliverApc(UserMode, 0, trap_frame);
- 恢復(fù)irql
- 如果PreserveEax=1债蓝,恢復(fù)eax
-
TRAP_EPILOG
是自陷處理壳鹤,參數(shù):ebp = PKTRAP_FRAME
// This macro creates an epilogue for leaving any system trap.
// It is used for exiting system calls, exceptions, interrupts and generic
// traps.
- 通過TrapFrame恢復(fù)一堆寄存器、堆棧信息饰迹,然后sysexit回到用戶態(tài)空間
繼續(xù)看一下調(diào)用KiDeliverApc
內(nèi)部究竟是怎么處理的
KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame) //系統(tǒng)空間堆棧的“自陷框架”
{
//1. 保存原來的trap_frame
OldTrapFrame = Thread->TrapFrame;
Thread->TrapFrame = TrapFrame;
/* Clear Kernel APC Pending */
Thread->ApcState.KernelApcPending = FALSE;
/* Check if Special APCs are disabled */
if (Thread->SpecialApcDisable) goto Quickie;
//2. 先投遞內(nèi)核Apc芳誓,循環(huán)投遞隊列中所有的內(nèi)核apc,不涉及切換到用戶空間
while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
{
//Thread->ApcQueueLock加鎖訪問
//取出一個Apc
ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
NormalRoutine = Apc->NormalRoutine;
KernelRoutine = Apc->KernelRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;
//特殊Apc啊鸭,特指內(nèi)核Apc锹淌,但是Apc的NormalRoutine是空的
if (!NormalRoutine) {
//將Apc出隊列,然通過KernelRoutine調(diào)用內(nèi)核Apc響應(yīng)函數(shù)
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
} else {
//普通的內(nèi)核Apc
if ((Thread->ApcState.KernelApcInProgress) ||
(Thread->KernelApcDisable))
{ //退出赠制,必須安全才會投遞
}
////將Apc出隊列赂摆,然通過KernelRoutine調(diào)用內(nèi)核Apc響應(yīng)函數(shù)
KernelRoutine(Apc,
&NormalRoutine, //內(nèi)部可能修改NormalRoutine
&NormalContext,
&SystemArgument1,
&SystemArgument2);
//如果NormalRoutine依然不為空,在調(diào)用NormalRoutine
if (NormalRoutine)
{
/* At Passive Level, an APC can be prempted by a Special APC */
Thread->ApcState.KernelApcInProgress = TRUE;
KeLowerIrql(PASSIVE_LEVEL); //將到PASSIVE_LEVEL執(zhí)行
/* Call and Raise IRQ back to APC_LEVEL */
NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);
KeRaiseIrql(APC_LEVEL, &ApcLock.OldIrql);
}
Thread->ApcState.KernelApcInProgress = FALSE;
//繼續(xù)循環(huán)
}
}
//3. 投遞完內(nèi)核apc钟些,如果KiDeliverApc目標是用戶apc烟号,那么繼續(xù)投遞用戶apc
//每次值投遞一個User mode Apc
if ((DeliveryMode == UserMode) &&
!(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) &&
(Thread->ApcState.UserApcPending)) //TRUE
{
Thread->ApcState.UserApcPending = FALSE;
//取出第一個Apc
//先調(diào)用他的KernelRoutine
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
/* Check if there's no normal routine */
if (!NormalRoutine)
{
/* Check if more User APCs are Pending */
KeTestAlertThread(UserMode);
}
else
{
/* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */
//不是直接調(diào)用NormalRoutine,因為他是用戶太的函數(shù)政恍,需要切換到用戶空間才能執(zhí)行
KiInitializeUserApc(ExceptionFrame,
TrapFrame,
NormalRoutine,
NormalContext,
SystemArgument1,
SystemArgument2);
}
}
根據(jù)注釋應(yīng)該很清楚deliver的邏輯了汪拥,還是在看張圖
CHECK_FOR_APC_DELIVER
用戶態(tài)Apc的delvier有個重點,Thread->ApcState.UserApcPending必須是TRUE篙耗,那什么時候才會是TRUE迫筑,我蠻來看看
- 在KiInsertQueueApc宪赶,如果線程等待,且Alertable是TRUE
else if ((Thread->State == Waiting) &&
(Thread->WaitMode == UserMode) &&
((Thread->Alertable) || //
(Thread->ApcState.UserApcPending)))
{
/* Set user-mode APC pending */
Thread->ApcState.UserApcPending = TRUE;
Status = STATUS_USER_APC;
goto Unwait;
}
- KiCheckAlertability中(wrk中是TestForAlertPending)
FORCEINLINE
NTSTATUS
KiCheckAlertability(IN PKTHREAD Thread,
IN BOOLEAN Alertable,
IN KPROCESSOR_MODE WaitMode)
{
/* Check if the wait is alertable */
if (Alertable)
{
/* It is, first check if the thread is alerted in this mode */
if (Thread->Alerted[WaitMode])
{
/* It is, so bail out of the wait */
Thread->Alerted[WaitMode] = FALSE;
return STATUS_ALERTED;
}
else if ((WaitMode != KernelMode) &&
(!IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])))
{
/* It's isn't, but this is a user wait with queued user APCs */
Thread->ApcState.UserApcPending = TRUE;
return STATUS_USER_APC;
兩種情況都需要Alertable = TRUE铣焊,這個字段表示線程是喚醒的逊朽,也就是說只有可喚醒的線程罕伯,才能拿投遞他的用態(tài)APC曲伊,否則不會
SleepEx, WaitForSingleObject,WaitForMultipleObjects都可以設(shè)置線程為Alertable
接著繼續(xù)看看KiInitializeUserApc
是怎么切換到用戶空間執(zhí)行的用戶態(tài)函數(shù)
VOID
NTAPI
KiInitializeUserApc(IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN PKNORMAL_ROUTINE NormalRoutine,
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
//V86模式下追他,不投遞
/* Save the full context */
Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
KeTrapFrameToContext(TrapFrame, ExceptionFrame, &Context);
//檢查不是KernleMode
ASSERT((TrapFrame->SegCs & MODE_MASK) != KernelMode);
...
/* Get the aligned size */
AlignedEsp = Context.Esp & ~3;//來自于TrapFrame.HardwareEsp或TempEsp
//Context和4個參數(shù)的長度
ContextLength = CONTEXT_ALIGNED_SIZE + (4 * sizeof(ULONG_PTR));
//將原始堆棧擴展ContextLength坟募,用來保存Context和參數(shù)
Stack = ((AlignedEsp - 8) & ~3) - ContextLength;
/* Probe the stack */
ProbeForWrite((PVOID)Stack, AlignedEsp - Stack, 1);
ASSERT(!(Stack & 3));
/* Copy data into it */
//(4 * sizeof(ULONG_PTR)))是后面4個參數(shù)的位置,然后接著拷貝Context邑狸,將老的TrapFrame內(nèi)容拷貝到用戶太堆棧中
RtlCopyMemory((PVOID)(Stack + (4 * sizeof(ULONG_PTR))),
&Context,
sizeof(CONTEXT));
/* Run at APC dispatcher */
TrapFrame->Eip = (ULONG)KeUserApcDispatcher; //KeUserApcDispatcher保存的其實就是KiUserApcDispatcher懈糯,是用戶空間函數(shù)
TrapFrame->HardwareEsp = Stack;//棧頂
/* Setup Ring 3 state */
TrapFrame->SegCs = Ke386SanitizeSeg(KGDT_R3_CODE, UserMode);
TrapFrame->HardwareSegSs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
TrapFrame->SegDs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
TrapFrame->SegEs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
TrapFrame->SegFs = Ke386SanitizeSeg(KGDT_R3_TEB, UserMode);
TrapFrame->SegGs = 0;
TrapFrame->ErrCode = 0;
/* Sanitize EFLAGS */
TrapFrame->EFlags = Ke386SanitizeFlags(Context.EFlags, UserMode);
/* Check if thread has IOPL and force it enabled if so */
if (KeGetCurrentThread()->Iopl) TrapFrame->EFlags |= 0x3000;
/* Setup the stack */
*(PULONG_PTR)(Stack + 0 * sizeof(ULONG_PTR)) = (ULONG_PTR)NormalRoutine;
*(PULONG_PTR)(Stack + 1 * sizeof(ULONG_PTR)) = (ULONG_PTR)NormalContext;
*(PULONG_PTR)(Stack + 2 * sizeof(ULONG_PTR)) = (ULONG_PTR)SystemArgument1;
*(PULONG_PTR)(Stack + 3 * sizeof(ULONG_PTR)) = (ULONG_PTR)SystemArgument2;
...
}
執(zhí)行流程根據(jù)注釋應(yīng)該很清楚了,這里要解釋一下TrapFrame单雾。
CPU進入嚙合之后赚哗,內(nèi)核堆棧就會有個TrapFrame,保存的是用戶空間的線程(因進入內(nèi)核原因不同硅堆,可能是自陷屿储、中斷、異辰ヌ樱框架够掠,都是一樣的結(jié)構(gòu))。CPU返回用戶空間時會使用這個TrapFrame茄菊,才能正確返回原理啊的斷點疯潭,并回復(fù)寄存器的狀態(tài)
這里為了讓Apc返回到用戶空間執(zhí)行,就會修改這個TrapFrame面殖,原來的TrapFrame就需要保存竖哩,這里保存在了用戶空間堆棧中(CONTEXT)
執(zhí)行完Apc函數(shù)之后,執(zhí)行一個NtContinue脊僚,將這個CONTEXT作為參數(shù)相叁,這樣保存的TrapFrame就會還原到原來的狀態(tài),然后CPU又能正吵蕴簦回之前的用戶空間了钝荡。
KiDeliverApc完了之后,回到_KiServiceExit舶衬,會使用被修改過的TrapFrame回到用戶空間埠通,執(zhí)行指定的KiUserApcDispatcher
(ntdll提供)
//更具這個執(zhí)行KiUserApcDispatcher
TrapFrame->Eip = (ULONG)KeUserApcDispatcher; //其實就是KiUserApcDispatcher,是用戶空間函數(shù)
TrapFrame->HardwareEsp = Stack;//棧頂
.func KiUserApcDispatcher@16
.globl _KiUserApcDispatcher@16
_KiUserApcDispatcher@16:
/* Setup SEH stack */
lea eax, [esp+CONTEXT_ALIGNED_SIZE+16];原始堆棧的位置逛犹,SEH
mov ecx, fs:[TEB_EXCEPTION_LIST]
mov edx, offset _KiUserApcExceptionHandler
mov [eax], ecx
mov [eax+4], edx
/* Enable SEH */
mov fs:[TEB_EXCEPTION_LIST], eax
/* Put the Context in EDI */
pop eax;彈出第一個參數(shù)
lea edi, [esp+12];context的位置
/* Call the APC Routine */
call eax //調(diào)用IntCallUserApc
/* Restore exception list */
mov ecx, [edi+CONTEXT_ALIGNED_SIZE]
mov fs:[TEB_EXCEPTION_LIST], ecx
/* Switch back to the context */
push 1
push edi;Context
call _ZwContinue@8 //正常是不會返回的
/* Save callback return value */
mov esi, eax
/* Raise status */
StatusRaiseApc:
push esi
call _RtlRaiseStatus@4 //如果ZwContinue失敗了端辱,這里處理
jmp StatusRaiseApc
ret 16
.endfunc
KiUserApcDispatcher
其實挺簡單的梁剔,通過esp彈出APc函數(shù),然后調(diào)用舞蔽,就進入了IntCallUserApc荣病,
恢復(fù)TrapFrame
執(zhí)行完成后,調(diào)用_ZwContinue(Context, 1)渗柿,回到內(nèi)核回復(fù)之前修改TrapFrame个盆,也會重新檢查是否有Apc需要投遞,有則繼續(xù)投遞朵栖,
重復(fù)上面的步驟颊亮,直到?jīng)]有了則可以回到之前被中斷的用戶態(tài)的斷點處。
.func NtContinue@8
_NtContinue@8:
/* NOTE: We -must- be called by Zw* to have the right frame! */
/* Push the stack frame */
push ebp ; 指向本次調(diào)用的自陷框架陨溅,記為T1
/* Get the current thread and restore its trap frame */
mov ebx, PCR[KPCR_CURRENT_THREAD]
mov edx, [ebp+KTRAP_FRAME_EDX]
mov [ebx+KTHREAD_TRAP_FRAME], edx;thread->TrapFrame = edx
/* Set up stack frame */
mov ebp, esp ; ESP指向新的框架(函數(shù)調(diào)用框架)
/* Save the parameters */
mov eax, [ebp+0] ; 原來的EBP终惑,就是自陷框架指針,就是T1
mov ecx, [ebp+8] ; Context
/* Call KiContinue */
push eax ;TrapFrame
push 0 ;ExceptionFrame
push ecx ;Context
call _KiContinue@12 ; 將Context恢復(fù)到T1中
/* Check if we failed (bad context record) */
or eax, eax
jnz Error
/* Check if test alert was requested */
cmp dword ptr [ebp+12], 0
je DontTest
/* Test alert for the thread */
mov al, [ebx+KTHREAD_PREVIOUS_MODE]
push eax
call _KeTestAlertThread@4 ; 檢查用戶模式APC隊列是否為空门扇,不空將UserApcPending置為TRUE
DontTest:
/* Return to previous context */
pop ebp
mov esp, ebp
jmp _KiServiceExit2 ; 本質(zhì)和_KiServiceExit相同雹有,如果還有用戶APC,會繼續(xù)投遞臼寄,直到投遞完霸奕,才會回到用戶被中斷的點
Error:
pop ebp
mov esp, ebp
jmp _KiServiceExit
.endfunc
下面將_KiServiceExit到IntCallUserApc的流程總結(jié)一下:
到這里,終于執(zhí)行到了用戶的Apc函數(shù)脯厨。
結(jié)賬走人
到這铅祸,APC流程基本弄清楚了。
下一篇將結(jié)合APC機制分析一下最近比較新的AtomBombing注入技術(shù)的詳細實現(xiàn)和各個細節(jié)合武。
參考
轉(zhuǎn)載請注明出處临梗,博客原文:http://anhkgg.github.io/win-apc-analyze1/