用户态异步执行注入-APC与Fiber

admin 2026-08-29 04:33:09 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文讲解Windows用户态异步执行注入技术,先回顾CreateRemoteThread注入的完整EDR检测链,再深入剖析APC内核数据结构、KiDeliverApc派发机制、经典APC注入流程、EarlyBird变体及Fiber原理与注入方法,指出异步执行可消除跨进程线程创建这一最强检测信号,但跨进程内存操作仍受ETW-TI监控,并给出选择alertable目标线程、结合ModuleStomping合法化例程地址等规避建议。 综合评分: 86 文章分类: 红队,免杀,渗透测试,实战经验,安全工具


用户态异步执行注入-APC与Fiber

原创

pandazhengzheng pandazhengzheng

安全分析与研究

2026年8月28日 22:00 广东

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

目录

  1. 为什么需要异步执行注入
  2. Remote Thread Injection 的检测回顾
  3. APC 机制深度原理
  4. APC Injection 技术
  5. Early Bird APC Injection
  6. 特殊 APC 变体与进阶
  7. Fiber 机制深度原理
  8. Fiber 注入技术
  9. 两种技术的对比与组合
  10. EDR 检测与规避总览
  11. 小结

1. 为什么需要异步执行注入

1.1 注入的本质问题

“注入”要解决两个问题:

  1. 代码落地:把 shellcode/payload 放到目标进程的内存中(可执行);
  2. 执行触发:让目标进程的某个线程开始执行这段代码。

经典的 CreateRemoteThread 同时解决两者:VirtualAllocEx 落地 + WriteProcessMemory 写入 + CreateRemoteThread(start=addr) 触发。但它产生一组极强的信号:

  • 跨进程 OpenProcess(句柄回调);
  • 跨进程 VirtualAllocEx(ETW-TI EtwTiLogAllocExecVm);
  • 跨进程 WriteProcessMemory(ETW-TI EtwTiLogReadWriteVm);
  • 跨进程 CreateRemoteThread(inline hook + ETW-TI EtwTiLogCreateThread + PsSetCreateThreadNotifyRoutine)。

1.2 异步执行的思路

异步执行注入保留”代码落地”(仍需跨进程写入),但改变”执行触发”方式:不创建新线程,而是让目标进程已有的线程去执行 payload。这削减了”新线程创建”这一最强信号,并将”执行”伪装为该线程的正常异步处理。

Windows 提供两种用户态异步执行机制:

  • APC(Asynchronous Procedure Call):内核提供的线程异步调用队列;
  • Fiber(纤程):用户态实现的协作式调度单元。

2. Remote Thread Injection 的检测回顾

作为对照基线,回顾 CreateRemoteThread 的完整检测链:

| 步骤 | API | 检测信号 | | — | — | — | | 1 | OpenProcess(PROCESS_ALL_ACCESS) | ObRegisterCallbacks 记录跨进程句柄;剥离危险权限 | | 2 | VirtualAllocEx(RWX) | inline hook + ETW-TI EtwTiLogAllocExecVm;RWX 是强特征 | | 3 | WriteProcessMemory | inline hook + ETW-TI EtwTiLogReadWriteVm | | 4 | CreateRemoteThread(addr) | inline hook + ETW-TI EtwTiLogCreateThread + PsSetCreateThreadNotifyRoutine | | 5 | 线程执行 | 新线程的调用栈不自然(起始地址为私有内存) |

关键信号:第 4 步的”跨进程线程创建”几乎无法在用户态规避——NtCreateThreadEx 必然触发内核回调。因此异步执行注入的核心收益是消除第 4 步


3. APC 机制深度原理

3.1 APC 的内核数据结构

每个线程(KTHREAD)内嵌一个 KAPC_STATE

typedef struct _KAPC_STATE {
    LIST_ENTRY ApcListHead[2];   // [0]=KernelMode, [1]=UserMode
    PKPROCESS Process;
    BOOLEAN KernelApcInProgress;
    BOOLEAN KernelApcPending;
    BOOLEAN UserApcPending;
    ...
} KAPC_STATE;

每个 APC 节点是一个 KAPC

typedef struct _KAPC {
    UCHAR Type;            // ApcObject
    UCHAR SpareByte0;
    UCHAR Size;
    UCHAR SpareByte1;
    ULONG SpareLong0;
    PVOID Thread;          // 目标线程
    LIST_ENTRY ApcListEntry;
    PVOID KernelRoutine;   // 内核态例程(派发时先执行)
    PVOID RundownRoutine;  // 线程退出时的清理例程
    PVOID NormalRoutine;   // 用户态例程(最终执行)
    PVOID NormalContext;
    PVOID SystemArgument1;
    PVOID SystemArgument2;
    CHAR ApcMode;          // KernelMode / UserMode
    BOOLEAN Inserted;
} KAPC;

3.2 APC 排队:NtQueueApcThread

用户态调用 NtQueueApcThread(hThread, pApcRoutine, pArg1, pArg2, pArg3) 时,内核:

  1. 分配并初始化一个 KAPCNormalRoutine = pApcRoutineApcMode = UserMode
  2. 将其插入目标线程 KTHREAD.ApcState.ApcListHead[UserMode]
  3. 若目标线程正在 alertable wait,置 UserApcPending = TRUE 并取消其等待(使其从 wait 返回);
  4. 目标线程在从 wait 返回路径上调用 KiDeliverApc,派发 APC。

3.3 APC 派发:KiDeliverApc

