XAML当代码用:NetamLoader的加载链拆解"

admin 2026-10-30 13:54:05 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

XAML 当代码用:NetamLoader 的加载链拆解

原创

赛博57库
赛博57库

赛博57库

2026年10月30日 05:00
广东

去阅读

恶意软件 XAML 当代码用:NetamLoader 的加载链拆解 恶意软件 · 加载链 · AppDomainManager · XAML · AMSI 绕过

本文材料来自 Kaspersky GReAT 2026 年 10 月 29 日的报告《Unknown APT uses NetamLoader to attack a Win10 OS customized for governments in Asia》。原文按\"发现顺序\"写,这篇按\"攻击者怎么把东西送进去、怎么跑起来、跑起来之后干了什么\"重排,并给每一跳补上机制解释。

一、背景:一条告警牵出的链

事情的起点不是发现受害者,而是一条自动告警。

2026 年上半年,Kaspersky 的安全网络(KSN)在例行狩猎里报了一条:某个 .config 文件正在用 AppDomainManager 注入加载任意 .NET 程序集。这个文件被打包在一个 CAB包,放在 Windows 存放系统更新包的目录下:

C:\\Windows\\SoftwareDistribution\\Download\\e4537a1762fcafd2fd96bfc825bad34f9db9a24d

注意这个文件名——40 位十六进制,看着像哈希。更新缓存目录里的包通常有可读的命名或 GUID,一串裸哈希本身就有点不合群。

拿到包之后的第一次分析几乎没结果。这不是分析失误,而是样本特性:CAB 里七个文件,六个是第三方补丁工具 Patch My PC 的正常组件,连被用来做注入的那个 .NET 程序集都是微软签名的。唯一的恶意文件是一个 .resx,而它当时被当成了普通资源文件。

后来重新逐一比对那个 .config 和完整的加载过程,才把链条挖出来。这条链被命名为 NetamLoader,目前没有关联到任何已知 APT。

这个背景值得单独说,因为它决定了两件事:第一,静态扫描看签名是没用的,被滥用的全是合法组件;第二,防守方的入口不在\"抓恶意 PE\",而在\"看谁在什么位置读了什么配置\"。

二、CAB 是怎么进来的

原文在这件事上非常克制,只有一句:

We did not observe how the CAB file was delivered to the victim.

也就是说投递环节没有观测到。这一点必须写清楚,不能替它补一个\"供应链攻击\"的结论。

已知的事实是这些:

事实 说明
CAB 落在 SoftwareDistribution\\Download\\ 这是 Windows 更新(含 WSUS / 第三方补丁)的下载暂存目录
解包后落到 ...\\Download\\Install\\ 同目录下的安装暂存位置
七个文件,只有一个恶意 Resources.resx ;其余是 Patch My PC 的正常组件
用 PatchMyPC-ScriptRunner.exe 启动 参数是 -updatepackage -applicationid=<GUID>

从这四条可以做一个推断(原文没有下这个结论):攻击者没有自己写加载器,而是借用了 Patch My PC 这条既有的第三方补丁执行链。这个工具本身就是用来把补丁包下载到 SoftwareDistribution、解到 Install、再由 ScriptRunner 跑起来的。恶意包只要混进这条流水线,剩下的动作全部是\"合法工具在做它本来就会做的事\"。

至于这个包是怎么混进去的——被篡改的补丁源、被劫持的更新通道、还是内部横向移动时手工投放——原文没有答案。这也是这条链最难防的一段:入口在更新系统里,而更新系统天生要执行外部内容。

三、执行入口:为什么选 ScriptRunner 当宿主

恶意包真正被激活,是这一行:

C:\\Windows\\SoftwareDistribution\\Download\\Install\\PatchMyPC-ScriptRunner.exe -updatepackage -applicationid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

选它当宿主有三个好处:

01  它有正当理由从更新目录里运行。 这个路径下的进程本来就在做更新,行为基线是\"正常\"的。

02  它会加载同目录的配置文件。 .NET 进程启动时会读 <exe名>.exe.config——攻击者只需要把这份配置换掉。

03  -applicationid=<GUID> 每次都不一样。 这个看起来无关紧要的参数,后面会变成解密密钥的来源。

第三条是这条链里最巧的设计,第四节会讲。

四、六跳加载链

从进程启动到插件跑起来,一共六跳。每一跳的产物都是下一跳的输入,磁盘上几乎没有中间可执行文件。

