首页
社区
课程
招聘
[原创]AppDomainManager 注入:从GAC 利用到无文件加载的多种实现
发表于: 1小时前 69

[原创]AppDomainManager 注入:从GAC 利用到无文件加载的多种实现

1小时前
69

1. 原理

过去的注入技术(如跨进程注入)依赖底层的 Windows API(如 VirtualAllocEx、WriteProcessMemory、CreateRemoteThread),这些 API 目前已被 EDR和杀毒软件严密监控。传统的 dll 劫持则需要依靠文件路径、容易被 DLL 签名校验发现 ,也容易存在重名文件导致劫持失败。

AppDomainManager 注入则避免使用敏感 API, 转向利用 .NET 原生框架(如运行时)的合法功能,而且通过设置全局环境变量,可实现所有 .NET 程序启动即感染。其核心思路是: 攻击者编写一个恶意的 C# 类,继承自官方的 System.AppDomainManager,然后通过修改配置文件或环境变量,欺骗一个合法的、有签名的 .NET 程序在启动时将这个恶意类作为其“AppDomainManager”加载。

当编写并编译一个 .NET 程序(如 C# 或 VB.NET)时,它并不会直接变成计算机 CPU 能听懂的机器码(0101),而是变成一种中间语言(MSIL/CIL)。.NET 运行时的任务就是负责把这些中间代码翻译成机器码,并全程管理程序运行时的各种需求,特别是内存管理需求。在 .NET 中,为了防止内存泄漏,运行时会自动追踪不再使用的对象并回收内存;而不是像 C++ ,开发者必须手动申请和释放内存。在 Windows 平台上,它最核心的部分被称为 CLR (Common Language Runtime,公共语言运行时)。

AppDomainManager 注入技术是安全专家 Casey Smith 在 2017 年首次提出。与传统的 DLL 劫持(DLL Side-Loading)不同,这利用的是 .NET 自身正常的程序集解析和加载机制,行为更加隐蔽,安全软件很难在不影响正常业务的前提下进行拦截。换句话说,AppDomainManager 注入的核心价值在于利用“受信任的微软签名程序”来加载“不受信任的代码”,属于 LOLBins (Living Off The Land Binaries) 。

AppDomain(应用程序域): 在 .NET 框架中,AppDomain 是进程内的一个轻量级隔离边界。一个操作系统进程(PID)可以包含多个 AppDomain。这种机制使得 .NET 程序可以安全地加载和卸载程序集(Assembly),防止不同的代码段互相干扰。

AppDomainManager: 这是一个官方提供的基类,用于让宿主(Host)自定义如何创建和管理新的 AppDomain。当一个 .NET 应用程序启动并初始化它的第一个 AppDomain 时,它会检查是否配置了自定义的 AppDomainManager。如果有,.NET 运行时会优先加载并执行这个管理器代码

AppDomainManager 不依赖于文件缺失,也不需要复杂的远程线程注入。它利用的是 CLR 加载机制中的“配置优先”原则,代码运行环境极其“纯净”且受信任。AppDomainManager 允许代码在目标程序的 Main 函数执行之前就获得控制权。这意味着攻击者可以在安全软件还没来得及挂钩(Hook)或初始化监控之前,就已经完成了反调试、反沙箱或内存补丁等恶意操作。

以下测试均基于.NET Framework 4.x,虽然 .NET Core 及更高版本(.NET 5/6/7/8)也支持 AppDomainManager,但其加载机制(如 app.config 的处理方式)和 GAC 的概念已经发生了巨大变化。

2. 修改配置文件实现注入

2.1 测试 dll

使用 C# 创建一个类库(DLL),包含一个继承自 AppDomainManager 的类,并重写 InitializeNewDomain 方法。在该方法中放入恶意代码。具体编译过程:新建一个文件 MyPayload.cs,使用记事本输入以下 C# 代码。这段代码继承了 AppDomainManager,重写了InitializeNewDomain(),实现在.NET 程序初始化时启动计算器。

图片描述

打开命令提示符(cmd),进入 MyPayload.cs 文件所在目录,利用系统自带的 .NET 编译器 (csc.exe) 将源码编译为 AppVStreamingUX_Multi_User.dll。运行以下命令:

图片描述

2.2 目标 .NET 应用程序

选择 .NET 框架自带的合法且有微软签名的程序 dfsvc.exe 作为启动测试 dll 的目标程序,将其复制到AppVStreamingUX_Multi_User.dll 所在目录,并重命名为AppVStreamingUX.exe,稍微伪装一下

图片描述

dfsvc.exe (Deployment File Service) 是 Microsoft .NET Framework 的一部分,主要负责 ClickOnce 应用程序的安装、更新和管理。它是合法的 Windows 系统进程,通常随 .NET Framework 运行。虽然它是正常的部署服务,但也可能被滥用为执行恶意代码的白名单程序。

图片描述

不过,安全软件可能会校验文件名与签名中的原始文件名是否匹配,如果不匹配,反而会增加风险。这里仅仅做个测试,就先不管这么多了。

2.3 .config 配置文件

在目标 .NET 程序AppVStreamingUX.exe(原名为dfsvc.exe )同目录下创建一个名为 (目标程序名).exe.config 的文件。在配置文件中指定恶意 DLL (AppVStreamingUX_Multi_User.dll)和类名(MyAppDomainManager)。

当受害者或攻击者运行该合法 exe( AppVStreamingUX.exe)时,.NET 运行时会自动读取 .config 文件(AppVStreamingUX.exe.config),并静默加载 AppVStreamingUX_Multi_User.dll 执行其中的恶意代码(弹出计算器)。

将以下 XML 内容写入该文件:

图片描述

2.4 运行效果

点击运行程序AppVStreamingUX.exe,计算器顺利弹出 图片描述

通过火绒剑可以观察到AppVStreamingUX.exe 进程创建后,很快就打开并读取了配置文件AppVStreamingUX.exe.config 图片描述

接着又打开了文件 AppVStreamingUX_Multi_User.dll 图片描述

可以清楚地看到,这个AppVStreamingUX.exe 加载了 AppVStreamingUX_Multi_User.dll。 图片描述这次测试成功实现了利用配置文件的 AppDomainManager 注入。

3. 修改环境变量实现注入

3.1 全局环境变量设置

编译新的 dll

图片描述

运行命令,将以上代码编译为 GlobalManager.dll

图片描述 图片描述

管理员权限打开 Powershell,依次执行以下命令 图片描述 图片描述

接着把 dll 文件放到文件夹C:\test,然后设置环境变量 图片描述

点击运行AppVStreamingUX.exe,创建了 success.txt 文档,说明注入成功 图片描述

注意,.NET Core 和 .NET 5+ 之后,环境变量的前缀从 COMPLUS_ 变更为 DOTNET_(例如 DOTNET_AppDomainManagerAsm)

3.2 环境变量设置+路径加载

将GlobalManager.dll放进 .NET Framework 的核心目录里,例如C:\Windows\Microsoft.NET\Framework64\v4.0.30319,该路径下有几个常用的.NET 程序,例如MSBuild 和RegAsm 图片描述

和之前的环境变量设置方法有点不同,由于 DLL 就在路径里,不再需要版本号、Token 等复杂信息。

图片描述

设置好环境变量之后,可以看一下注册表HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment 验证一下 图片描述

环境变量的设置确实生效了,然后点击RegAsm.exe,通过火绒剑看到RegAsm.exe 打开了GlobalManager.dll,并执行了这个 dll 创建了success.txt 文档 图片描述

这与 DLL 劫持不同的是,这个是“一对多”的关系,即不仅是RegAsm.exe,此文件夹下的其他很多程序在运行时都会加载这个 dll。

4. 利用 GAC 实现注入

利用 GAC 实现 AppDomainManager 注入,是指通过将具备强签名的恶意 DLL 植入受信任的系统GAC(全局程序集缓存),并配置特定的环境变量,可迫使 CLR 在启动任意 .NET 进程时,优先从 GAC 加载该恶意类并实例化为 AppDomainManager。此过程无需篡改合法宿主文件,即可在主程序代码执行前静默劫持控制流,实现高隐蔽、全局性的系统级权限驻留。

全局程序集缓存 (Global Assembly Cache, GAC) 是 .NET Framework 中的一个专用、系统范围的中央存储库,用于存储被多个应用共享的“强名称程序集”。它能解决版本冲突,节省磁盘空间,并支持强名称和数字签名验证。GAC 主要存在于 .NET Framework 环境中(例如 C:\Windows\assembly),在 .NET Core 及后续版本中已被弃用

有分析报告显示,在国内比较猖獗的银狐木马曾在攻击中使用这种技术。

4.1 编译生成 DLL

打开 Visual Studio,新建一个 “类库 (.NET Framework)”,注意.NET 的版本,要与虚拟机中的.NET 版本保持一致,否则可能无法成功实现注入。

图片描述

  • 右键点击项目名 GacInjectTest -> 属性 (Properties)
  • 在左侧找到“签名 (Signing)” 选项卡,勾选 “为程序集签名”
  • 在下拉框中选择 “<新建...>”
  • 随便起个密钥文件名,比如 key,取消勾选“使用密码保护我的密钥文件”,点击确定。

然后编译生成文件GacInjectTest.dll 图片描述

GAC(全局程序集缓存)里面的 DLL 会被全系统所有的 .NET 程序共享调用,权限极高。在 Visual Studio 里做的这个“新建签名”操作,本质上就是利用非对称加密算法(RSA),给 DLL 盖上了一个独一无二的“加密防伪钢印”,这个在 .NET 术语中就叫 “强名称(Strong Name)”。

全世界可能有成千上万个程序员都写了一个名叫 Utils.dll 的文件。如果都塞进 GAC 里,系统就彻底乱套了。给程序集签名后,系统识别这个 DLL 就不再只看名字了,而是看它的 “四元组身份”:文件名 + 版本号 + 语言文化 + 公钥标记 (PublicKeyToken)

4.2 将 dll 注册到 GAC

将 dll 注册到系统的 GAC 中,方便所有的 .NET 程序共享调用,可以通过在 Powershell 执行以下命令实现注册:

图片描述

注册完成之后,执行以下命令获取一下已注册 dll 的“四元组身份”:文件名 + 版本号 + 语言文化 + 公钥标记 (PublicKeyToken),方便后续的环境变量设置。

$assembly = [System.Reflection.Assembly]::ReflectionOnlyLoadFrom("C:\Demo\GacInjectTest.dll")
$assembly.FullName

获取的文件“四元组身份”:GacInjectTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9416298015f19395

在 .NET 程序集的强名称中,Culture 指的是该程序集所包含的区域性(语言和区域)资源,它决定了程序集是主程序集还是附属(卫星)程序集。

  1. Culture=neutral :neutral(非特定区域) 表示该程序集不包含任何特定语言或区域的资源,它属于默认程序集(主程序集)。这类程序集通常包含代码逻辑和默认语言(通常是英语)的资源(或资源作为后备)。
  2. 如果一个程序集的 Culture 不是 neutral,比如 Culture=zh-CN 或 Culture=fr-FR,它就是一个卫星程序集,里面只包含语言特定的资源(如翻译后的字符串、图片),不包含代码。它会被放在 GAC 的特定子目录(或应用程序的 zh-CN\ 等子文件夹)中,由 .NET 根据当前线程的 CurrentUICulture 自动加载。

检查看看 dll 在 GAC 的注册是否成功,看一下文件夹路径 C:\Windows\Microsoft.NET\assembly\GAC_MSIL\,找到了以 dll 名称命名的文件夹,这个文件夹会有一个名字很长的子文件夹,格式是这样的:v4.0_1.0.0.0__9416298015f19395 (包含了版本号和PublicKeyToken),再进入这个长名字文件夹,会看到GacInjectTest.dll,说明注册成功。

图片描述

需要注意的是,访问和修改 GAC 通常需要管理员权限。

4.3 设置环境变量

# 开启开发者模式
[Environment]::SetEnvironmentVariable("COMPlus_DevelopmentMode", "1", "Machine")  

# 定义 DLL 的信息 
$asmName = "GacInjectTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9416298015f19395"
$typeName = "GacInjectTest.MyManager"

# 设置系统级环境变量
[Environment]::SetEnvironmentVariable("APPDOMAIN_MANAGER_ASM", $asmName, "Machine")
[Environment]::SetEnvironmentVariable("APPDOMAIN_MANAGER_TYPE", $typeName, "Machine")
[Environment]::SetEnvironmentVariable("DEVPATH", "C:\Demo", "Machine")

4.4 运行效果

点击运行 x64 的 RegAsm.exe,计算器顺利弹出。注意此时RegAsm.exe 都不在同一个文件夹,这说明通过利用 GAC ,AppDomainManager 注入无需依赖文件路径了,任意文件路径的.NET 程序运行时都会优先加载这个 dll。

图片描述

5. AppDomainManager 注入结合无文件的内存执行

在稍微复杂一点的攻击中,攻击者极其讨厌在磁盘上留下多余的文件(比如 .config 和 dll 文件太显眼了),这容易被安全软件发现。AppDomainManager 注入结合无文件的内存执行要隐蔽得多,最终的恶意载荷可以在内存中执行,常见的攻击步骤大致如下:

  • 网络拉取:通过 HTTP 请求将远程服务器上的可执行文件(.exe 或 .dll)作为纯字节流(Byte Array)下载到内存中。
  • 反射加载:利用 .NET 的反射(Reflection)机制,直接将内存中的字节流解析并加载为当前进程中的一个程序集(Assembly)。
  • 动态调用:找到该程序集的入口点(通常是 Main 函数),并直接在当前进程的内存空间中执行它。

接下来就逐步复现一下这个“AppDomainManager 注入 + 无文件的内存执行”

5.1 网络设置

查看一下主机虚拟网卡VMnet1 的 IP 地址为 192.168.148.1,将虚拟机的网络适配器设置为“仅主机”(Host-Only),关闭物理机的网络防火墙,发现虚拟机可以 ping 通物理机了,就可以继续接下来的试验了 。

图片描述

5.2 准备 Payload.exe

创建一个文本文件 Payload.cs,写入以下代码,编译生成成Payload.exe

图片描述

5.3 开启物理机 80 端口

在物理机Payload.exe 所在文件夹内打开命令行(CMD/PowerShell),执行命令python -m http.server 80,启动 Python 简易服务器

图片描述

在虚拟机浏览器的地址栏中输入192.168.148.1/Payload.exe,能下载Payload.exe,说明这个简易的服务器能下载成功 图片描述

5.4 准备注入的 dll

图片描述

5.4.1 .NET 反射(Reflection)机制

反射是.NET中的一项技术,允许程序在运行时动态地访问和操作程序集、类型和对象的信息。通过反射,能够在编译时进行动态加载程序集、创建对象实例、调用对象方法、访问属性和字段等操作。

5.4.2 DownloadData()

这个函数与 DownloadFile 不同。DownloadFile 会强制要求提供一个本地磁盘路径,而 DownloadData 会将远程文件下载并直接返回为一个 字节数组(byte[])。下载的恶意程序 Payload.exe 只是内存中的一串二进制 01 码,没有触发“创建文件”的行为,这样也可以直接在内存中通过Assembly.Load加载

5.4.3 Load()

通常情况下,程序加载 DLL 或 EXE 是通过磁盘路径(如 LoadLibrary("C:\test.dll"))。Assembly.Load 允许 CLR(公共语言运行时)把DownloadData 下载到内存中的字节数组byte[] 解析为一个可执行的 .NET Assembly 对象,挂载到当前的应用程序域中。

(Assembly.Load(byte[]) 虽然不产生文件,但会在 ETW(Windows 事件跟踪)中留下痕迹。现在的 EDR 已经可以通过监控 CLR 内部事件,例如 ETW 事件中的 AssemblyLoad,来捕捉到这种内存加载行为)

5.4.4 执行

  • EntryPoint:自动在内存中寻找该Assembly (程序集)的 Main 或 WinMain 函数入口。
  • Invoke:正式下令 CPU 开始执行这段内存中的代码。

这两个函数意味着,不需要手动去寻找复杂的内存偏移地址,.NET 反射机制完成了所有定位工作,使得攻击载荷(Payload)的执行非常稳定。

5.5 注入实现

将 InjectObj.dll 拷贝到虚拟机,例如 C:\Demo\ 文件夹,在虚拟机设置环境变量 图片描述

还是点击AppVStreamingUX.exe,Payload.exe 成功执行 图片描述

WireShark 可以看到点击AppVStreamingUX.exe 后,与地址 192.168.18.1 产生的链接和下载文件Payload.exe 的数据包 图片描述

当然了,魔高一尺,道高一丈。现在包括 EDR 在内的安全软件对AppDomainManager 注入的各种操作也提高了警惕。

DEVPATH 方式需要修改机器范围的 .config 文件(machine.config)或者设置 developmentMode。在目前 Windows 安全策略下,修改这些全局配置通常需要高权限,容易被监控。APPDOMAIN_MANAGER_ASM 和 APPDOMAIN_MANAGER_TYPE 的设置,对 .exe.config 文件的异常写入(尤其是往系统自带的 dfsvc.exe 等白名单程序目录下写配置),AppVStreamingUX.exe 加载一个不在常规路径下的 DLL(如 C:\test\GlobalManager.dll),这都很有可能触发安全软件告警。

6. 参考链接

Use AppDomainManager to maintain persistence

全局程序集缓存 - .NET Framework

360数字安全集团-数字安全的领导者

《.NET安全攻防指南(上册)》第二章 ,作者: 李寅、莫书棋,机械工业出版社


[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 1小时前 被ZyOrca编辑 ,原因: 调整排版,修正错误
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回