1.APC简介
APC是异步过程调用(Asyncroneus Procedure Call)的缩写。简单来说,每一个线程都有APC列表,其他线程想要控制这个线程的时候可以向APC列表里面插入APC实现对这个线程的控制,当这个线程被唤醒时,他就会遍历这个列表,执行需要被执行的函数。
1.1 APC寻址
APC的相关内容保存在_ETHREAD
结构体头部的_KTHREAD
子结构体的ApcState
结构体中,这个结构体中包含了APC链表和APC状态等重要信息。
kd> dt _ETHREAD nt!_ETHREAD +0x000 Tcb : _KTHREAD +0x1c0 CreateTime : _LARGE_INTEGER +0x1c0 NestedFaultCount : Pos 0, 2 Bits +0x1c0 ApcNeeded : Pos 2, 1 Bit +0x1c8 ExitTime : _LARGE_INTEGER +0x1c8 LpcReplyChain : _LIST_ENTRY +0x1c8 KeyedWaitChain : _LIST_ENTRY +0x1d0 ExitStatus : Int4B +0x1d0 OfsChain : Ptr32 Void +0x1d4 PostBlockList : _LIST_ENTRY +0x1dc TerminationPort : Ptr32 _TERMINATION_PORT +0x1dc ReaperLink : Ptr32 _ETHREAD +0x1dc KeyedWaitValue : Ptr32 Void +0x1e0 ActiveTimerListLock : Uint4B +0x1e4 ActiveTimerListHead : _LIST_ENTRY +0x1ec Cid : _CLIENT_ID +0x1f4 LpcReplySemaphore : _KSEMAPHORE +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE +0x208 LpcReplyMessage : Ptr32 Void +0x208 LpcWaitingOnPort : Ptr32 Void +0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION +0x210 IrpList : _LIST_ENTRY +0x218 TopLevelIrp : Uint4B +0x21c DeviceToVerify : Ptr32 _DEVICE_OBJECT +0x220 ThreadsProcess : Ptr32 _EPROCESS +0x224 StartAddress : Ptr32 Void +0x228 Win32StartAddress : Ptr32 Void +0x228 LpcReceivedMessageId : Uint4B +0x22c ThreadListEntry : _LIST_ENTRY +0x234 RundownProtect : _EX_RUNDOWN_REF +0x238 ThreadLock : _EX_PUSH_LOCK +0x23c LpcReplyMessageId : Uint4B +0x240 ReadClusterSize : Uint4B +0x244 GrantedAccess : Uint4B +0x248 CrossThreadFlags : Uint4B +0x248 Terminated : Pos 0, 1 Bit +0x248 DeadThread : Pos 1, 1 Bit +0x248 HideFromDebugger : Pos 2, 1 Bit +0x248 ActiveImpersonationInfo : Pos 3, 1 Bit +0x248 SystemThread : Pos 4, 1 Bit +0x248 HardErrorsAreDisabled : Pos 5, 1 Bit +0x248 BreakOnTermination : Pos 6, 1 Bit +0x248 SkipCreationMsg : Pos 7, 1 Bit +0x248 SkipTerminationMsg : Pos 8, 1 Bit +0x24c SameThreadPassiveFlags : Uint4B +0x24c ActiveExWorker : Pos 0, 1 Bit +0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit +0x24c MemoryMaker : Pos 2, 1 Bit +0x250 SameThreadApcFlags : Uint4B +0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit +0x250 LpcExitThreadCalled : Pos 1, 1 Bit +0x250 AddressSpaceOwner : Pos 2, 1 Bit +0x254 ForwardClusterOnly : UChar +0x255 DisablePageFaultClustering : UChar kd> dt _KTHREAD nt!_KTHREAD +0x000 Header : _DISPATCHER_HEADER +0x010 MutantListHead : _LIST_ENTRY +0x018 InitialStack : Ptr32 Void +0x01c StackLimit : Ptr32 Void +0x020 Teb : Ptr32 Void +0x024 TlsArray : Ptr32 Void +0x028 KernelStack : Ptr32 Void +0x02c DebugActive : UChar +0x02d State : UChar +0x02e Alerted : [2] UChar +0x030 Iopl : UChar +0x031 NpxState : UChar +0x032 Saturation : Char +0x033 Priority : Char +0x034 ApcState : _KAPC_STATE +0x04c ContextSwitches : Uint4B +0x050 IdleSwapBlock : UChar +0x051 Spare0 : [3] UChar +0x054 WaitStatus : Int4B +0x058 WaitIrql : UChar +0x059 WaitMode : Char +0x05a WaitNext : UChar +0x05b WaitReason : UChar +0x05c WaitBlockList : Ptr32 _KWAIT_BLOCK +0x060 WaitListEntry : _LIST_ENTRY +0x060 SwapListEntry : _SINGLE_LIST_ENTRY +0x068 WaitTime : Uint4B +0x06c BasePriority : Char +0x06d DecrementCount : UChar +0x06e PriorityDecrement : Char +0x06f Quantum : Char +0x070 WaitBlock : [4] _KWAIT_BLOCK +0x0d0 LegoData : Ptr32 Void +0x0d4 KernelApcDisable : Uint4B +0x0d8 UserAffinity : Uint4B +0x0dc SystemAffinityActive : UChar +0x0dd PowerState : UChar +0x0de NpxIrql : UChar +0x0df InitialNode : UChar +0x0e0 ServiceTable : Ptr32 Void +0x0e4 Queue : Ptr32 _KQUEUE +0x0e8 ApcQueueLock : Uint4B +0x0f0 Timer : _KTIMER +0x118 QueueListEntry : _LIST_ENTRY +0x120 SoftAffinity : Uint4B +0x124 Affinity : Uint4B +0x128 Preempted : UChar +0x129 ProcessReadyQueue : UChar +0x12a KernelStackResident : UChar +0x12b NextProcessor : UChar +0x12c CallbackStack : Ptr32 Void +0x130 Win32Thread : Ptr32 Void +0x134 TrapFrame : Ptr32 _KTRAP_FRAME +0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE "这个正常情况下:ApcStatePointer[0] 指向 ApcState,ApcStatePointer[1] 指向 SavedApcState。挂靠情况下:ApcStatePointer[0] 指向 SavedApcState,ApcStatePointer[1] 指向 ApcState" +0x140 PreviousMode : Char +0x141 EnableStackSwap : UChar +0x142 LargeStack : UChar +0x143 ResourceIndex : UChar +0x144 KernelTime : Uint4B +0x148 UserTime : Uint4B +0x14c SavedApcState : _KAPC_STATE "备用APC队列" +0x164 Alertable : UChar +0x165 ApcStateIndex : UChar "当前线程是否挂靠在别的进程,正常是0,挂靠是1" +0x166 ApcQueueable : UChar "是否可以插入APC,为真才能插入" +0x167 AutoAlignment : UChar +0x168 StackBase : Ptr32 Void +0x16c SuspendApc : _KAPC +0x19c SuspendSemaphore : _KSEMAPHORE +0x1b0 ThreadListEntry : _LIST_ENTRY +0x1b8 FreezeCount : Char +0x1b9 SuspendCount : Char +0x1ba IdealProcessor : UChar +0x1bb DisableBoost : UChar kd> dt _KAPC_STATE nt!_KAPC_STATE +0x000 ApcListHead : [2] _LIST_ENTRY "2个双向APC队列,用户APC和内核APC" +0x010 Process : Ptr32 _KPROCESS "当前线程所挂靠的进程" +0x014 KernelApcInProgress : UChar "内核APC是否正在执行" +0x015 KernelApcPending : UChar "是否有等待执行的内核APC" +0x016 UserApcPending : UChar "是否有等待执行的用户APC"
无论当前线程是否挂靠在其他进程中,ApcStatePointer[ApcStateIndex]都指向当前进程的ApcState。
1.2 APC结构体
APC结构体在内核叫_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总入口(3环) 或者 真正的内核APC函数(0环)
+0x020 NormalContext //用户APC:真正的APC函数;内核APC:NULL ;
+0x024 SystemArgument1//APC函数的参数
+0x028 SystemArgument2//APC函数的参数
+0x02c ApcStateIndex //挂哪个队列,有四个值:0 1 2 3
+0x02d ApcMode //内核APC是0 用户APC是1
+0x02e Inserted //表示本APC是否已挂入队列 挂入前:0 挂入后 1
1.3 备用APC队列
在_KTHREAD
结构体的0x14c偏移处有一个SavedApcState
结构体,这个结构体也是一个_KAPC_STATE
,这个地方的结构体被称为备用APC队列。当这个线程的挂靠在别的进程中时,就会用到这个_KAPC_STATE
结构体。例如,当A进程的线程T1去挂靠到了B进程,假如原来APC里存的函数地址是0x12345678,如果在B进程的地址空间里里执行这个函数那么地址就是错的。因此当挂靠到别的进程中时,就会把原有的ApcState
备份到这个SavedApcState
中,等恢复到A进程时再恢复APC队列。
2.APC挂入过程
三环调用QueueUserAPC函数过程如下图所示:
2.1 KeInitializeApc函数
VOID KeInitializeApc ( IN PKAPC Apc,//"KAPC指针" IN PKTHREAD Thread,//"目标线程" IN KAPC_ENVIRONMENT TargetEnvironment,//"0 1 2 3四种状态,这四个值就写到了ApcStateIndex里" IN PKKERNEL_ROUTINE KernelRoutine,//"销毁KAPC的函数地址" IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL, IN PKNORMAL_ROUTINE NormalRoutine,//"用户APC总入口或者内核apc函数地址" IN KPROCESSOR_MODE Mode,//"要插入用户apc队列还是内核apc队列,内核模式0,用户模式1" IN PVOID Context//"内核APC:NULL 用户APC:真正的APC函数地址" )
2.2 _KAPC.ApcStateIndex
这里的ApcStateIndex
是_KAPC
结构体里的,跟KTHREAD
里那个没关系。下表为ApcStateIndex
的具体意义:
ApcStateIndex | 含义 | 插入的位置 |
---|---|---|
0 | 原始环境 | ApcStatePointer[0] 指向 ApcState;ApcStatePointer[1] 指向 SavedApcState |
1 | 挂靠环境 | ApcStatePointer[0] 指向 SavedApcState;ApcStatePointer[1] 指向 ApcState |
2 | 当前环境 | 当前进程的ApcState |
3 | 插入APC时的当前环境 | 插入的时候,当前进程的ApcState |
3.APC执行
3.1 内核APC执行
内核的APC在线程切换的时候就会进行处理,内核APC一个while循环就处理完了。
3.2 用户APC执行
用户APC当线程从Ring0返回Ring3的时候才会执行,一般来说,在三环注入APC的时候,被注入线程中必须有某些运行了可能导致线程进入Alertable
状态的函数才能成功执行。以sleep
和sleepEx
为例,其中sleepEx
函数可以设置线程进入Alertable
状态。实验如下:
1.使用如下代码,线程不会进入Alertable
状态,APC将无法被执行。
#include <stdio.h> #include <Windows.h> HANDLE hThread1=NULL; void func1(){ while(TRUE){ printf("I am Func1!\n"); Sleep(3000); } } VOID WINAPI APCProc(LPVOID dwParam) { printf("I'm APC Func!!!\n"); } int main(){ printf("I'am main Thread!\n"); hThread1 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)func1,0,0,0); Sleep(3000); QueueUserAPC((PAPCFUNC)APCProc,hThread1,NULL); getchar(); }
2.修改sleep
为sleepEx
,APC成功执行
#include <stdio.h> #include <Windows.h> HANDLE hThread1=NULL; void func1(){ while(TRUE){ printf("I am Func1!\n"); SleepEx(3000,1); } } VOID WINAPI APCProc(LPVOID dwParam) { printf("I'm APC Func!!!\n"); } int main(){ printf("I'am main Thread!\n"); hThread1 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)func1,0,0,0); Sleep(3000); QueueUserAPC((PAPCFUNC)APCProc,hThread1,NULL); getchar(); }
练习
1.分析APC插入函数的流程 2.swapcontext分析看看APC什么时候执行
参考
- https://bbs.pediy.com/thread-258625.htm UserMode下的APC插入
- https://bbs.pediy.com/thread-258756.htm APC Dll注入