Windows APC注入解析

1. 前言

常用的用户态注入通常使用CreateRemoteThread通过在目标进程中创建新线程的方式来远程执行注入代码。但是,相比APC注入直接利用现有线程上下文执行,传统方式存在明显的线程/句柄/权限操作,非常容易被检测。

2. 简介

  • 基本概念

APC(Asynchronous Procedure Call - 异步过程调用)是一种在目标线程"合适时机"异步安排并执行函数的机制。它允许一个线程把一个函数排队到另一个线程上,当那个线程进入可执行 APC的状态时,内核会在适当时机通过KiDeliverApc调度APC。

  • 分类

按照执行上下文而言,分为用户APC(User-mode APC)和内核APC(Kernel APC)两类,也可以继续细分为普通和特殊APC。

其中,内核例程在APC_LEVEL执行,如果存在正常例程,则在PASSIVE_LEVEL执行。

类型 模式 普通例程 内核例程 位置 说明
普通 用户 队列尾部 可警告状态下执行单个APC
特殊 用户 队列头部 win10新增特性,全部顺序执行
普通 内核 队列尾部 全部顺序执行
特殊 内核 --- 队列头部 全部顺序执行

3. 初始化

内核中通过未导出函数KeInitializeApc初始化APC,具体定义如下。

cpp 复制代码
typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment, // 原始线程
    AttachedApcEnvironment, // 附加线程
    CurrentApcEnvironment,  // 当前线程
    InsertApcEnvironment    // 
} KAPC_ENVIRONMENT;

NTKERNELAPI VOID NTAPI KeInitializeApc(
    _Out_ PRKAPC Apc,                             // APC上下文
    _In_ PETHREAD Thread,                         // 目标线程
    _In_ KAPC_ENVIRONMENT Environment,
    _In_ PKKERNEL_ROUTINE KernelRoutine,          // 通常用于APC正常执行时回收系统资源
    _In_opt_ PKRUNDOWN_ROUTINE RundownRoutine,    // 用于目标线程终止正常内核例程无法执行时回收系统资源
    _In_opt_ PKNORMAL_ROUTINE NormalRoutine,      // 等待执行的APC例程
    _In_opt_ KPROCESSOR_MODE ApcMode,
    _In_opt_ PVOID NormalContext);                // 传递APC例程的第一个参数

从上图代码中可以看出,KeInitializeApc只是简单通过传参初始化APC结构,没有什么复杂业务。

其中相对重要的一个参数就是Environment,它会在插入APC队列时和线程APC状态一起决定要插入的APC队列。

4. 排队

KeInsertQueueApc的主要通过调用KiInsertQueueApcKiSignalThreadForApcKiExitDispatcher三个函数来实现具体的业务功能。

cpp 复制代码
NTKERNELAPI BOOLEAN NTAPI KeInsertQueueApc(
    PRKAPC Apc,
    PVOID SystemArgument1,   // 传递APC例程的第二个参数
    PVOID SystemArgument2,   // 传递APC例程的第三个参数
    KPRIORITY Increment);

通过KiInsertQueueApc实现APC的插入的同时,同时调用KiSignalThreadForApcKiExitDispatcher来更新线程状态和执行特定的APC。

  • KiInsertQueueApc根据线程和APC的状态决定最终插入的APC队列的位置。

    其中Thread->ApcStateIndex 代表线程是否附加到其它线程,SavedApcState用于备份线程的原始APC,ApcState指向当前活跃的APC。从上图可以看到,

  • Apc->ApcStateIndex = 0 && Thread->ApcStateIndex = 1时,将APC插入目标线程的SavedApcState

  • Apc->ApcStateIndex = 0 && Thread->ApcStateIndex = 0时,将APC插入目标线程的ApcState

  • Apc->ApcStateIndex != 0 时,也将APC插入目标线程的ApcState队列中

  • KiSignalThreadForApc函数根据当前线程、APC信息决定是否更新线程状态,优先执行某些内核APC。


  • KiExitDispatcher函数通过调用KiDeliverApc执行内核APC。

