文章总结: 本文讲解Windows用户态异步执行注入技术,先回顾CreateRemoteThread注入的完整EDR检测链,再深入剖析APC内核数据结构、KiDeliverApc派发机制、经典APC注入流程、EarlyBird变体及Fiber原理与注入方法,指出异步执行可消除跨进程线程创建这一最强检测信号,但跨进程内存操作仍受ETW-TI监控,并给出选择alertable目标线程、结合ModuleStomping合法化例程地址等规避建议。 综合评分: 86 文章分类: 红队,免杀,渗透测试,实战经验,安全工具
用户态异步执行注入-APC与Fiber
原创
pandazhengzheng pandazhengzheng
安全分析与研究
2026年8月28日 22:00 广东
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
目录
- 为什么需要异步执行注入
- Remote Thread Injection 的检测回顾
- APC 机制深度原理
- APC Injection 技术
- Early Bird APC Injection
- 特殊 APC 变体与进阶
- Fiber 机制深度原理
- Fiber 注入技术
- 两种技术的对比与组合
- EDR 检测与规避总览
- 小结
1. 为什么需要异步执行注入
1.1 注入的本质问题
“注入”要解决两个问题:
- 代码落地:把 shellcode/payload 放到目标进程的内存中(可执行);
- 执行触发:让目标进程的某个线程开始执行这段代码。
经典的 CreateRemoteThread 同时解决两者:VirtualAllocEx 落地 + WriteProcessMemory 写入 + CreateRemoteThread(start=addr) 触发。但它产生一组极强的信号:
- 跨进程
OpenProcess(句柄回调); - 跨进程
VirtualAllocEx(ETW-TIEtwTiLogAllocExecVm); - 跨进程
WriteProcessMemory(ETW-TIEtwTiLogReadWriteVm); - 跨进程
CreateRemoteThread(inline hook + ETW-TIEtwTiLogCreateThread+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) 时,内核:
- 分配并初始化一个
KAPC,NormalRoutine = pApcRoutine,ApcMode = UserMode; - 将其插入目标线程
KTHREAD.ApcState.ApcListHead[UserMode]; - 若目标线程正在 alertable wait,置
UserApcPending = TRUE并取消其等待(使其从 wait 返回); - 目标线程在从 wait 返回路径上调用
KiDeliverApc,派发 APC。
3.3 APC 派发:KiDeliverApc
当线程进入 alertable wait 后被唤醒(或主动调用 NtTestAlert),内核在返回用户态前调用 KiDeliverApc:
- 遍历
ApcListHead[KernelMode],对每个内核 APC 调用其KernelRoutine; - 遍历
ApcListHead[UserMode],对每个用户 APC:
- 先调用
KernelRoutine(通常是KeBugCheck或默认的KiInitializeUserApc); KiInitializeUserApc在用户态栈上构造一个 trap frame,设置用户态返回到ntdll!KiUserApcDispatcher;
- 返回用户态,控制权交给
KiUserApcDispatcher; KiUserApcDispatcher调用NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);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-TIEtwTiLogProtectExecVm。
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 | EtwTiLogAllocExecVm 、EtwTiLogReadWriteVm |
| 例程地址内存属性 | 内存扫描 | 派发后扫描例程地址所在内存 |
| 调用栈 | 栈回溯 | APC 派发时栈帧不自然(无合法调用方) |
注意:APC Injection 不触发PsSetCreateThreadNotifyRoutine 和 EtwTiLogCreateThread——这是相对 CreateRemoteThread 的核心收益。
4.4 规避策略
- 避免跨进程:在 loader 自身进程内创建一个 alertable 线程,对其排队 APC。由于不跨进程,ETW-TI 的”跨进程”信号不触发。但 loader 自身仍需不被怀疑。
- 例程地址合法化:结合 Module Stomping,使例程地址落在合法映像。
- **使用
NtQueueApcThreadEx/Ex2**:较新变体支持更多上下文,但同样被 hook/ETW-TI 感知。 - APC 内容伪装:排队一个指向
LoadLibraryW的 APC,参数为 DLL 路径——但 DLL 加载仍被监控。可结合反射加载(但反射加载又有自身信号)。
5. Early Bird APC Injection
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:安全分析与研究 pandazhengzheng pandazhengzheng《用户态异步执行注入-APC与Fiber》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论