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 分钟前
windows上使用Lycium 交叉编译移植鸿蒙三方库指南
windows·华为·harmonyos
FL162386312923 分钟前
flash_attn windows whl下载安装教程
windows·stm32·单片机
素素.陈24 分钟前
根据图片中的起始位置的特殊内容将图片进行分组
java·linux·windows
郑泰科技1 小时前
windows下启动hbase的步骤
数据库·windows·hbase
用户6135411460161 小时前
nbsetup2.8.4128管理工具安装教程及工具详解
windows
蒜丶2 小时前
Windows 批量删除 .svn 隐藏文件
windows·svn
染指11102 小时前
24.IDA逆向句柄表算法-Windows驱动
windows·驱动开发·内核·保护·windows驱动
Damon小智2 小时前
Windows系统安装Docker容器搭建Linux环境
linux·运维·windows·docker·子系统
yuuki2332333 小时前
【C++】揭秘STL:stack与queue的底层实现
java·c++·windows
weixin_425023003 小时前
Java开发高频实用技巧汇总(List操作/多线程/反射/监控等)
java·windows·list