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

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

相关推荐
扬帆破浪3 小时前
免费开源AI软件.桌面单机版,可移动的AI知识库,察元 AI桌面版:免费开源的AI软件首启动 FirstRunSetup向导背后做了什么
人工智能·windows·电脑·知识图谱
ITHAOGE153 小时前
2026年Win7最终版ISO系统映像下载!(集成补丁、旗舰版、完整无精简、64位/32位可选、Windows 7、简体中文/繁体中文/英语可选)
windows·科技·microsoft·微软·电脑
yuanpan4 小时前
Python + PyAutoGUI 实战:Windows 自动化办公脚本开发入门
windows·python·自动化
扬帆破浪4 小时前
免费开源AI软件.桌面单机版,可移动的AI知识库,察元 AI桌面版:Windows装包被防病毒拦了 看安装日志和签名链的实战
人工智能·windows·开源·知识图谱
万邦科技Lafite4 小时前
API接口一键获取商品评论,根据商品评论分析客户画像
linux·服务器·数据库·windows·microsoft·电商开放平台
Kiling_07044 小时前
Java集合框架:List集合详解与应用
java·开发语言·windows
一个人旅程~5 小时前
如何永久关闭bitlocker并防止出现更新后被强制加密?
linux·windows·经验分享·电脑
csdn2015_5 小时前
java springboot 文件导入,判断第一列的值是否有重复
java·windows·spring boot
扬帆破浪5 小时前
免费开源AI软件.桌面单机版,可移动的AI知识库,察元 AI桌面版:本地离线知识库的妥协与收益 老电脑跑察元AI的可行边界
人工智能·windows·开源·电脑·知识图谱
Lyrig~6 小时前
ClaudeCode-cli Windows终端安装,并配置Openrouter链接
windows