0%
毅种循环

模块 39 - APC 注入

恶意软件开发课程 - APC 注入


模块 39 - APC 注入#

[!IMPORTANT] 本知识库声明

本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示实践心得

  1. 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
  2. 版权归属:原始知识产权归原作者/官方所有。
  3. 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程
  4. 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。

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 之一来完成:

这些函数用于同步线程并提高应用程序的性能和响应能力,但在本例中,传递一个虚拟事件的句柄就足够了。传递给这些函数的正确参数不是必需的,因为仅仅使用其中一个函数就足以将线程置于可警报状态。

要创建虚拟事件,将使用 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 注入实现逻辑#

回顾一下实现逻辑如下:

  1. 首先,创建一个线程,该线程运行前面提到的函数之一,以将其置于可警报状态。
  2. 将 Payload 注入内存。
  3. 将线程句柄和 Payload 基地址作为输入参数传递给 QueueUserAPC

APC 注入函数#

RunViaApcInjection 是一个执行 APC 注入的函数,需要 3 个参数:

  • hThread - 可警报或挂起线程的句柄。
  • pPayload - 指向 Payload 基地址的指针。
  • sPayloadSize - Payload 的大小。

演示 - 使用可警报线程的 APC 注入#

图片
图片

图片
图片

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

图片
图片

图片
图片


🎯 总结#

在本模块中,我们学习了:

  1. APC (Asynchronous Procedure Call):Windows 的异步过程调用机制。
  2. 可警报状态 (Alertable State):这是 APC 执行的关键。线程必须主动“休息”并声明自己可接收 APC。
  3. QueueUserAPC:将代码“插入”线程待办列表的核心 API。
  4. 多种姿势:既可以劫持正在“休息”的可警报线程,也可以在挂起线程被激活前塞入任务。

💡 关键要点

  • APC 注入比线程劫持 (Thread Hijacking) 稍微温和一些,因为它利用的是操作系统内置的任务调度机制。
  • 在红队行动中,“早期鸟” (Early Bird) APC 注入(即利用挂起线程)是非常流行的技术,因为它能让 Shellcode 在目标进程真正的逻辑开始运行前就执行。

📚 下一步学习

下一个模块将介绍 Early Bird APC Injection。我们将深入探讨这种在进程初始化阶段就实现注入的高级技术,它在绕过某些 EDR 检测方面表现卓越!