5. 执行

内部函数KiDeliverApc负责实际投递和执行 APC。它是 APC 机制的核心调度器。

c 复制代码
VOID KiDeliverApc (
    IN KPROCESSOR_MODE PreviousMode,
    IN PKEXCEPTION_FRAME ExceptionFrame,
    IN PKTRAP_FRAME TrapFrame
    )
  1. 在启用特殊内核APC的情况下,执行所有的内核APC

  2. 第一步的前提下执行普通内核APC,直到普通内核APC被禁用,或者APC正在执行

  1. 第一步的前提下无条件执行所有的特殊内核APC

  2. 执行用户模式APC时遍历APC队列,直到满足条件

  3. 从队列中移除APC,同时将SpecialUserApcPending复位

  4. 执行用户APC内核例程

  5. 通过KiInitializeUserApc修改KTRAP_FRAME获取用户APC执行时机

KiInitializeUserApc函数入口主要包含KiInitiateUserApcKiContinueEx

前者往往从系统调用或者APC中断来执行,而后者则一般用于执行普通APC。

两者的区别在于从KiContinueEx进入时会复用上一次执行APC时的地址,避免反复备份KTRAP_FRAME


  1. KeUserApcDispatcher直接执行APC,或者通过wow64转发,最后调用ZwContinueEx回到内核
  1. KiContinueEx通过flag参数决定是否执行下一个用户APC,或者执行原始的用户线程

通过以上9个步骤,我们可以得出以下结论:

  • 内核APC一次调用全部执行

系统调用/中断 KiDeliverApc

  • 用户特殊APC一次调用执行一个?(此处存疑)

系统调用/中断 KiDeliverApc KiInitializeUserApc KeUserApcDispatcher KiContinueEx 原始线程

  • 用户普通APC唤醒状态一次调用全部执行

Y Y 系统调用/中断 KiDeliverApc UserApcPending KiInitializeUserApc KeUserApcDispatcher KiContinueEx 是否存在用户APC 原始线程

6. 问题

  • 问题1:插入用户APC后可能无法执行

当插入APC的线程处于不可Alertable状态时,APC会一直排队,直到状态变化。用户态可使用SleepExWaitForSingleObjectEx等函数,内核态则推荐KeDelayExecutionThread或者KeTestAlertThread

  • 问题2:使用驱动插入APC,若停止驱动时APC正在执行,则可能会导致BSOD。

当用户自定义的KernelRoutine正在执行时,驱动停止会导致函数不可用,从而引发BSOD。可以通过在KAPC中持有EX_RUNDOWN_REF来保证EX_RUNDOWN_REF完全释放前驱动程序不可卸载。

7.apc_injector

一个简易的内核注入测试工程

相关推荐
骇客野人2 小时前
Java @Mapper 使用注解的方式注入SQL
linux·服务器·windows
脸红ฅฅ*的思春期11 小时前
免杀对抗—WinDbg查看Windows内存
windows·windbg·windows内核·windows内存查看
无限进步_15 小时前
深入理解 C/C++ 内存管理:从内存布局到动态分配
c语言·c++·windows·git·算法·github·visual studio
克喵的水银蛇16 小时前
Flutter 通用标签选择组件:TagSelector 支持单选 / 多选
javascript·windows·flutter
txzz888816 小时前
网络应用netstart命令
网络·windows·计算机网络·microsoft
水饺编程17 小时前
第3章,[标签 Win32] :处理 WM_PRINT 消息
c语言·c++·windows·visual studio
求梦82018 小时前
Java:Windows家庭中文版的Docker下载安装
java·windows·docker
北极糊的狐18 小时前
报错java: 找不到符号符号: 类 XxxController位置: 程序包 com.ruoyi.xxx.xxx.service
开发语言·windows·python
这儿有一堆花19 小时前
Windows 环境下 Nmap 的实战逻辑
windows·网络安全