跳 载体 机制 产物
1 PatchMyPC-ScriptRunner.exe.config AppDomainManager 注入 加载一个微软签名的程序集
2 fabric.config 配置类的类型实例化缺陷 实例化 ResXResourceSet
3 Resources.resx BinaryFormatter 反序列化 → TypeConfuseDelegate 执行 XAML-1
4 XAML-1 命令行哈希当密钥 解出 XAML-2
5 XAML-2 分叉:绕 AMSI | 装 .NET 1 Loader XAML-3 与插件就位
6 XAML-3 RPC + TypeConfuseDelegate 迁进程 插件在 dllhost.exe 里跑

第一跳:一个 .config 就能预设加载

CLR 启动进程时,会在 Main 之前读 <exe名>.exe.config 里的这两项:

<runtime> </runtime>

PublicKeyToken=31bf3856ad364e35 是微软自己的公钥标记。也就是说,被指定实例化的是一个微软签名的程序集。签名校验在这里不但没拦住,反而成了掩护——出问题的不是它,是后面它去读了什么。

第二跳:把配置类当成加载器

DataCacheFactoryConfiguration 是 Windows Server AppFabric 缓存客户端的配置类。它被实例化时会去找同目录的 fabric.config,并对里面的 customType 做\"取类型 + 带参实例化\":

<section name=\"foo\"> <customType className=\"System.Resources.ResXResourceSet, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089(Resources.resx)\" /> </section>

于是这个微软类会 Type.GetType 拿到 ResXResourceSet,用字符串 Resources.resx 当参数调它的构造函数。

这不是漏洞利用,是信任边界问题:一个配置类把\"类型名 + 构造参数\"从配置文件里读出来直接实例化,等于把类型选择权交给了文件的作者。

第三跳:.resx 里的反序列化

ResXResourceSet 解析 .resx 时会处理序列化对象字段。攻击者在 Resources.resx 里塞了一个 Base64 的二进制对象,走的是 BinaryFormatter.Deserialize()——这条路径上是经典的 TypeConfuseDelegate gadget:把委托链改写成\"先调 Process.Start、再调 XamlReader.Parse\"。

效果是:一个资源文件被解析,等价于执行了一段代码。

第四跳:命令行就是密钥

XAML-1 只做一件事——解出 XAML-2。但它同时也是反沙箱措施:

key  = SHA256( lowercase( 当前进程完整命令行 ) ) blob = Base64Decode(内嵌串) iv   = blob[0:16];  cipher = blob[16:] XAML-2 = AES_Decrypt(key, iv, cipher) XamlReader.Load(XAML-2)

密钥不硬编码,而是从命令行算出来。补丁工具传的 -applicationid=<GUID> 每次不同,密钥就不同,载荷就解不开。

这正是第三节那个\"无关紧要的参数\"的用处:它既是正常业务参数,又是密钥种子。沙箱如果复现不了原始命令行,只能面对一堆解不开的密文。

解不开时的处理也很干脆:捕获异常后 Environment.Exit(0),进程静默退出,不弹错误框。

第五跳:XAML-2 分两路

XAML-2 里装着两段 Base64(各自 ZIP 压缩后再编码):

•  XAML-2-1,796 行,只干一件事:绕 AMSI;

•  XAML-2-2,装着 .NET 1 Loader 和一个 JSON 配置。

AMSI 这一段是全链技术含量最高的:

01  先判 IntPtr.Size == 8,不是 64 位进程就放弃——后面要按 x64 调用约定打补丁;

02  用 CLSID fdb00e52-a214-4aa1-8fba-4357bb0072ec 建出 AMSI 的 COM 对象;

03  找 Console.Clear() 的原生代码地址,把开头几个字节改成:

41 C7 00 01 00 00 00    ; mov dword ptr [r8], 1 33 C0                   ; xor eax, eax C3                      ; ret

也就是\"往输出参数写 1,返回 0\"。在 AMSI 里 0 就是 AMSI_RESULT_CLEAN;

01  取 COM 对象的 IUnknown,读虚表指针,加 0x18 定位到 CAmsiAntimalware::Scan 的函数指针;

02  VirtualProtect 改成可写,把指针换成刚打过补丁的 Console.Clear() 地址,再恢复保护属性。

这一步的巧妙在于补丁打在合法模块里:改的是 Console.Clear() 的内存代码,AMSI 模块只被改了虚表项。两处都不碰文件,代码完整性校验看不到。

第六跳:换到 dllhost.exe

.NET 1 Loader 拿到 JSON 配置后,先做清扫(下一节细说),再验环境,然后用 RPC 建一个远程 COM 对象(CLSID ab8902b4-09ca-4bb6-b78d-a8f59079a8d5),借 TypeConfuseDelegate 把 XAML-3 送进去,落在 dllhost.exe 里执行。

为什么要换进程:dllhost.exe 是 COM 代理宿主,系统正常进程。载荷迁过去之后原始进程树断了,EDR 上看到的父子关系对不上恶意行为;再加上 Loader 已经把 COMPlus_ETWEnabled 置 0,进程内的 CLR 事件也没有了。

