此博文的附带代码可在此处找到。https://github.com/MayerDaniel/profiler-lateral-movement
介绍
我花了很多空闲时间修改 Unity 游戏。由于 Unity 是用 C# 编写的,因此与编译为非托管代码的游戏相比,这些游戏非常容易操作。这使得它成为一个完美的业余项目,可以轻松上手并轻松完成。
随着我对 C# 游戏的修改越来越深入,我意识到挂钩函数实际上比非托管程序稍微复杂一些,这是违反直觉的,因为几乎所有其他事情都容易得多。
对于非托管代码,挂钩函数相对简单。基本步骤如下:
-
分配一些内存来存放函数调用时要运行的代码,并在其中写入指令
-
覆盖原始函数指令的开头以跳转到新代码
-
处理所有必要的细节,以确保程序的执行回到原来的函数,并且堆栈在最后不会变得混乱
使用 .NET 和 Mono 时,事情就没那么简单了。.NET 程序集的功能由二进制指令集(称为通用中间语言 (CIL))组成,该指令集在运行时由公共语言运行时 (CLR) 即时 (JIT) 编译为机器指令。
尝试挂接托管代码的主要问题是,当你将目标函数注入到要挂接函数的进程时,目标函数可能已经被 JIT 处理,如果是这样,CLR 会缓存它被翻译成的 x86 指令。如果你修改了 CIL 字节码并且再次调用该函数,CLR 可能只会执行在修改之前编译的缓存的 x86 指令。除了这个问题之外,还有无数的小情况让这个问题变得非常令人头疼。(虽然实际上,这是一个已解决的问题。已经有许多出色的解决方案和框架可以帮助开发人员轻松实现这一点,例如Harmony和MonoMod,但我仍然很好奇想了解更多相关信息。)
.NET 分析器
在谷歌搜索挂钩解决方案时,我偶然发现了微软的 .NET 分析 API,虽然它对我的改装需求没有多大帮助,但它似乎确实有一些方便的红队原语!它旨在通过在加载到给定 .NET 进程中的非托管 COM 服务器 DLL 中实现回调接口来允许对 .NET 进程进行检测。
然后,当执行期间发生不同事件时,CLR 会从接口调用函数。对于 CLR 中发生的几乎所有事情,您都可以实现一个回调,该回调会在运行时被调用来检查和操纵行为,例如加载程序集和模块时、函数进行 JIT 编译时等等。看看所有这些回调,这还不是全部!
.NET 分析 API 回调
有关这些分析器工作原理的更多基础知识,我建议观看 Pavel Yosifovich 的演讲。这是我发现的最有价值的资源:
https://youtu.be/TqS4OEWn6hQ
.NET Profiler 的攻击价值
执行和坚持:
执行时,给定进程的 CLR 将检查环境变量中是否存在三个特定变量,这些变量会导致加载分析 DLL:
特定于分析器的环境变量
-
COR_ENABLE_PROFILING — 如果设置为 1,则此标志将为给定进程启用分析,这意味着分析器 DLL 将被加载到进程中。
-
COR_PROFILER — 这是将传递给分析器 DLL 以查看它是否是正确的 COM 服务器的 CLSID。分析器 DLL 可以选择不检查这一点,并且无论此 CLSID 是什么都会加载。
-
COR_PROFILER_PATH — 将加载的分析器 DLL 的路径。
如果所有这些变量都存在、启用了分析功能并且 DLL 存在于磁盘上,则分析器 DLL 将在执行开始时加载到进程中。这为我们提供了一个非常好的代码执行原语,可以将 DLL 加载到任意 .NET 进程中。
这已经被记录了一段时间,并且被野外威胁行为者观察到使用。我偶然发现了Bohops 的这篇博客,其中详细介绍了 Windows 中 .NET 分析基础结构的其他有趣滥用情况。它引用了Casey Smith 在 2017 年发表的一篇博客,详细介绍了以这种方式加载 DLL,MITRE 有针对此的技术以及一些野外示例。
由于环境变量可以在系统范围内设置,这意味着它也可以作为一种持久性形式。每当 .NET 进程执行时,它都会加载指定的 DLL。
可以滥用此漏洞的最小可行分析器是一个“假”COM 服务器 DLL,它导出函数DllGetClassObject,该函数用于检查 COM 服务器 DLL 的 CLSID。不过如上所述,这里不需要实际实现检查逻辑,而是可以执行任意代码:
最小可行的“假”分析器
在.NET 进程中执行“假”分析器
横向移动:
我曾与 Lee Chagolla-Christensen ( @tifkin ) 讨论过如何在远程计算机上设置这些环境变量,以便通过 UNC 路径加载 DLL,他告诉我Win32_ProcessStartup WMI 类允许为特定进程设置环境变量,这意味着可以通过Win32_Process Create调用来滥用此功能,以远程执行 .NET 进程并加载 .NET 分析器 DLL!谢谢 Lee!
因此,我着手创建 BOF 和 Payload,以便更轻松地使用它。结果在这里。
我修改了Yaxser 的 WMI 横向移动 BOF,使其包含一个 Win32_ProcessStartup 类,其中定义了适当的环境变量和一个用户定义的 DLL 路径,以便通过 .NET 分析器实现横向移动。
添加环境变量以启用 WMI 横向移动
此外,我修改了 Pavel Yosifovich 的示例 .NET 分析器,使其成为更好的有效载荷。我利用ired.team 的这个教程将 shellcode 有效载荷存储为可以热交换的资源,并使用函数ICorProfilerInfo2::SetEnterLeaveFunctionHooks2在所有 JITed 函数上设置一个进入钩子。钩子将从资源中加载并执行 shellcode,本质上是执行进程挖空,因为如果有效载荷是类似 Cobalt Strike 信标的东西,钩子函数的正常功能将无限期停止。
在初始化期间设置钩子
将 shellcode 执行添加到函数输入钩子中
在同一个文件CoreProfiler.cpp中,您可以看到所有其他方便的回调,它们可用于执行原语或更有趣的用例。一个使用 .NET 分析器进行规避的简洁示例是 Omer Yair 的InvisiShell,它监视 PowerShell 进程中的程序集加载,然后修补函数以禁用 AMSI。我相信这里有很多沃土,可以进一步研究 CLR 公开的所有回调。
综合起来
当我们一起使用有效载荷和 BOF 时,你会得到类似这样的横向移动:
.NET Profiler BOF 执行
你可能会想:“嘿,丹!你之前不是说过你想从 UNC 路径加载有效载荷吗?”是的,我说过,记忆力很好。遗憾的是,由于我们使用 WMI,我们遇到了双跳问题,这意味着我们在远程计算机上执行的进程无法通过远程文件共享进行身份验证,从而无法通过 UNC 路径提取有效载荷。不过没关系,因为 DLL 可以从 WebDAV 服务器加载:
.NET Profiler BOF 执行,采用 WebDAV
您甚至可以利用wsgidav将其设置为通过您正在执行 BOF 的信标。首先执行此命令以在您的工作站上本地托管您的服务器:
wsgidav --host=0.0.0.0 --port=80 --root=/payload/folder --auth=anonymous
然后在信标上启动反向端口转发:
rportfwd 80 localhost 80
现在您可以执行.NET 进程,自动拉取您的有效负载并执行。
结论
我发现 .NET Profiler 的这个“功能”非常巧妙,尽管有点笨重。您会发现有效载荷纯粹是为了演示目的。没有尝试使其具有规避性,因此防御者可能会在构建后吃掉它。抱歉!
我希望这能让人们对 .NET 分析器可以做的所有酷事更加好奇,我相信还有其他方法可以远程设置环境变量,使其更加有用。我简要研究了setx和其他通过远程注册表设置它们的方法,但似乎更改直到重新启动后才生效。我敢打赌一定有办法让它发挥作用!
再次感谢 Lee、Pavel、Yaxser 和 Mantvydas 之前的所有研究,因为有效载荷和 BOF 实际上只是你们工作的拼贴画。
https://posts.specterops.io/lateral-movement-with-the-net-profiler-8772c86f9523
原文始发于微信公众号(Ots安全):使用 .NET Profiler 进行横向移动
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论