Cisco Talos 最近观察到 8Base 进行的活动有所增加,该勒索软件组织使用Phobos 勒索软件的变体和其他公开可用的工具来促进其操作。
该组织的大多数 Phobos 变体都是由后门木马SmokeLoader分发的。这种商品加载器通常在部署时丢弃或下载额外的有效负载。然而,在 8Base 活动中,它的加密有效负载中嵌入了勒索软件组件,然后解密并加载到 SmokeLoader 进程的内存中。
8Base 的 Phobos 勒索软件有效负载包含我们在本博客中描述的嵌入式配置。除了这种嵌入式配置之外,我们的分析没有发现 8Base 的 Phobos 变体与 2019 年以来在野外观察到的其他 Phobos 样本之间存在任何其他显着差异。
我们对 Phobos 配置的分析揭示了许多有趣的功能,包括用户访问控制 (UAC) 绕过技术和向外部 URL 报告受害者感染。
值得注意的是,在我们分析的 2019 年以来发布的所有 Phobos 样本中,相同的 RSA 密钥保护加密密钥。这使我们得出结论,获得关联的私钥可以解密所有这些样本。
SmokeLoader 交付 Phobos 有效负载的三阶段过程
我们不会利用这个空间来提供 SmokeLoader 的完整概述(Malpedia 有基础知识),但我们想展示逆向工程师如何达到最终的有效负载。在此示例中,我们将使用样本518544e56e8ccee401ffa1b0a01a10ce23e49ec21ec441c6c7c3951b01c1b19c,但任何最近的 8base 样本最终都将具有相同的勒索软件二进制负载。此过程需要在受控环境中的x32dbg等调试器下执行恶意软件。
SmokeLoader 恶意软件分三个阶段解密其有效负载。第一个包含许多随机 API 调用来混淆执行流程,而另外两个则涉及存储在分配的内存中的 shellcode。最终的二进制文件在第三阶段公开,其中该内存块中 PE(Windows 可移植可执行文件)数据的二进制副本以原始形式提供最终的有效负载。
SmokeLoader 嵌入有效负载的解密过程
在第一阶段,在 VirtualAlloc 或 VirtualProtect 处启用断点并检查其参数应该会显示下一阶段将被解密的地址。然后,该内存位置将在入口点 (EP) 函数的末尾被调用,如上所示。
在第二阶段 shellcode 上,在从入口点调用的函数中进行第二次调用后,将导致对第三阶段的第二个分配的内存的调用。到达第三阶段后,该内存区域应包含解压的二进制文件 (PE),然后可以将其导出到文件并进行分析。
通过此方法提取的最终有效负载哈希为32a674b59c3f9a45efde48368b4de7e0e76c19e06b2f18afb6638d1a080b2eb3。
Phobos 勒索软件中包含的功能概述
我们对 Phobos 的分析发现了许多功能,使勒索软件的操作者能够在目标系统中建立持久性、执行快速加密、删除备份等功能。Phobos 是一种典型的勒索软件,能够加密本地驱动器和网络共享中的文件。在我们的2023 财年第二季度 Talos 事件响应季度报告中,我们详细介绍了 8Base 小组在安装 AnyDesk 后如何使用其 Phobos 变体来实现对计算机的远程访问以及通过 LSASS 执行凭证转储。这些被盗的凭据后来被用来提升权限、窃取数据和运行勒索软件模块。
Talos 观察到恶意软件代码中存在以下特征:
- 对1.5MB以下的文件进行完全加密,对超过此阈值的文件进行部分加密,以提高加密速度。较大的文件将在整个文件中加密较小的数据块,并且这些块的列表与文件末尾的密钥一起保存在元数据中。
- 能够扫描本地网络中的网络共享。
- 通过启动文件夹和运行注册表项实现持久性。
- 生成要加密的扩展名和文件夹的目标列表。
- 进程看门狗线程杀死可能使目标文件保持打开状态的进程。这样做是为了提高重要文件被加密的机会。
- 禁用系统恢复、备份和卷影副本以及 Windows 防火墙。
- 嵌入式配置有 70 多个可用选项。此配置使用与加密文件相同的 AES 函数进行加密,但使用硬编码密钥。
根据对此配置数据的分析,我们能够发现恶意软件二进制文件中存在的其他功能,这些功能可以通过设置特定选项来启用。迄今为止,这些功能尚未记录在案或仅在开源报告中肤浅地提及:
- 使用 .NET 探查器 DLL 加载漏洞绕过 UAC。
- 存在启用恶意软件中附加功能的调试文件,如下节所示。
- 指向使用 Phobos 恶意软件的其他组的名称的黑名单文件扩展名列表。
- 动态导入API调用以避免安全产品的行为检测。
- 硬编码的 RSA 密钥,用于保护加密中使用的每个文件 AES 密钥。
- 向外部 URL 报告受害者感染情况。
- 操作系统检查西里尔语,以防止恶意软件在不需要的环境中运行。
我们还研究了 Phobos 使用的加密方法。2019 年之后发布的 Phobos 版本使用 AES-256 加密的自定义实现,每个加密文件使用不同的随机对称密钥,而不是像早期变体那样使用 Windows Crypto API。每个文件加密后,将使用 RSA-1024 和硬编码公钥对加密中使用的密钥以及其他元数据进行加密,并将其保存到文件末尾。该算法之前已有记录,例如在2019 年的 Malwarebytes 帖子中,并且该过程仍然保持不变。
这使得通过暴力破解文件的可能性极小,因为每个文件都使用不同的密钥,尽管过去曾尝试过暴力破解。然而,这意味着一旦知道 RSA 私钥,自 2019 年以来由任何 Phobos 变体加密的任何文件都可以可靠地解密。自 2019 年以来,对 VirusTotal 上提供的 1000 多个未加壳的 Phobos 样本进行的分析支持了这一事实,其中使用了上述相同的 RSA 公钥。
Talos 分析了自 2019 年以来在所有 Phobos 变体中使用的 RSA 密钥
接下来,我们将更深入地了解配置文件及其启用的一些功能。
解密Phobos配置文件
为了了解Phobos中如何使用配置数据,我们分析了IDA Pro中的恶意代码。这使我们能够使用IDA Python与配置数据进行交互,并立即解密所有配置数据。但首先,我们要看看配置数据在二进制文件中的样子。一旦文件加载到 IDA Pro 中,我们观察到的代码执行的第一个操作就是检查其有效负载并将其加载到内存中:
入口点的代码片段,其中包含内存中的 Phobos 配置数据设置
在上面的代码中,我们看到恶意软件最初检查 PE 文件最后一部分中数据的 CRC32 哈希值。在我们的示例中,该部分被命名为“.cdata”,尽管不同的示例可能使用该名称的变体。我们观察了使用“.cdata”和“.sdata”的样本。
然后,数据被加载到堆分配的内存中,并且带有指向该数据的指针的结构被保存到全局变量中,在上图中名为“ payoad_struct_addr ”。解密函数将使用此结构,以便稍后在整个代码中加载请求的配置条目。每个条目都有一个特定的索引,该索引作为参数传递给解密函数,正如我们在最后一个代码块中看到的那样。
用于处理配置数据的结构体有48个字节,定义如下:
存储指向配置数据的指针的结构
标头和实际数据加载到堆内存中分配的不同缓冲区中,指向这些缓冲区的指针以及配置中的条目数保存在结构中。数据后面是用于解密配置的 16 字节 AES 密钥。该密钥被硬编码在二进制文件中。
标头包含用于查找配置中每个索引的加密数据的信息。该结构体有12个字节,定义如下:
配置数据中标头条目的结构
上面的偏移量是相对于从 0x00 开始的加密数据缓冲区的开头。索引也从 0x00 开始,每个索引对于其包含的数据类型来说是相对静态的,这意味着对于给定的索引,每个样本中应该具有相同类型的配置数据。
然而,我们观察到一些样本的索引内容发生了微小变化。由于 Phobos 过去已被许多团体使用,这些变化可能表明他们正在使用不同的构建器或变体。在对 VirusTotal 中可用的公共样本的分析中,我们发现大约 88% 的样本有 64 个配置条目,而有些样本的配置条目少则 40,多则 72。
根据我们的代码分析,我们还能够推断出存在三个附加选项,用于设置报告 URL 和发送回攻击者的自定义消息。在我们分析的任何样本中都没有观察到这些选项,但处理它们的代码存在于所有样本中。本博客稍后将更好地介绍此功能。
解密函数接受两个参数:所需条目的索引和指向缓冲区的指针,如果条目包含超过四个字节的数据,则使用该缓冲区。否则,缓冲区参数必须为零。
解密函数显示使用有效负载结构中存在的密钥调用 AES_Init 和 AES_Decrypt
该代码扫描标头缓冲区以查找与请求的索引匹配的条目。然后,它分配足够的内存来存储加密数据并将数据复制到该空间,并使用“ payload_struct_addr + 0x10 ”中存在的密钥调用 AES_Init 函数,该密钥在示例数据部分中被硬编码。然后使用加密数据和此 AES 对象作为参数调用函数 AES_Decrypt。
使用 IDA Pro 自动配置解密
一旦理解了解密算法,就可以轻松自动解密配置中的每个条目并将详细信息输出到文件中。IDA Pro 允许使用 Python 来自动执行应用程序内的任务,我们决定使用Flare-Emu Python 模块来模拟恶意软件的 AES 例程,而不是用 Python 重新实现代码。
由于解密函数只需要两个参数,因此相当独立,因此我们决定从该点开始模拟,创建一个类似于恶意软件对有效负载数据所做的结构:
eh=flare_emu.EmuHelper()
conf_struct=eh.allocEmuMem(0x30)
header_data=eh.allocEmuMem(header_size)
config_data=eh.allocEmuMem(config_size)
然后用适当的值填充此结构:
eh.writeEmuPtr(conf_struct,header_data)
eh.writeEmuPtr(conf_struct+4,config_entries)
eh.writeEmuPtr(conf_struct+8,config_data)
eh.writeEmuPtr(conf_struct+0xc,config_size)
eh.writeEmuMem(conf_struct+0x10,AES_Init_key_struct)
# load buffers
eh.writeEmuMem(header_data,eh.getEmuBytes(enc_data_start+0x8, header_size))
eh.writeEmuMem(config_data,eh.getEmuBytes(config_start,config_size))
# write addr of struct back to code
eh.writeEmuPtr(eh.analysisHelper.getNameAddr(CFG_STRUCT_NAME), conf_struct)
创建结构后,我们可以遍历标头中的每个条目并准备模拟器堆栈并从解密函数开始调用模拟:
if entry_size