二进制入门学习笔记-22.APC

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状态的函数才能成功执行。以sleepsleepEx为例,其中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.修改sleepsleepEx,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注入