五、擦痕迹:这一步比加载更露信息

.NET 1 Loader 的第一件事不是连 C2,是打扫:

•  删掉更新记录,以及 CAB 解出来的三个文件(.config、fabric.config、Resources.resx);

•  删 CLR 使用日志 %LOCALAPPDATA%\\Microsoft\\CLR_v4.0\\UsageLogs\\PatchMyPC-ScriptRunner.exe.log;

•  删 Prefetch %WINDIR%\\Prefetch\\PATCHMYPC-SCRIPTRUNNER.EXE-*.pf;

•  遍历 %WINDIR%\\Logs\\CmitUpdateAgent\\20????\\20??????,把含 GetExtendedUpdateInfoHandler 的行过滤掉再写回;

•  清掉注册表里一套\"异常状态记录\"键。

最后两条最有情报价值。CmitUpdateAgent 这个目录名指向一套定制版 Windows 10 的更新代理——攻击者不但知道它存在,还知道要清理哪一行。而注册表那套状态记录更反常:它在 HKLM\\SOFTWARE\\Classes\\appid\\ 下按异常码建 GUID 子键,GUID 由 C2 地址推导,每个 C2 对应一组固定的九个键,异常码按二进制位决定建哪几个。用注册表当状态机、把状态藏在 CLSID 里,这种写法在普通恶意软件里见不到。

擦完之后才验环境:校验配置项(不一致就中止并上报)、检查当前用户是否管理员(不是就抛异常码 128)、用 SusClientId 拼 \\x14\\x43\\x9B\\x64 算出实例 ID。

六、插件:通信、代理与隔离域

XAML-3 在 dllhost.exe 里加载模板 __P1__(同一套 AMSI 绕过)和 XAML-P2,后者把 .NET 2 Plugin 解出来并调 m.C()。插件线程做四件事。

取指纹。 读 CLR 版本和系统 BuildLab,拼成 4.7.03190\ 17763.rs5_release.180914-1434 这样的字符串——用来确认\"是不是那套定制系统\"。

保活。 调 CoLockObjectExternal 锁住一个托管对象,防止这个由 RPC 拉起来的 dllhost.exe 被系统回收。

超长休眠。 连 C2 前先随机等 20–40 分钟(沙箱等不了),起一个约 28 小时的看门狗(wi=100801 秒),到点就加载一段 Environment.Exit(0) 的 XAML 自杀。轮询间隔另一套:有响应 5–6 分钟一次,没响应 22–28 小时一次。

通信。 全流量 AES-128-CBC 加 HMAC-SHA256,Encrypt-then-MAC:

IV(16) || ciphertext || HMAC-SHA256(IV || ciphertext, mac_key)

密钥从主密钥 ec 派生:以单字节种子生成 16 字节 HMAC 密钥,对主密钥做 HMAC-SHA256 得中间值,再用它 HMAC 常量 0x01,取前 16 字节。种子 0–3 分别派生出四个方向的加解密与认证密钥,客户端与服务端互为镜像。

请求形状固定:

https://openaigpt4v[.]com/model_v4/<实例ID>?b=&c=<si>

证书校验被配置成 tp=\"*\",接受任意证书。上报结果另有独立配置和密钥。

代理发现三层回退:WinHttpGetDefaultProxyConfiguration() → 找 explorer.exe 并 Impersonate() 它的令牌取代理 → .NET 的 DefaultWebProxy。保证在企业代理环境里也能出网。

隔离域执行是最后一处技巧:从 C2 下载的 XAML 不在主域跑,而是新建随机命名的 AppDomain,复制配置过去,再用 MulticastDelegate 的手法改 _invocationList,把 TimeZoneInfo.ClearCachedData() 换成 XamlReader.Parse(),靠 DoCallBack() 触发。跑完卸载域,内存里不留。

七、基础设施、受害者与归因

项 值
C2 域名 openaigpt4v[.]com
IP 104.18.14[.]219 (Cloudflare CDN,AS15169)
首次见到 2023-09-27
受害者 KSN 遥测里只有一台,位于亚洲;目标是政府专用定制 Windows 10
归因 无法关联任何已知 APT

样本哈希(MD5):

F2458ED133F8D0CA19E22C10F577E98E  CAB 包(文件名即 40 位十六进制) E1DC6F1ED193594F2284BE22AE08DFA4  Resources.resx AB81564BCB8CA7E710C66832DE747556  .NET 1 Loader CFB8B281BDF5D9390E64B10C312FB2C1  .NET 2 Plugin 67E3617F8709D34F806F8E02BADBA8A5  fabric.config 514AC0D73B8A1B73488EF5E2E55A75C3  PatchMyPC-ScriptRunner.exe.config

