本文我们会介绍如何通过使用公共语言运行时应用程序域管理器执行任意代码并维护对Microsoft Windows系统的访问。
工具、安全性评估和测试
在安全性评估中,获得对组织内部网络的初始访问是一项非常具有挑战性的任务,需要花费很多的时间和精力。
为了解决这个问题,可以使用多种持久性技术。这些技术可以确保失去访问权只是暂时的,这意味着你可以在特定的时间或执行特定的操作之后恢复访问权限。本文解释了如何通过使用公共语言运行时应用程序域管理器执行任意代码并维护对Microsoft Windows系统的访问。
在这篇文章中,我们将具体介绍:
1.基本.Net知识(例如公共语言运行时,全局程序集缓存);
2.PowerShell脚本的基础知识;
3.C#编程的基础知识
.Net概述
.Net是Microsoft于2002年2月发布的开发平台,支持三种语言:C#,F#和Visual Basic .Net(VB.NET)。
C#1.0于2002年1月首次发布,现在是最流行和最常用的.Net语言之一。这是一种高级的面向对象编程语言,主要用于开发胖客户端应用程序,Web应用程序和移动应用程序(Xamarin)。
F#1.x于2005年5月发布,是一种用于科学和数据分析的强函数式语言。
最后说说VB.NET,其第一个版本于2002年发布,基于Visual Basic 7.0,用于开发桌面应用程序。
.Net之所以变得如此受欢迎的原因是因为它提供了许多有趣的功能,例如:
1.自动内存管理功能:垃圾收集(GC)自动解除分配.Net应用程序使用的已分配内存,另外GC还确保只能访问分配的内存,从而确保内存安全。
2.使用非托管资源功能:不由.Net维护的资源称为非托管资源,.Net应用程序可以通过托管类型(例如MemoryStream、CryptoStream、UnmanagedMemoryStream等)使用这些非托管资源。
3.类型安全功能:类型的每个对象都定义了具有不同可访问级别的方法和属性,例如private、public、protected、internal、protected internal、private internal。.Net在尝试访问未定义或不可访问的方法或属性时,通过抛出运行时或编译时异常来确保类型安全。
4. delegate函数和“Lambda 表达式”: 委托(delegate)是一种类型安全的函数指针,用于通用语言运行库(CLI)。在C#中,delegate是一种class,包装了一个或多个函数指针及绑定的类实例。“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数,Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。
5. Java泛型(Generics): 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。Generics有“类的,属性的”之意,在Java中代表泛型,泛型作为一种安全机制而产生。
类可以利用泛型来允许程序员在构造类时指定类型,例如,实现泛型数据结构的类型是List,它可用于构造字符串List或整数列表的强类型List。
6.异步编程:通过C#中的async和await关键字,编写I / O绑定和CPU绑定的异步代码相当容易,这有助于改善应用程序的执行时间和性能。
7.语言集成查询(LINQ):语言集成查询是微软公司提供的一项新技术。它能够将查询功能直接引入到.NET Framework 3.5所支持的编程语言(如C#、Visual Basic等)中,使开发人员能够在应用程序代码中形成基于集合的查询,而不必使用单独的查询语言。查询操作可以通过编程语言自身来传达,而不是以字符串嵌入到应用程序代码中。LINQ的语法和语义和SQL很像,具有许多相同的优势。要查询的数据的内部结构发送改变后,不必修改查询代码。注意,虽然LINQ和SQL看起来很像,但LINQ更加灵活,而且能处理范围更大的逻辑数据结构。例如,LINQ能处理以层次化的方式组织的数据,例如XML文档中的数据。
8.互操作性:操作系统提供了应用程序编程接口(API),可以用来访问操作系统管理的函数和信息的硬件。.Net提供了多种方法来调用系统的API。此外,在Windows上,互操作性允许使用组件对象模型(COM)组件。
7.不安全的代码:为了操作内存块(如API调用、指针、算法),访问本机内存是必要的,可以在c#中使用不安全的关键字来执行不安全的代码。
虽然多年来,.Net诞生了不同的实现方式,但就本质来讲,.Net只有四种主要的实现方式:
· 1..Net框架:这是.Net在2002年的最初实现方式,此实现仅适用于Windows系统,用于开发Web,Windows,Windows Phone,Windows Server和Azure的应用程序;
· 2..Net内核:这是.Net的开源和跨平台实现,主要用于构建设备、云和物联网(IoT)应用程序;
· 3. Mono:Mono是基于.Net Framework的.Net的开源和跨平台实现,该框架最初是为在Unix系统上使用.Net Framework应用程序而开发的。现在,Mono主要由Xamarin用于Android,iOS,tvOS和watchOS应用程序。Xamarin作为一个跨平台开发框架始创于2011年,旨在使移动开发变得难以置信地迅捷和简单。Xamarin的产品简化了针对多种平台的应用开发,包括iOS、Android、Windows Phone和Mac App。Xamarin由许多著名的开源社区开发者创立和参与,而且也是Mono项目的主导者——C#与?NET框架的开源、跨平台实现。
· 4.通用Windows平台(UWP):最后一种实现通常用于开发现代物联网、移动设备、平板电脑、平板手机或Xbox应用程序。
所有的实现都有两个共同点:.Net标准,以及一个或多个运行时。.Net标准是每个.Net基类库实现的一组API。此API可确保任何.Net实现的一致性,从而允许跨实现库。这个API确保了任何.Net实现的一致性,因此允许跨实现库。不同的运行时(托管应用程序的执行环境)包括:.Net Framework的公共语言运行时(CLR); .Net Core的内核CLR;用于UWP的.Net Native和用于基于Mono的应用程序的Mono运行时。
公共语言运行时
公共语言运行时(Common Language Runtime,CLR)是Microsoft的公共语中言基础结构(CLI)的一个商业实现,CLI是一种国际标准,用于创建语言和库在其中无缝协同工作的执行和开发环境基础。.NET Framework 提供了一个称为公共语言运行时的运行环境,它运行代码并提供使开发过程更轻松的服务。有了公共语言运行时,就可以很容易的设计出对象能够跨语言交互的组件和应用程序。也就是说,用不同语言编写的对象可以互相通信,并且它们的行为可以紧密集成。
元数据描述应用程序使用的类型、成员和引用,CLR在运行时使用元数据来确保应用程序拥有所需的一切(例如加载程序集、分配内存、生成本机代码),并设置运行时上下文边界。
公共语言运行时遵循公共语言架构的标准,能够使C++、C#、Visual Basic、以及JScript等多种语言深度集成。 CLR从某种意义上理解相当于Java中的Java虚拟机(JVM),而MSIL相当于Java中的字节码(.class文件)。MSIL总是及时编译(称为JIT编译)为相应平台的机器代码,这一点与Java也很相似。
编译将源代码翻译为Micrisoft 中间语言(MSIL)并生成所需的元数据。MSIL是一组可以有效地转换为本机代码且独立于CPU的指令。它包括用于加载、存储和初始化对象以及对对象调用方法的指令,还包括用于算术和逻辑运算、控制流、直接内存存取、异常处理和其他操作的指令。元数据描述代码中的类型,包括每种类型的定义、每种类型的成员的签名、代码引用的成员和运行时在执行时使用的其他数据。
在执行时,实时(JIT)编译器将MSIL翻译为本机代码。在此编译过程中,代码必须通过验证过程,该过程检查MSIL和元数据以查看是否可以将代码确定为类型安全。
以上所述可用下图形象地表示:
通过使用中间语言反汇编程序(ILDASM)二进制文件,可以获取.exe,.dll,.obj,.lib和.winmd文件的MSIL代码。请注意,此二进制文件可从Windows软件开发工具包(SDK)获得。
例如,可以将以下C#编译到名为Tool.exe的文件中,然后使用Ildasm.exe进行分析:
using System; namespace Tools { class Program { static void Main(string[] args) { Console.WriteLine("Hook my Common Language Runtime"); } } }
下面的屏幕截图显示了通过Ildasm.exe从上面的C#编译代码中提取的Main方法的MSIL代码。
应该注意的是,任何使用CLR的. net应用程序都被认为是托管应用程序。CLR可以使用的代码也被认为是托管代码。托管应用程序的主要优点是它们可以从CLR的服务和功能中获益,例如:
1.跨语言集成;
2.跨语言异常处理;
3.增强的安全性;
4.版本控制和部署支持
5.组件交互的简化模型
6.调试和分析服务
在上面的功能列表中,最有趣的功能之一是跨语言集成。实际上,任何托管应用程序都可以加载和使用任何托管库,而不管用于开发库的语言是什么。
除了上述问题,还有一个关键的问题,就是Windows如何使用CLR执行应用程序?事实上,Windows使用运行时主机,如Microsoft Internet Explorer、APS.Net或Windows shell来执行托管应用程序,这些运行时主机是将CLR的DLL加载到它们的进程空间的进程。
CLR使用的主要DLL是'mscorlib.dll',它代表多语言标准公共对象运行时库(MS COR LIB)。但是,CLR使用了许多其他DLL,它们都以'mscor'为前缀。因此,可以通过列出以“mscor”开头的所有DLL名称来列出CLR在系统上使用的所有DLL。
以下PowerShell cmdlet将通过‘C:\Windows\Microsoft.Net\Framework’ 文件夹,该文件夹包含.Net Framework DLL并使用‘mscor*’过滤名称。
请注意; .Net Framework 4.0及更高版本中的主要DLL是'clr.dll'。
此外,运行时主机不仅加载CLR的DLL。启动运行时主机时,将创建一个默认应用程序域,然后在默认应用程序域中加载并执行托管代码。
以上所述可用下图形象地表示:
如上图所示,运行时主机创建一个应用程序域,加载托管代码,然后在应用程序域中执行JIT编译器编译的托管代码。
应用程序域和应用程序域管理器
应用程序域是安全边界,执行托管应用程序时,运行时主机将自动创建一个默认的应用程序域。多个应用程序域可以驻留在同一运行时主机中,此安全边界提供了应用程序域之间的隔离,这意味着应用程序域A无法直接访问应用程序域B的代码或资源。
同一运行时主机中的应用程序域之间的隔离提供了以下几个好处:
1.如果一个应用程序出现故障,则不会影响其他应用程序域;
2.应用程序域可以单独停止/重新启动,而无需停止/重新启动运行时主机;
3.每个应用程序域的代码都有一组独特授予的权限,这些权限可能与其他应用程序域不同;
4.每个应用程序域都有自己的与域无关的程序集列表,可以根据需要加载或卸载程序集;
应该注意,从一个应用程序域访问代码或资源是可能的。但是,需要一个代理。
每个应用程序域都有一种类型的应用程序域管理器,而一个应用程序域管理器可以有X个应用程序域。应用程序域管理器是System.AppDomainManager类,负责创建和自定义新创建的应用程序域。总的来说,这个类公开了4个属性和6个方法,可以覆盖这些属性和方法来修改默认的CLR行为。
以下屏幕截图显示了System.AppDomainManager类的属性和方法。
可以重写标记为virtual的属性和方法,并因此在自定义程序集中实现,以便在执行任何托管代码之前修改新的应用程序域。
下表描述了可以根据Microsoft文档重写的属性和方法:
1.ApplicationActivator:属性,获取处理域的外接程序和基于清单的应用程序的激活的应用程序激活器;
2.HostSecurityManager:属性,获取参与应用程序域安全性决策的主机安全性管理器;
3.HostExecutionContextManager:属性,获取管理执行上下文流程的主机执行上下文管理器;
4.EntryAssembly:属性,获取应用程序的项目程序集;
5.CreateDomainHelper:方法,提供用于创建应用程序域的帮助程序方法;
6.CheckSecuritySettings:方法,指示应用程序域中是否允许指定的操作;
7. CreateDomain:方法,返回新的或现有的应用程序域;
8.InitializeNewDomain:方法,初始化新的应用程序域。
此外,因为System.AppDomainManager类继承自System.MarshalByRefObject,也可以重写以下方法:
1.ApplicationActivator:属性,获取处理域的外接程序和基于清单的应用程序的激活的应用程序激活器。
2.CreateObjRef:方法,创建一个对象,该对象包含生成用于与远程对象通信的代理所需的所有相关信息;
3.InitializeLifetimeService:方法,获取生命周期服务对象以控制此实例的生存期策略;
现在,虽然我们已经知道可以重写的方法和属性,但是,目前尚不清楚如何更改CLR使用的默认应用程序域管理器。
要解决此问题,必须设置“APPDOMAIN_MANAGER_ASM”和“APPDOMAIN_MANAGER_TYPE”环境变量。它们分别表示从System.AppDomainManager继承并覆盖方法和属性的自定义程序集的全名,以及自定义程序集用于实现System.AppDomainManager类的类型名称。
最后,由于应用程序域管理器可以修改CLR的安全性决策,因此自定义程序集需要完全受信任,这就要求必须是强名称程序集,并且必须安装到全局程序集缓存(GAC)中。
本篇文章,我们已经知道可以重写的方法和属性,以及如何更改CLR使用的默认应用程序域管理器。下一篇,我们就来继续说说如何将实现System.AppDomainManager的强名称程序集安装到GAC中,并最终实现使用公共语言运行时获取持久性。