Mandiant Advanced Practices团队最近发现了一个新的恶意软件家族,并将其命名为 PRIVATELOG 及其安装程序 STASHLOG。在这篇文章中,我们将分享样本用来隐藏数据的一种新颖且特别有趣的技术,以及在 FLARE 分析师的支持下对这两个文件进行的详细分析。我们还将分享样本检测规则和搜寻建议,以在你的环境中找到类似的活动。
Mandiant 尚未在任何客户环境中观察 PRIVATELOG 或 STASHLOG,也尚未恢复由 PRIVATELOG 启动的任何第二阶段有效载荷。这可能表明恶意软件仍在开发中。
CLFS 和事务文件
PRIVATELOG 和 STASHLOG 依靠通用日志文件系统 (CLFS) 来隐藏注册表事务文件中的第二阶段有效载荷。事务文件是一种数据文件,其中存储与主文件中的条目相关的事务记录。这类文件用于将常规事务活动与主文件信息隔离开来,同时允许数据库通过键引用将两者联系在一起。此键引用通常是两组记录,例如帐号、员工或客户姓名,或采购订单。这允许数据库操作员保持相对较小的主文件,同时可以访问完整的事务历史记录以进行审计跟踪或报告。
CLFS 是 Microsoft 在 Windows Vista 和 Windows Server 2003 R2 中为实现高性能而引入的日志框架。它为应用程序提供了 API 函数(在 clfsw32.dll 中可用)来创建、存储和读取日志数据。
由于文件格式没有被广泛使用或记录,因此没有可以解析 CLFS 日志文件的可用工具。这为攻击者提供了以方便的方式将他们的数据隐藏为日志记录的机会,因为这些数据可以通过 API 函数访问。这在本质上类似于恶意软件,例如,可能依赖于Windows注册表或NTFS扩展属性来隐藏他们的数据,这也提供位置来存储和检索二进制数据与Windows API。
在Microsoft Windows中,内核事务管理器(KTM)主要用于事务性NTFS (TxF)和事务性注册表(TxR)操作。它们允许应用程序对文件系统或注册表执行大量更改,所有更改都集中在可以提交或回滚的单个事务中。例如,要在事务中打开注册表项,可以使用RegCreateKeyTransacted()、RegOpenKeyTransacted()和RegDeleteKeyTransacted()函数。
注册表事务存储在具有以下命名方案的专用文件中:< hive > < GUID >.TMContainer< number >.regtrans-ms 或 < hive >< GUID >.TxR.< number >.regtrans-ms。这些是在仅包含元数据的主 .blf 文件中引用的 CLFS 容器,可以在包括用户配置文件目录在内的各种位置中被找到。
CLFS 主文件和容器文件格式大多没有记录,但是,以前的研究可以在 GitHub 上找到。
恶意软件混淆
与许多恶意软件家族一样,PRIVATELOG和STASHLOG使用的大多数字符串都是经过混淆的。然而,这里观察到的技术并不常见,它依赖于对每个字节与硬编码的内联字节进行异或运算,没有循环。因此,每个字符串实际上都使用唯一的字节流进行加密。
“PrintNotify”的字符串去混淆示例
有趣的是,安装程序中的一些反混淆字符串用于记录错误消息,并且存在拼写错误或输入错误,例如:
Log index=%d, data border exceed bounday.
Interal data hash mismatch.
Log buffer size=%u too small, expect aleast %u bytes.
引入STASHLOG
除了包含混淆的字符串外,安装程序的代码还使用各种控制流混淆技术进行保护,这些技术使静态分析变得很麻烦。图 2 是安装程序的 main() 函数的图表概览,展示了控制流混淆的效果。
main() 的图形视图
STASHLOG 有两种不同的操作模式:
没有任何参数,在此期间它将准备好环境;
使用一个参数,该参数是一个应该隐藏在CLFS文件中的文件;
环境的准备
不带参数执行时,安装程序会向控制台打印两个值:
从 HKLMSOFTWAREMicrosoftCryptographyMachineGUID 的注册表值返回的 GUID;
从使用 CoCreateGUID() 随机生成的 GUID 派生的 56 字节值;
控制台输出示例
56 字节值是随机 GUID、其 SHA1 哈希值和先前值的 SHA1 哈希值的链接,所以为GUID+sha1(GUID)+sha1(GUID+sha1(GUID))。
随机生成的 GUID 作为字符串存储在 GlobalAtom 表中,以win::为前缀。该表驻留在内存中并包含所有应用程序都可以使用的带有标识符的字符串。
如果执行安装程序时已存在以 win:: 为前缀的字符串,则将重用 GlobalAtom 表中预先存在的 GUID。
实际上,当不带参数执行时,安装程序会生成并打印出加密密钥,攻击者使用这些密钥在将载荷写入磁盘之前对其进行预加密。
存储有效载荷
当使用参数启动时,安装程序将打开并解密作为参数传递的文件内容。它验证文件是否以其SHA1哈希作为后缀,然后使用内存中存储的 GlobalAtom GUID 字符串生成相同的 56 字节值。
56 字节值再次经过 SHA1 哈希处理,前 16 字节形成初始化向量 (IV),而密钥是来自主机注册表的 16 字节 MachineGUID 值。加密算法为HC-128,在恶意软件中很少被使用。
预期的解密文件内容有一个 40 字节的标头:
在分析的安装程序中,“magic”值被称为校验和。但是,STASHLOG 会验证此值是否与硬编码值 0x00686365 匹配。在偏移量 16 处指定的块数量必须在2到5之间。恶意软件还会检查操作系统版本是否在上下边界内,以及解密数据的 SHA1 哈希值是否与偏移量 20 处的载荷标头值匹配。
在有效载荷标头之后,恶意软件需要带有 8 字节标头的加密数据块。每个区块头具有以下结构:
一旦恶意软件检查并验证了有效载荷的结构,它就会在默认用户的配置文件目录中搜索 .blf 文件,并使用具有最早创建日期时间戳的 .blf 文件。
实际上,恶意软件通常应该找到用于注册表事务日志的文件:C:UsersDefaultNTUSER.DAT< GUID >.TM.blf
如果确实找到了匹配的 .blf 文件,则使用来自 clfsw32.dll 的 CreateLogFile() API 打开它。此函数打开 CLFS 日志并需要以下格式的文件名,不带 .blf 扩展名:log:< LogName >[::< LogStreamName >]
使用 CloseAndResetLogFile() 函数重置日志文件,并将再次打开以插入数据。
在将数据插入 CLFS 日志文件之前,恶意软件会使用 HC-128 解密每个块。密钥是 16 字节的原子 GUID,IV 是原子 GUID SHA1 哈希的前 16 字节。然后使用新的密钥材料重新加密每个块,如下所示:
加密密钥是来自 GetVolumeNameForVolumeMountPointW() 的 16 字节 GUID;
IV 是来自以下连接的 GUID 的 SHA1 哈希值的前 16 个字节:
GetVolumeNameForVolumeMountPointW();
注册表值 HKLMSOFTWAREMicrosoftCryptographyMachineGUID;
使用 clfsw32.dll API 函数 ReserveAndAppendLog() 将内容写入 CLFS 日志文件。载荷标头作为第一个条目写入日志文件,然后是每个块的单独条目。
数据有效地存储在注册表事务日志的第一个容器文件中:C:UsersDefaultNTUSER.DAT< GUID >.TMContainer00000000000000000001.regtrans-ms。
进入私人日志
Mandiant 恢复的 PRIVATELOG 样本是一个未混淆的 64 位 DLL,名为 prntvpt.dll。它包含导出,模拟合法的prntvpt.dll文件,尽管导出没有任何功能。PRIVATELOG 期望从 PrintConfig.dll 加载,这是一个名为PrintNotify的服务的主DLL,通过DLL搜索顺序劫持。
恶意代码在 DLL 的入口点执行,它首先验证它正在运行的进程的命令行参数,并希望在 svchost.exe -k print 下运行。如果匹配,恶意软件会解析 PrintConfig.dll 的 ServiceMain 导出函数的函数地址,它是使用此命令行的服务入口点。此函数使用 Microsoft Detours(一个用于检测 Win32 函数的公开可用库)修复,因此执行流似乎发生在合法服务 DLL 中。
修复后的 ServiceMain 函数是 PRIVATELOG 执行其大部分功能的位置。
与 STASHLOG 类似,PRIVATELOG 通过枚举默认用户配置文件目录中的 *.blf 文件开始,并使用具有最早创建日期时间戳的 .blf 文件。
如果找到匹配的 .blf 文件,PRIVATELOG 会使用 clfsw32.dll 函数 CreateLogFile() 打开它。然后使用其他特定于 CLFS 的函数对日志文件进行编组和解析,例如 CreateLogMarshallingArea()、ReadLogFile() 和 ReadNextLogFile()。该恶意软件希望找到与我们对安装程序的分析相匹配的特定条目。
PRIVATELOG希望第一个日志条目具有以下格式:
一个大于 40 的大小(有效载荷大小);
偏移量 16(块数)处的 WORD 值 2、3、4 或 5;
如果第一个条目符合上述条件,则读取后续记录,直到有一个包含以下内容的 8 字节标头:
它的第一个 DWORD 必须等于 2(假定的魔法值);
它的第二个 DWORD 必须小于条目的大小减去标题,该值等于将被解密的有效载荷的大小;
找到预期的日志条目后,将使用 HC-128 加密算法对其内容进行解密。解密密钥和 IV 是使用与 STASHLOG 使用的相同的唯一主机属性生成的。
值得注意的是,PRIVATELOG只解密第一个匹配块,而STASHLOG预计至少会插入2到5个块。
PRIVATELOG 最终使用了一种很少见的技术来执行 DLL 载荷,这次它依赖于 NTFS 事务。注入过程类似于Phantom DLL hollowing ,描述如下:
通过API CreateFileTransactedA()打开已复制文件的事务句柄,在Mandiant分析的样本中,用于事务的文件是合法二进制文件C:WindowsSystem32dbghelp.dll的副本,该文件被复制到C:WindowsSystem32 WindowsPowerShellv1.0dbghelp.dll;
使用解密的有效载荷内容覆盖事务处理的文件;
通过 API NtCreateSection() 创建由具有 SEC_IMAGE 属性的事务文件支持的部分;
映射新创建的部分的视图,这在一定程度上悄悄地加载事务处理的文件数据。PE 标头经过验证,并将部分映射到内存中;但是,它不修复部分权限或解析导入。
修复部分权限;
解析导入表中的导入;
执行有效载荷的入口点;
查找并执行名为SvcMain的导出函数;
寻找PRIVATELOG
容器样本
下图显示了一个虚构的容器文件,它表示由STASHLOG创建并由PRIVATELOG加载的示例预期日志文件。
STASHLOG创建的示例日志文件
YARA规则
Mandiant 创建了 YARA 规则来寻找 PRIVATELOG 和 STASHLOG 以及基于它们使用的各种方法和独特字符串的可能变体。还提供了检测匹配 PRIVATELOG 结构或包含加密数据的 CLFS 容器的规则,这些规则在生产环境中运行之前应该进行彻底的测试。
EDR/SIEM
为了补充 Yara 的静态搜索,Mandiant 还建议在典型 EDR 日志的“进程”、“图像加载”或“文件写入”事件中寻找类似的攻击指标。这些将涵盖 PRIVATELOG 可以使用 LoadLibrary() 和 GetProcAddress() 动态解析导入的情况,而不是当前已知样本中的静态导入。
下图标识了 PRIVATELOG 加载的可用于创建搜索查询的关键模块:ktmw32.dll、dbghelp.dll 和 clfsw32.dll。
正在运行的PRIVATELOG进程的内存视图
示例搜索查询包括:
任何正在写入或加载C:WindowsSystem32WindowsPowerShellv1.0dbghelp.dll的进程;
任何同时加载clfsw32.dll和ktmw32.dll的进程;
svchost.exe -k print加载clfsw32.dll 或 ktmw32.dll;
任何加载clfsw32.dll的svchos .exe进程;
关于svchost.exe,虽然我们已经观察到许多其他svchost.exe进程加载ktmw32.dll的情况,但很少观察到svchost.exe进程加载clfsw32.dll。
对.regtrans-ms或.blf文件进行文件写入是相当常见的,但是将进程名和文件路径叠加起来也可以提供预期的结果。例如,对默认用户的注册表事务文件的文件写入很可能是不常见的。
参考及来源:https://www.fireeye.com/blog/threat-research/2021/09/unknown-actor-using-clfs-log-files-for-stealth.html