当线程进入 alertable wait 后被唤醒(或主动调用 NtTestAlert),内核在返回用户态前调用 KiDeliverApc

  1. 遍历 ApcListHead[KernelMode],对每个内核 APC 调用其 KernelRoutine
  2. 遍历 ApcListHead[UserMode],对每个用户 APC:
  • 先调用 KernelRoutine(通常是 KeBugCheck 或默认的 KiInitializeUserApc);
  • KiInitializeUserApc 在用户态栈上构造一个 trap frame,设置用户态返回到 ntdll!KiUserApcDispatcher
  1. 返回用户态,控制权交给 KiUserApcDispatcher
  2. KiUserApcDispatcher 调用 NormalRoutine(NormalContext, SystemArgument1, SystemArgument2)
  3. NormalRoutine 返回后,KiUserApcDispatcher 调用 NtContinue 恢复原 trap frame,线程从被中断处继续。

3.4 Alertable Wait

线程只有在 alertable 状态才会派发用户态 APC。以下 API 使线程进入 alertable wait:

  • SleepEx(..., TRUE)
  • WaitForSingleObjectEx(..., TRUE)
  • WaitForMultipleObjectsEx(..., TRUE)
  • SignalObjectAndWait(..., TRUE)
  • GetOverlappedResultEx(alertable 变体)

关键实战问题:目标线程必须实际进入 alertable wait,APC 才会派发。许多线程从不进入 alertable wait(如 GUI 线程的消息循环、纯计算线程),对它们排队 APC 会一直挂起在队列中不执行。

3.5 哪些线程适合作为 APC 目标

  • 服务线程svchost.exe 中的服务线程经常在 WaitForSingleObjectEx 上等待事件,且常 alertable;
  • 工作线程:线程池工作线程(ntdll!TppWorkerThread)在 alertable wait 等待工作项;
  • I/O 线程:使用 overlapped I/O 的线程;
  • 新创建的挂起线程:见 Early Bird。

不适合:GUI 消息循环线程(GetMessage 非 alertable)、纯 CPU 计算线程。


4. APC Injection 技术

4.1 经典流程

HANDLE hProc = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, pid);
LPVOID addr = VirtualAllocEx(hProc, NULL, shellcodeSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(hProc, addr, shellcode, shellcodeSize, NULL);
DWORD oldProt;
VirtualProtectEx(hProc, addr, shellcodeSize, PAGE_EXECUTE_READ, &oldProt);

HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, tid);
NtQueueApcThread(hThread, addr, NULL, NULL, NULL);
// 等待目标线程进入 alertable wait,APC 自动派发

4.2 关键实现细节

4.2.1 内存属性

  • 先分配 PAGE_READWRITE 写入,再 VirtualProtectEx 改为 PAGE_EXECUTE_READ避免直接分配 RWX(RWX 是强特征);
  • 但 VirtualProtectEx 提升至可执行仍触发 ETW-TI EtwTiLogProtectExecVm

4.2.2 APC 例程地址

NtQueueApcThread 的第二个参数是目标进程地址空间中的例程地址。攻击者直接传 shellcode 地址。但 EDR 在 hook 中检查该地址:

  • 若落在 MEM_PRIVATE → 强可疑;
  • 若落在 MEM_IMAGE 但非合法导出函数 → 可疑;
  • 若落在 MEM_IMAGE 的合法函数(如 LoadLibraryW)→ 较不可疑,但 LoadLibraryW 加载非签名 DLL 仍被映像加载监控。

进阶:将 shellcode 写入一个已 Module Stomping 的合法 DLL 的 .text,使 APC 例程地址通过”映像范围”检查(见第三节组合)。

4.2.3 目标线程选择

  • 枚举目标进程线程:NtQuerySystemInformation(SystemProcessInformation) 获取所有 TID;
  • 优先选择工作线程/服务线程(更可能 alertable);
  • 若目标线程不 alertable,APC 永不派发——需选择或制造 alertable 线程。

4.3 EDR 对 APC Injection 的检测

| 检测点 | 信号源 | 检测逻辑 | | — | — | — | | NtQueueApcThread 调用 | inline hook | 检查例程地址合法性、目标线程是否跨进程 | | APC 排队 | ETW-TI EtwTiLogQueueApc | 内核埋点,记录排队者/目标/例程地址 | | APC 派发 | ETW-TI EtwTiLogApcCapture | 派发时感知,可检查例程地址 | | 跨进程内存操作 | ETW-TI | EtwTiLogAllocExecVmEtwTiLogReadWriteVm | | 例程地址内存属性 | 内存扫描 | 派发后扫描例程地址所在内存 | | 调用栈 | 栈回溯 | APC 派发时栈帧不自然(无合法调用方) |

注意:APC Injection 不触发PsSetCreateThreadNotifyRoutine 和 EtwTiLogCreateThread——这是相对 CreateRemoteThread 的核心收益。

4.4 规避策略

  1. 避免跨进程:在 loader 自身进程内创建一个 alertable 线程,对其排队 APC。由于不跨进程,ETW-TI 的”跨进程”信号不触发。但 loader 自身仍需不被怀疑。
  2. 例程地址合法化:结合 Module Stomping,使例程地址落在合法映像。
  3. **使用 NtQueueApcThreadEx / Ex2**:较新变体支持更多上下文,但同样被 hook/ETW-TI 感知。
  4. APC 内容伪装:排队一个指向 LoadLibraryW 的 APC,参数为 DLL 路径——但 DLL 加载仍被监控。可结合反射加载(但反射加载又有自身信号)。

5. Early Bird APC Injection


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:安全分析与研究 pandazhengzheng pandazhengzheng《用户态异步执行注入-APC与Fiber》

评论:0   参与:  0