其实在此之前,我们已经见识过这种应用白名单绕过方法了,攻击者可以通过恶意软件来感染你的系统,并且使用已签名的受信任工具来获取系统更多的权限和功能。假设攻击者所使用的工具是由A厂商签名的,而你的应用白名单又允许A厂商签名的程序运行,那么攻击者的入侵活动就会畅通无阻了。除此之外,管理员们通常也会以发布者的身份来允许文件执行,这样就可以简化系统环境的部署过程了,但是这样的处理方式会给系统带来一定的安全风险。
常见绕过方法
在这个例子中,大家可以看看攻击者如何利用一个包含漏洞的已签名驱动来绕过系统内核模式的保护机制。
近期,Matt Graeber演示了一种新型的绕过方法。他可以通过一款已签名的调试器(windbg或cdb)来绕过Windows 10的Device Guard(设备保护)。
C#脚本
能够在命令行接口(csi.exe)中运行的C#脚本同样也是一种已签名的工具,攻击者可以对C#脚本进行修改,并且通过改造后的C#脚本来绕过用户模式下的代码完整性检测。
在深入研究C#脚本之前,我们首先要了解C#脚本的使用场景。C#脚本是一种用来测试C#和.Net代码的工具,在C#脚本的帮助下,开发人员可以直接测试或调试项目代码,而无需针对特定项目去创建多个测试单元。我们可以把它当作一种轻量级的编码选择,它不仅可以帮助开发人员在命令行接口中快速编码完成一个针对集合的LinQ方法调用,而且还可以检测用于解压文件的.NET API接口,或者调用一个REST API来查看程序的返回值内容。简单来说,C#脚本为开发人员提供了一种能够查询和理解API接口工作机制的简单方法。
关于C#脚本编程的内容请参阅微软公司的官方文档:[点我获取]
注:你可以在命令行接口中以交互式的形式来编译C#代码,这其实是学习C#语言的一种非常好的方法。
绕过方案
在下面的这个例子中,我在csi.exe中加载了一个任意的exe应用程序。我可以通过一个普通的文本文件来完成整个加载过程,测试环境是一台运行了Windows Device Guard的普通计算机。
下方给出的是我所使用的代码:
using System; using System.Reflection; string s = System.IO.File.ReadAllText(@"katz.txt"); byte[] b = System.Convert.FromBase64String(s); Assembly a = Assembly.Load(b); MethodInfo method = a.EntryPoint; object o = a.CreateInstance(method.Name); method.Invoke(o, null);
其中,“katz.txt“文件是一个采用了base64编码的图片文件。
实际上,系统并不应该允许这样的文件加载方式。虽然我们应该相信大部分由微软签名的代码,但这并不意味着所有由微软签名的代码都应该被信任。
这也就意味着,攻击者只需要使用csi.exe,并解决目标环境中相应的依赖问题(攻击文件在未压缩的情况下大小约为6MB),攻击者就可以成功绕过目标系统中的应用白名单限制,然后实施下一步的攻击。
缓解方案
在下面这个例子中,我创建了一个新的代码完整性策略,并且为所有已签名版本的代码设置了明确的拒绝规则。在测试过程中,我主要针对的是cdb.exe、windbg.exe、以及kd.exe这三个用户模式和内核模式下的调试器,并且这些调试器都是由微软签名的。在这篇文章发表之前,我已经从微软公司的Device Guard团队那里得到了证实,我将要描述的这种方法的确是目前最为理想的缓解方法,这种方法可以帮助你阻止那些可能会绕过你代码完整性策略的代码。
# The directory that contains the binaries that circumvent our Device Guard policy $Scanpath = 'C:\Program Files\Windows Kits\10\windows\x64' # The binaries that circumvent our Device Guard policy $DeviceGuardBypassApps = 'cdb.exe', 'windbg.exe', 'kd.exe' $DenialPolicyFilePath = 'BypassMitigationPolicy.xml' # Get file and signature information for every file in the scan directory $Files = Get-SystemDriver -ScanPath $Scanpath -UserPEs -NoShadowCopy # We'll use this to filter out the binaries we want to block $TargetFilePaths = $DeviceGuardBypassApps | ForEach-Object { Join-Path $Scanpath $_ } # Filter out the user-mode binaries we want to block # This would just as easily apply to drivers. Just change UserMode to $False # If you’re wanting this to apply to drivers though, you might consider using # the WHQLFilePublisher rule. $FilesToBlock = $Files | Where-Object { $TargetFilePaths -contains $_.FriendlyName -and $_.UserMode -eq $True } # Generate a dedicated device guard bypass policy that contains explicit deny rules for the binaries we want to block. New-CIPolicy -FilePath $DenialPolicyFilePath -DriverFiles $FilesToBlock -Level FilePublisher -Deny -UserPEs # Set the MinimumFileVersion to 99.0.0.0 - an arbitrarily high number. # Setting this value to an arbitraily high version number will ensure that any signed bypass binary prior to version 99.0.0.0 # will be blocked. This logic allows us to theoretically block all previous, current, and future versions of binaries assuming # they were signed with a certificate signed by the specified PCA certificate {C}$DenyPolicyRules = Get-CIPolicy -FilePath $DenialPolicyFilePath $DenyPolicyRules | Where-Object { $_.TypeId -eq 'FileAttrib' } | ForEach-Object { # For some reason, the docs for Edit-CIPolicyRule say not to use it... Edit-CIPolicyRule -FilePath $DenialPolicyFilePath -Id $_.Id -Version '99.0.0.0' } # The remaining portion is optional. They are here to demonstrate # policy merging with a reference policy and deployment. $ReferencePolicyFilePath = 'FinalPolicy.xml' $MergedPolicyFilePath = 'Merged.xml' $DeployedPolicyPath = 'C:\DGPolicyFiles\SIPolicy.bin' #> # Extract just the file rules from the denial policy. We do this because I don't want to merge # and possibly overwrite any policy rules from the reference policy. $Rules = Get-CIPolicy -FilePath $DenialPolicyFilePath Merge-CIPolicy -OutputFilePath $MergedPolicyFilePath -PolicyPaths $ReferencePolicyFilePath -Rules $Rules #> # Deploy the new policy and reboot. ConvertFrom-CIPolicy -XmlFilePath $MergedPolicyFilePath -BinaryFilePath $DeployedPolicyPath #>
在上面这段代码中,我生成了一个新的策略,并且指定了恶意代码的安装路径。在真实的攻击场景中,这些代码可能会存在于任何一个目录中,所以你可以在设备上生成这种新型的拒绝策略来阻止恶意代码的执行。接下来,我对该目录进行了扫描。在这一步中,你需要过滤出你所希望阻止的那部分特定代码,然后将拒绝策略和引用策略进行整合,最后重新部署新生成的策略。当你部署完成之后,你需要测试新的策略是否有效。为了进行验证,你需要确保完成了下列配置工作:
1. 确保已经将目标代码的x86版本和x64版本都屏蔽了。
2. 至少要屏蔽目标代码的两种版本或两种架构。
比如说,为了验证已签名的cdb.exe是否还会得到执行,你需要确保32位和64位的cdb.exe版本都已经被添加到你的拒绝规则中了。
大家可能已经发现了,为了防止这种类型的攻击,我们必须修改安全策略,并且向XML文件中手动添加目标代码的特定版本编号。所以在下一版本的Device Guard中,微软将允许用户通过指定一个通配符来为特定代码的所有版本设定拒绝规则。与此同时,这种机制貌似是一劳永逸的。因为新的绕过方法会不断涌现,而你就可以利用这种简单的处理流程来向Device Guard的代码完整性策略中增加相应的拒绝规则。
目前,我已经对这种缓解方式进行了大量的测试,从测试结果来看,我认为这种缓解方案不仅有效,而且实现起来也并不困难。尽管如此,但我还是希望各位安全研究人员们能够从我的理论中找出漏洞。如果你能够绕过我的缓解方案,那么请你一定要告诉我。
在此,我基于上述代码生成了一份策略文件。具体代码如下所示:
10.0.0.0 {A244370E-44C9-4C06-B551-F6016E563076} {2E07F7E4-194C-4D20-B7C9-6F44A6C5A234} Enabled:Unsigned System Integrity Policy Enabled:Audit Mode Enabled:Advanced Boot Options Menu Required:Enforce Store Applications Enabled:UMCI