报告把判断留在结论里:技术主权让越来越多国家自建操作系统,好处是全流程自主,代价是这套生态得自己守;成熟之前,系统和它新长出来的供应链都会成为入口。

八、防守侧能抓什么

按可落地程度排:

01  目录里的 .config。...\\SoftwareDistribution\\Download\\Install\\ 下出现非本机补丁工具产物的 .exe.config,且含 appDomainManagerType,直接高危。这个目录平时不该有手写的运行时配置。

02  异常命名的更新包。 该目录里出现 40 位十六进制之类的裸哈希文件名,且解包内容是\"正常工具 + 一个资源文件\"的组合,值得单独看。

03  appid 下的可疑键。HKLM\\SOFTWARE\\Classes\\appid\\ 里出现 DllSurrogate=\"\" 且不属于任何已装软件的子键——本例里 {ff259bcd-2f0a-44de-b34a-0092fec1d8a2} 是插件写的,用来把 COM 代理指向 dllhost.exe。

04  日志与 Prefetch 同时缺失。 CLR 使用日志和 Prefetch 本该成对出现,两者同时没有、而进程确实跑过,本身就是取证信号。

05  dllhost.exe 的行为。 它挂着装 C2 通信、且找不到对应的 COM 客户端进程,可以判异常。

06  CmitUpdateAgent 日志被整齐改写。 行数明显少于预期、或被过滤掉某一类行,值得单独比对。

07  出网形状。?b=<base64>&c=<guid> 这种两参数的 GET,参数值分别是密文和 GUID,形状很固定。

最后一点提醒:这套样本对沙箱的针对性很强——密钥绑命令行、初始 20–40 分钟抖动、28 小时看门狗。动态分析只跑几分钟,基本什么都看不到,得按它的时间尺度来,或者干脆先做静态。

⚠ 边界说明 ① 来源:所有数据来自一台自建隔离靶机(Windows 10 Pro / IIS 10 / .NET Framework 4.8)的实测,不是复述他人结论。② 载荷:只做「注册 + 返回固定字符串」,不含命令执行、不含加密信道,因此文中没有可直接投递的实现。③ 用途:检测与应急方法面向防守方,可自由取用;对未授权系统使用注入手法属违法行为。④ 数值:事件计数与文件哈希取自一次具体运行,换环境会有差异,方法可复用、数字不必照抄。

XAML当代码用:NetamLoader的加载链拆解" 网络安全文章

XAML当代码用:NetamLoader的加载链拆解"

本文拆解Kaspersky发现的NetamLoader恶意软件加载链,攻击者利用Windows更新目录与PatchMyPC合法组件,通过六跳加载链实现恶意代码执行,包括AppDomainManager注入、BinaryFormatter反序列化、XAML执行及AMSI绕过,最终迁移至dllhost.exe进程。文章详述各跳机制与清理痕迹手法,指出入口在更新系统,防守应关注配置读取行为而非恶意PE。
GhostAction黑客攻击浪潮虽已平息,但攻击者仍在GitHub上潜伏" 网络安全文章

GhostAction黑客攻击浪潮虽已平息,但攻击者仍在GitHub上潜伏"

GhostAction网络攻击活动持续活跃,攻击者入侵开发者账户并向约3万个代码仓库注入恶意GitHubActions工作流以窃取凭证。攻击者部署多种有效负载,包括凭证窃取器和针对云访问的载荷,并模仿朝鲜关联的PolinRider活动。GitHub已阻止恶意工作流执行,但近1800个账户仍被入侵。建议代码仓库所有者删除恶意文件、全面吊销令牌并轮换受影响凭证。
内网搭建docker镜像缓存站" 网络安全文章

内网搭建docker镜像缓存站"

本文介绍内网搭建Docker镜像缓存站的方法,通过配置Docker代理及registry缓存容器,实现镜像首次从互联网拉取后本地缓存复用,提升拉取速度。文中详述服务端与客户端配置步骤,包括daemon.json、config.yml等关键配置,适用于仅需部分镜像的内网场景,可操作性强。
150人考149人过:'包过团'内网穿透入侵考场改答案,9人获刑" 网络安全文章

150人考149人过:'包过团'内网穿透入侵考场改答案,9人获刑"

湖北谷城通报一起特种设备作业考试作弊案,150人参考149人通过,实为包过团伙利用内网穿透接入考场服务器篡改答案,9人因非法控制计算机信息系统罪获刑,主犯获缓刑。案件暴露考场物理接入、内网穿透、远程改库等入侵链条,建议加强终端管控、异常告警与数据完整性校验,并警示技术人员提供技术支持亦涉罪。
评论:0   参与:  0