巧用AI大语言模型回溯海莲花APT样本释放过程

巧用AI大语言模型回溯海莲花APT样本释放过程

引言

最近帮朋友分析了一个APT组织海莲花样本,尝试使用AI插件来分析完整的文件释放过程,留此记录。

海莲花(OceanLotus,APT32)是一支活跃在东南亚地区的APT(高级持续性威胁)组织,主要针对政府、军工、科研、能源等关键基础设施进行攻击。本次分析使用了IDA Pro的Gepetto插件,该插件通过集成大语言模型(LLM),可帮助研究人员更高效地进行恶意软件的静态分析。

Gepetto插件介绍

Gepetto是一个Python插件,支持多个LLM(如GPT-4o、Llama 3),可用于分析IDA Pro反编译的函数代码,并提供函数解释和变量重命名功能。主要特点包括:

    • 通过LLM自动解释函数逻辑,减少人工分析负担。

    • 变量、函数命名优化,提高代码可读性。

    • 兼容OpenAI、Ollama、Groq等多种AI模型。

安装Gepetto后,可在IDA Pro的伪代码窗口中右键调用它来分析目标函数,同时支持CLI模式用于查询特定代码片段。

图片[1]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

样本分析

本次分析的样本涉及多个变种,其中一个核心文件为 s.exe.v(MD5: 7A0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)。

沙箱检测什么都没出,看到存在反逆向工程。

20250226163118275-image-20250226104749852

1. 反调试与反分析技术

该恶意样本具备多种反调试机制,目的是防止研究人员和安全工具进行分析。使用Gepetto分析后,可快速总结出主要的反分析策略:

    • 检测调试状态:通过 IsDebuggerPresent、 NtQueryInformationProcess 等API检查是否在调试环境中。

    • 异常触发:利用 int 2D 指令进行调试检测。

    • 环境检测:检查 PEB 结构中的 DebugObject,如存在则进入无限循环。

    • 虚拟机检测:检查BIOS序列号、磁盘信息、虚拟机相关进程(如 vboxservice.exe、 vmware.exe)。

    • 用户交互检测:如果鼠标长时间不移动,则判定为分析环境。

2. 核心攻击流程回溯

我们利用Gepetto逐步回溯样本的核心攻击流程。

(1)初始载荷执行

    • 样本执行后创建 conhost.exe 进程。

    • 在 %temp% 目录下释放 1.msc,并调用 mmc.exe 运行该文件。

(2)利用MMC XSS漏洞执行JS代码

    • 1.msc 文件中嵌入了 StringTable 部分的恶意 APDS 资源。

    • mmc.exe 解析后,在进程上下文中执行JS代码。

(3)远程命令执行与持久化

    • JS 代码加载VBS脚本,解密 BinaryStorage 资源中的二进制数据。

    • 在 ProgramFiles\Cloudflare\ 目录下创建 Warp.exe(白文件)和 7z.dll(恶意DLL)。

    • 通过 WScript.Shell.run 运行 Warp.exe,实现持久化。

(4)最终Payload加载与C2通信

    • 7z.dll 通过 DLL劫持 被 Warp.exe 载入。

    • 该DLL 进一步解密 ShellCode 并执行。

    • 连接C2服务器 sz-everstar[.]com,进行远控通信。

详细静态分析过程

样本代码显示出种多反调试机制,在确保程序在被调试时不会正常运行。

检测调试器

    • if ( IsDebuggerPresent() ):
      • 该函数检查当前进程是否有调试器附加。如果有,进入无限循环,调用 Sleep(0x2710u)。

        • 0x2710u 是十六进制的数字,转换为十进制为 10000 毫秒(即 10 秒),表示在此状态下进程会每 10 秒“休眠”一次,似乎是为了消耗CPU资源,或者防止调试器进一步操作。

检查 PEB (Process Environment Block)

    • if ( NtCurrentPeb()->BeingDebugged == 1 ):
      • 此行检查当前进程的 PEB 中的 BeingDebugged 字段。如果该字段为 1,表示该进程被标记为正在被调试。
      • 进入无限循环,调用 Sleep(0x4E20u),其中 0x4E20u 转换为十进制为 20000 毫秒(即 20 秒)。

        • 这个循环与上面的类似,目的也是为了消耗资源或阻止调试行为。

检查远程调试器

    • pbDebuggerPresent[0] = 0;:初始化 pbDebuggerPresent 数组的第一个元素为 0。

    • CurrentProcess = GetCurrentProcess();:获取当前进程的句柄。

    • CheckRemoteDebuggerPresent(CurrentProcess, pbDebuggerPresent);:
        • 这个函数检查是否有远程调试器正在调试当前进程。如果有,则 pbDebuggerPresent[0] 会被设置为非零值。

    • if ( pbDebuggerPresent[0] ):
        • 如果检测到远程调试器,进入无限循环,调用 Sleep(0xEA60u)(即 60000 毫秒或 60 秒)。

检查全局标志

    • p_NtGlobalFlag = &NtCurrentPeb()->NtGlobalFlag;:
        • 获取当前进程的全局标志地址。

    • if ( p_NtGlobalFlag && (*(BYTE *)p_NtGlobalFlag & 0x70) != 0 ):
      • 检查全局标志是否存在,并且检查其特定位是否被设置(具体是位 6 和 位 5)。

        • 如果符合条件,进入无限循环,调用 Sleep(0x15F90u)(即 90000 毫秒或 90 秒)。

具体实现的核心代码如下:

    • 通过 IsDebuggerPresent 和 NtCurrentPeb()->BeingDebugged 检查调试器是否附加。

    • 使用 CheckRemoteDebuggerPresent 检查是否存在远程调试。

    • 检查进程环境块中的全局调试标志。

    • 如果任何一种检测到调试器的标志为真,代码会进入无限循环,调用 Sleep 函数,阻止进一步的调试操作和分析。

图片[3]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

采用了通过触发异常来进行反调试的技术

设置异常处理程序:使用 SetUnhandledExceptionFilter 函数注册一个顶级异常过滤器 TopLevelExceptionFilter,以便在发生未处理异常时执行自定义的处理逻辑。

触发异常:调用 RaiseException(0xC000008E, 0, 0, 0) 以人为方式引发异常。这个异常代码对应于特定类型的错误,通常与内存访问或状态相关。

重新设置异常处理:在引发异常后,再次调用 SetUnhandledExceptionFilter 来设置新的异常处理程序。

异常条件判断:接下来,通过检查 dword_14001C8E4 的值来决定是否进入无限循环。如果该值为真,程序将进入一个无限循环,每次循环中调用 Sleep(0x9C40u)(即约 40 秒),进一步减缓分析速度。

修改 OS 版本信息:之后,程序还会对 dwOSVersionInfoSize 和其他操作系统版本信息字段进行修改,以伪装其运行环境,从而增加分析难度。

图片[4]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

通过植入 int 2Dh 中断,程序可以检测到是否有调试器附加到其进程上。

如果调试器存在,该中断可能会导致程序崩溃或触发特定的调试异常,从而使程序无法在调试环境中正常运行。

函数定义

    • int64_t sub_14000E6F0() 是一个返回 int64_t 类型的函数,通常用于存储64位整型值。

结果初始化

    • int64_t result; 声明了一个64位的整数变量 result。

    • result = 0x164; 将 result 初始化为16进制的 0x164,其十进制值为 356。

内嵌汇编

    • __asm { int 2Dh; Windows NT – debugging services: eax = type } 使用内嵌汇编调用 int 2Dh。这个指令在 Windows NT 系统中用于调用调试服务,具体来说,int 2Dh 是一个中断指令,它会触发 Windows 的调试异常。

    • 这种调用可以被用作反调试技术。如果程序在调试环境中运行,调试器会接收到这个中断并可能会进行相应处理。通过这种方式,程序可以检测到自己是否在调试中,从而能够采取不同的行为。

返回结果

    • return result; 返回初始化的 result 值(356)。

图片[5]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

通过检查 PEB 中的 DebugObject,程序能有效地检测是否在调试环境中运行。

函数调用检查

    • 代码首先通过一个函数指针调用 v91,并传入一些参数。如果返回值为假(即0),则继续执行后续代码。

    • 这个检查通常用于验证当前进程的状态,确保其没有被调试器影响。

变量初始化

    • v95 = *v93;:从 v93 指向的位置读取值并赋给 v95。

    • v96 = (unsigned int64_t)(v93 + 2);:v96 被设置为 v93 加上2的地址,通常用于指向 PEB(进程环境块)中的某个结构。

    • v97 = 0;:初始化计数器 v97。

检测 DebugObject

    • while (StrCmpW(L”DebugObject”, *(PCWSTR *)(v96 + 8))):循环检查 v96 + 8 地址中的字符串是否为 “DebugObject”。这用于检测进程是否被调试。

    • 如果存在 “DebugObject”,则表示该进程正在被调试。

指针操作和条件判断

    • v98 = *(QWORD *)(v96 + 8) + *(unsigned int16_t *)(v96 + 2) + 8;:计算新的指针地址。

    • 通过位运算和条件判断,更新 v96 的值。

    • if (++v97 >= v95):增加计数器并检查其是否超过 v95,如果是,则跳转到 LABEL_176。

内存释放和睡眠

    • if (*(DWORD *)(v96 + 20)):检查某个条件,如果为真,则释放内存 VirtualFree(v93, 0x164, 0x8000u); 并进入死循环睡眠 40 秒。

    • 这段代码的意图是,如果检测到进程被调试,则释放内存并使程序进入休眠状态,以防止进一步的调试。

图片[6]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

创建一个名为 “Random name” 的互斥体,并设置其句柄的信息,最后关闭这个句柄以释放资源。互斥体用于在多线程环境中实现对共享资源的访问控制,确保同一时间只有一个线程可以访问特定资源。通过使用 SetHandleInformation,可以设置句柄的特定属性,增强对互斥体的管理。

函数定义

    • int64_t sub_14000A740() 是一个返回 int64_t 类型的函数,通常用于执行一些初始化或设置操作。

句柄声明

    • HANDLE MutexW; 声明一个句柄 MutexW,用于表示互斥体对象。

    • void *v1; 声明一个指针 v1,用于存储 MutexW 的值。

创建互斥体

MutexW = CreateMutexW(0i64, 0, L”Random name”);

调用CreateMutexW函数创建一个互斥体对象。

    • 第一个参数 0i64 表示不指定安全属性。

    • 第二个参数 0 表示初始状态未被拥有。

    • 第三个参数 L”Random name” 是互斥体的名称。

    • 这将返回一个句柄 MutexW,该句柄可用于后续的同步操作。

检查互斥体创建成功与否

    • if (MutexW):检查互斥体是否成功创建。如果 MutexW 不为 NULL,则继续执行后续代码。

设置句柄信息

关闭句柄

    • CloseHandle(v1);:关闭之前创建的互斥体句柄,释放资源。

返回值

    • return 0i64;:函数返回0,表示正常结束。

图片[7]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

监视对动态分配内存块的写入操作,通过 GetWriteWatch 函数可以检测是否有写入发生。在反调试的上下文中,使用 MEM_WRITE_WATCH 监视内存访问检测调试器或其他程序对其代码的修改,如果在监视的内存区域内进行了写入,程序可以通过其他逻辑处理这种情况。

内存分配

    • v103 = VirtualAlloc(0i64, 0x8000ui64, 0x3000u, 4);:使用 VirtualAlloc 函数申请一块内存,大小为 0x8000 字节(32KB),分配类型为 MEM_RESERVE,保护属性为 PAGE_READWRITE。如果成功,v103 将指向分配的内存地址。

    • if (v103):检查内存分配是否成功。

第二块内存分配

    • v104 = VirtualAlloc(0i64, 0x100000ui64, 0x203000u, 4);:申请一块大小为 0x100000 字节(1MB)的内存,分配类型为 MEM_WRITE_WATCH | MEM_COMMIT | MEM_RESERVE,保护属性为 PAGE_READWRITE。MEM_WRITE_WATCH 用于监控对该内存块的写入访问。

    • v105 = v104;:将 v104 的值赋给 v105。

检查第二块内存分配

    • if (!v104):如果第二块内存分配失败,则释放 v103 指向的内存并跳转到 LABEL_207。

写入内存

    • *v104 = 1234;:将 1234 写入到 v104 指向的内存地址。

设置写入监视

dwCount = 4096164;:初始化 dwCount 变量,用于存储监视写入的计数。

if (GetWriteWatch(0, v104, 0x100000ui64, (PVOID *)v103, &dwCount, dwGranularity)):调用GetWriteWatch函数来监视v104指向的内存块的写入情况。

    • 第一个参数是保留的,通常为 0。

    • 第二个参数是要监视的内存块的起始地址。

    • 第三个参数是内存块的大小。

    • 第四个参数是一个指向内存地址的指针,用于存储写入监视信息。

    • 第五个参数是传入的计数变量 dwCount。

    • 第六个参数是粒度,通常设置为系统页面的大小。

错误处理

    • LastError = GetLastError();:如果 GetWriteWatch 调用失败,获取最后的错误代码并打印错误信息。

    • 释放之前分配的内存 v103 和 v105。

图片[8]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

avghookx.dll是 AVG 杀毒软件的一个组件,它通常用于提供实时保护和监控系统。如果检测到该 DLL 存在,程序通过进入无限循环来阻止后续代码的执行,防止被 AVG 杀毒软件监控或干扰。

检查模块句柄

    • GetModuleHandleW(L”avghookx.dll”):该函数用于获取名为 avghookx.dll 的模块(动态链接库)的句柄。

    • 如果该模块已被加载到当前进程的地址空间中,GetModuleHandleW 将返回该模块的句柄;如果没有加载,返回值为 NULL。

条件判断

    • if (GetModuleHandleW(L”avghookx.dll”)):如果返回的句柄不为 NULL,说明 avghookx.dll 存在于当前进程中。

无限循环

    • while (1):如果检测到 avghookx.dll 存在,程序将进入一个无限循环。

    • Sleep(0x5BA0u);:在循环中调用 Sleep 函数,参数为 0x5BA0(23328),表示线程将暂停执行 23328 毫秒(约 23.3 秒)。

    • 由于这是一个无限循环,程序将持续休眠直到被外部干预或进程结束。

图片[9]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测当前运行的程序是否是已知的沙箱、如果发现当前程序的名称与已知的沙箱或恶意软件匹配,程序将进入一个无限循环,以防止执行进一步的操作。

字符串数组初始化

psz1是一个宽字符字符串数组,包含了一系列可能的程序名称,用于检测是否运行在沙箱环境中。程序名称包括:

    • sample.exe

    • bot.exe

    • sandbox.exe

    • malware.exe

    • test.exe

    • klavme.exe

    • myapp.exe

    • testapp.exe

获取当前进程的图像路径

    • Buffer = NtCurrentPeb()->ProcessParameters->ImagePathName.Buffer;:通过调用 NtCurrentPeb 获取当前进程的环境块,从中提取出当前进程的图像路径名称(执行文件的路径)。

路径检查

    • if (Buffer):检查 Buffer 是否有效,确保获取到的路径存在。

    • FileNameW = PathFindFileNameW(Buffer);:提取出路径中的文件名部分。

遍历程序名称并比较

    • for (i = 0; i < 8; ++i):遍历 psz1 中的所有程序名称。

    • if (!StrCmpIW(psz1[i], FileNameW)):使用 StrCmpIW 函数比较当前文件名和 psz1 中的每个名称,比较不区分大小写。

    • 如果找到匹配的文件名,进入一个无限循环:
        • while (1) Sleep(0x22CDu);:在无限循环中调用 Sleep 函数(参数为 0x22CD,即 8925 毫秒),使程序处于休眠状态,从而有效地停止进一步执行。

去除文件扩展名

    • PathRemoveExtensionW(FileNameW);:去除文件名的扩展名。此行在前面的逻辑中没有直接用途,但可能是为了后续处理。

变量初始化

    • v3 和 v4 被初始化为 -1,随后 v4 在一个 do 循环中自增,具体用途和上下文不清楚,可能用于某种计数或状态跟踪。

图片[10]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测当前运行程序的用户名,以判断是否在虚拟机、沙箱或由安全研究人员使用的环境中。如果用户名与数组中列出的任何名称匹配,程序可以采取适当的措施,例如进入无限循环或终止运行,以防止在这些环境中被分析。

字符串数组初始化

String是一个宽字符字符串数组,包含了一系列常见的用户名,这些用户名可能与虚拟机、沙箱或安全研究人员相关。包括:

    • CurrentUser

    • Sandbox

    • Emily

    • HAPUBWS

    • Hong Lee

    • IT-ADMIN

    • Johnson

    • Miller

    • milozs

    • Peter Wilson

    • timmy

    • user

    • sand box

    • malware

    • maltest

    • test user

    • virus

    • John Doe

内存分配

    • v138 = (WCHAR *)malloc(0x202ui64);:动态分配一块内存以存储当前用户的名称,大小为 0x202 字节(514 字节),用于存储宽字符字符串。

获取当前用户名

    • if (GetUserNameW(v138, (LPDWORD)&pcbBuffer)):使用 GetUserNameW 函数获取当前用户的名称,并将其存储在 v138 指向的内存中。

    • pcbBuffer 是一个指向 DWORD 的指针,用于存储返回的用户名的长度。

用户名比较

    • for (k = 0; k < 18; ++k):遍历 String 数组中的所有用户名。

    • 在这段代码中,实际的比较逻辑没有显示出来,但可以推测这里会有一段代码比较 v138 中获取的当前用户名与 String 数组中的每个字符串,以判断当前用户名是否匹配其中之一。

图片[11]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测当前计算机的名称,以识别是否在沙箱或虚拟机环境中运行。通过检查计算机名称是否与预定义的名称列表相匹配,采取措施,阻止程序运行或执行特定逻辑,以防止在这些环境中被分析。

字符串数组初始化

v404是一个包含多个宽字符字符串的数组,代表一系列常见的计算机名称,通常与沙箱、虚拟机或分析环境相关。这些名称包括:

    • SANDBOX

    • 7SILVIA

    • HANSPETER-PC

    • JOHN-PC

    • MUELLER-PC

    • WIN7-TRAPS

    • FORTINET

    • TEQUILABOOMBOOM

内存分配

    • v142 = (WCHAR *)malloc(0x20ui64);:动态分配一块内存用于存储计算机名称,这里分配的大小为 32 字节。

    • v143 = v142;:将 v142 的指针赋值给 v143,以便后续使用。

获取计算机名称

    • if (!GetComputerNameW(v142, (LPDWORD)&pcbBuffer)):调用 GetComputerNameW 函数,以获取当前计算机的名称,并将其存储在 v142 指向的内存中。如果函数调用失败,程序将跳转到 LABEL_290。

获取 DNS 主机名

    • GetComputerNameExW(ComputerNameDnsHostname, 0x0, (LPDWORD)&pcbBuffer);:调用 GetComputerNameExW 函数获取 DNS 主机名,pcbBuffer 用于存储返回的主机名的长度。

分配更大的内存

    • v145 = (WCHAR *)malloc(2164 * (unsigned int)((DWORD)pcbBuffer + 1));:根据上一步获取的主机名长度,分配足够的内存来存储主机名。

    • v146 = v145;:保存分配的内存指针。

再次获取计算机名称

    • if (!GetComputerNameExW(ComputerNameDnsHostname, v145, (LPDWORD)&pcbBuffer)):再次调用 GetComputerNameExW 函数,将主机名存储在 v145 指向的内存中。如果失败,将释放已分配的内存并跳转到 LABEL_296。

图片[12]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

通过 WMI 查询获得逻辑磁盘的信息,并检查查询是否成功。通过这种方式,程序可以检测系统的物理磁盘信息,从而可以进行进一步的分析或操作。

这种方法通常被用于系统监控、硬件检测或在恶意软件中用于隐藏自身。

WMI和字符串分配

    • sub_14000E1C0(&v17, &v11, L”ROOT\\CIMV2″):这个函数调用用于连接到 WMI(Windows Management Instrumentation)命名空间 ROOT\\CIMV2。如果连接失败,函数返回 0x164,表示错误。
      v1和v2分别使用SysAllocString分配两个字符串:
      • v1 被分配为 WQL(WMI Query Language)。

        • v2 被分配为 SELECT * FROM Win32_LogicalDisk,这是一个 WMI 查询,用于获取系统的逻辑磁盘信息。

检查指针有效性

    • v4 = 1;:初始化标志变量 v4,用于后续判断。

    • if (v1):检查 v1 是否有效,确保字符串分配成功。

    • if (v2 && …):检查 v2 是否有效,并且执行一个复杂的条件判断。

调用 WMI 查询

    • 如果查询失败(返回值小于 0),则进入条件判断。

处理查询失败

    • 在查询失败的情况下,设置 v4 = 0,表示查询未成功。

    • 调用 sub_14000DC60(L”ExecQuery”),可能是记录日志或执行某个操作。

    • 接下来,调用与 v17 和 v11 相关的函数,处理 WMI 对象的释放或清理。

    • CoUninitialize():调用此函数以释放 COM 库的初始化,这通常在完成 WMI 操作后进行。

图片[13]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测并访问系统中的物理驱动器。通过获取Windows系统目录,确定驱动器号,并构造设备路径打开驱动器,然后执行设备控制操作来收集有关驱动器的信息。

获取Windows系统目录

    • GetSystemWindowsDirectoryW(Buffer, 0x104u):此函数用于获取Windows系统目录的路径。如果调用失败,程序跳转到 LABEL_25。

获取驱动器号

    • DriveNumberW = PathGetDriveNumberW(Buffer):该函数用于获取存储系统目录的驱动器的驱动器号。如果返回值小于0,则跳转到 LABEL_25。

准备设备路径

    • memset(pszDest, 0, 0x104u):清空 pszDest 缓冲区。

    • wnsprintfW(pszDest, 260, L”\\\\.\\%C:”, (unsigned int)(DriveNumberW + 65)):根据驱动器号生成对应的设备路径,例如 \\.\C:。

打开驱动器

    • FileW = CreateFileW(pszDest, 0x80000000, 3u, 0i64, 3u, 0x200000u, 0i64):尝试以读访问权限打开对应的驱动器。如果打开失败,跳转到 LABEL_25。

分配内存

    • v8 = LocalAlloc(0x40u, 0x200000ui64):分配内存用于存储驱动器信息。如果内存分配失败,则跳转到 LABEL_25。

设备控制操作

    • BytesReturned = 0;:初始化返回字节数为0。

    • if (DeviceIoControl(FileW, 0x560000u, 0i64, 0x2000u, &BytesReturned, 0x164) && *v8):调用 DeviceIoControl 函数,发送控制命令到驱动器,并检查返回是否成功。

    • 如果成功,进入循环处理逻辑。

处理物理驱动器

    • v9 = 0;:初始化物理驱动器索引。

    • while (wnsprintfW(FileName, 260, L”\\\\.\\PhysicalDrive%u”, (unsigned int)v8[6 * v9 + 2]) > 0):根据 v8 中存储的驱动器信息构造物理驱动器路径,格式为 \\.\PhysicalDriveX。

    • v10 = CreateFileName(FileName, 0x80000000, 1u, 0i64, 3u, 0, 0i64):尝试打开物理驱动器。如果打开失败,跳出循环。

    • if (!DeviceIoControl(v10, 0x7405Cu, 0i64, 0, &OutBuffer, 8u, &v16, 0i64)):对打开的物理驱动器执行控制命令,检查返回结果。

    • 如果成功,设置标志 v5 = 1,并关闭句柄。

图片[14]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测当前环境是否为虚拟机或沙箱。通过检查设备描述字符串,程序可以识别出常见的虚拟化环境,如 VirtualBox、VMware、QEMU 等。

如果检测到这些环境,程序可能会采取措施,如退出或执行特定逻辑,以避免在这些环境中运行。

检测虚拟机相关字符串

if (PropertyBuffer && …):此条件检查PropertyBuffer是否有效,并且使用StrStrIW函数检查PropertyBuffer

中是否包含以下字符串:

    • vbox:表示 VirtualBox 虚拟机。

    • vmware:表示 VMware 虚拟机。

    • qemu:表示 QEMU 虚拟机。

    • virtual:表示一般虚拟化相关的字符串。

    • 如果检测到这些字符串,程序将执行 break;,这通常表示在循环中跳出,可能是在检测到运行在虚拟机或沙箱环境中。

设备信息枚举

    • LABEL_15::这是一个标签,表示代码流程的一个位置。

    • if (!SetupDiEnumDeviceInfo(v2, ++v3, &DeviceInfoData)):调用 SetupDiEnumDeviceInfo 函数来枚举设备信息。如果失败,则执行后续的代码。

    • 在失败的情况下,v0 = 0; 用来标记状态,如果 PropertyBuffer 不存在,则跳转到 LABEL_18。

释放资源

    • LocalFree(PropertyBuffer);:释放 PropertyBuffer 占用的内存,以避免内存泄漏。

清理设备信息列表

    • LABEL_18::另一个标签,用于处理清理操作。

    • SetupDiDestroyDeviceInfoList(v2);:销毁之前创建的设备信息列表,释放相关资源。

    • if (GetLastError()):检查最后的错误,如果有错误发生,则判断错误码。

    • if (GetLastError() != 259):错误码 259 表示“没有更多数据”,这通常是正常情况。如果错误码不是 259,则返回 0,表示程序执行失败。

程序开始时,会记录鼠标的当前位置,然后在 5 秒后再次记录位置。如果这两个位置相同,程序将进入一个无限的休眠状态。这种行为通常可以用作一种反调试手段,试图检测程序是否在被调试或虚拟环境中运行,或者是为了阻止用户的进一步操作。

变量初始化

    • dwSize = 0i64; 和 pcbBuffer = 0i64;:这两行代码将 dwSize 和 pcbBuffer 初始化为0,类型为64位整型(i64)。

获取鼠标位置

    • GetCursorPos((LPPOINT)&dwSize);:调用 GetCursorPos 函数获取当前鼠标光标的位置,并将其存储在 dwSize 中。这个函数将鼠标的坐标(x 和 y)填充到 dwSize 中。由于 dwSize 被定义为 i64,这里可能存在数据类型不匹配的问题,因为 GetCursorPos 实际上需要一个 POINT 结构来存储两个整型值。

睡眠延迟

    • Sleep(0x1388);:此处调用 Sleep 函数使当前线程休眠一个指定的时间,这里是 0x1388 毫秒(即 5000 毫秒或 5 秒)。这可能是为了让用户在这段时间内进行一些操作。

再次获取鼠标位置

    • GetCursorPos((LPPOINT)&pcbBuffer);:再次调用 GetCursorPos 获取鼠标新位置,并存储在 pcbBuffer 中。

比较鼠标位置

    • if ((HKEY)dwSize == pcbBuffer):检查 dwSize 和 pcbBuffer 是否相等。这里的 (HKEY) 强制类型转换可能是为了将 dwSize 转换为某种指针或句柄类型,这种用法通常不太合理,因为它可能导致类型混淆。实际上,dwSize 应该是一个结构体或包含鼠标坐标的整型,而 pcbBuffer 也应类似。

无限循环

    • if 条件成立后,进入无限循环 while (1),在这个循环中调用 Sleep(0x9C40u);(约 4 秒)。这表示如果鼠标位置在这段时间内没有变化,程序将持续休眠,可能是为了阻止进一步的操作或监控。

图片[15]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测当前环境是否为虚拟机。通过 CPUID 指令和字符串比较,它可以识别出多种虚拟化环境。代码中的无限循环可能用于持续监控或处理某些操作,导致程序无法正常退出。

虚拟机标识字符串

v401数组包含了一系列字符串,这些字符串用于检测虚拟机环境。具体字符串包括:

    • L”KVMKVMKVM”:表示 KVM 虚拟机。

    • L”Microsoft Hv”:表示 Microsoft Hyper-V。

    • L”VMwareVMware”:表示 VMware 虚拟机。

    • L”XenVMXenVM”:表示 Xen 虚拟机。

    • L”prl hyperv “:表示 Parallels Hypervisor。

    • L”VBoxVBoxVBox”:表示 VirtualBox。

CPUID 指令

    • __asm { cpuid }:这一行通过汇编语言的 CPUID 指令来获取 CPU 信息。CPUID 指令可以返回处理器的特性和状态,包括虚拟化相关的信息。

处理器寄存器的存储

    • *(ULONG_PTR *)((char *)&v374[1] + 4) = __PAIR64__(_RCX, _RBX); 和 *(_QWORD *)MultiByteStr = __PAIR64__(_RCX, _RBX);:这两行代码将 RCX 和 RBX 寄存器的组合值存储到指定位置,MultiByteStr 可能用于后续的字符串处理。

变量初始化

    • v394 到 v397 被初始化为 0,这些变量可能用于后续的逻辑处理。

    • v390 = _RDX;:将 _RDX 寄存器的值存储到变量 v390 中,可能用于后续的处理。

字符转换

    • do {…} while (1);:这是一个无限循环。

    • 在循环内部,首先调用 MultiByteToWideChar 函数来转换多字节字符串为宽字符字符串,获取字符串长度并存储在 v172 中。

    • 然后,分配内存给 v173,用于存储转换后的宽字符串。

    • memset(v173, 0, 2i64 * (v172 + 1));:初始化 v173 所指向的内存区域为0。

    • MultiByteToWideChar(0, 0, MultiByteStr, -1, v173, v172);:将 MultiByteStr 转换为宽字符并存入 v173。

    • 循环将持续执行,可能是为了不断检查或处理虚拟机相关的数据。

图片[16]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测与虚拟机相关的服务是否在系统中运行。通过打开服务控制管理器并尝试获取服务信息,程序可以识别出特定的虚拟机服务。如果服务控制管理器的句柄获取失败,程序将返回错误,表明无法继续运行。

虚拟机服务名称数组

psz2数组包含了一系列与虚拟机相关的服务名称,这些服务通常与 VirtualBox 及其组件有关:

    • L”VBoxWddm”:VirtualBox WDDM 驱动。

    • L”VBoxSF”:VirtualBox 文件共享服务。

    • L”VBoxMouse”:VirtualBox 鼠标驱动。

    • L”VBoxGuest”:VirtualBox 客户端服务。

    • L”vmci”:虚拟机通信接口服务。

    • L”vmhgfs”:虚拟机高效文件系统服务。

    • L”vmmouse”:虚拟机鼠标驱动。

    • L”vmemctl”:虚拟机记忆控制服务。

    • L”vmusb”:虚拟机 USB 服务。

    • L”vmsusbmouse”:虚拟机 USB 鼠标服务。

    • L”vmx_svga”:虚拟化的 SVGA 显示驱动。

    • L”vmxnet”:虚拟网络驱动。

    • L”vmx86″:虚拟化的 86 处理器模拟。

打开服务控制管理器

    • v0 = OpenSCManagerW(0i64, L”ServicesActive”, 5u);:调用 OpenSCManagerW 函数以获取服务控制管理器(SCM)的句柄。这里的 0i64 表示指向本地计算机的指针,L”ServicesActive” 是服务数据库的名称,5u 表示访问权限。

    • if (!v0):如果OpenSCManagerW返回一个空值,表示获取 SCM 句柄失败。
      在这种情况下,程序打印错误信息并返回 1,表示出现了错误。

动态内存分配

    • lpServices = malloc(0xE0005E0);:分配一块内存(约 14,000,000 字节),用于存储服务信息。

    • v2 = lpServices;:将分配的内存地址赋值给 v2,方便后续使用。

    • ServicesReturned = 0;:初始化 ServicesReturned 变量,可能用于存储返回的服务数量。

检查内存分配

if (lpServices):检查lpServices是否成功分配内存。

    • 如果成功,初始化 pcBytesNeeded 和 ResumeHandle 为 0,这些变量可能在后续处理中用于存储服务信息或控制服务的恢复。

图片[17]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测 BIOS 序列号中是否包含与虚拟机相关的程序。通过比较 BIOS 序列号中的特定字符串来判断当前环境是否为虚拟机。如果发现与虚拟化相关的字符串,程序会执行相应的清理和处理操作。

获取 BIOS 序列号

    • 代码的第一部分通过调用一个函数(可能是某种 COM 接口)来获取 BIOS 序列号。这个函数的调用使用了一个函数指针,具体�参数包括字符串 L”SerialNumber”,指向 pvarg 的指针等,函数返回值大于0表示成功获取。

检查获取的值

    • if (pvarg.vt == 8 …):检查 pvarg 的 vt(变量类型)是否为8。 在 COM 中,类型8通常表示 BSTR 类型(字符串)。

    • StrStrIW(pvarg.bstrVal, L”VMware”):使用 StrStrIW 函数检查 pvarg.bstrVal 是否包含字符串 “VMware”。

    • *pvarg.plVal == 48:检查 pvarg.plVal 指向的值是否为48(可能表示某种状态或标识)。

    • StrStrIW(pvarg.bstrVal, L”Xen”)、StrStrIW(pvarg.bstrVal, L”Virtual”) 和 StrStrIW(pvarg.bstrVal, L”A M I”):继续检查其他与虚拟化相关的字符串。

虚拟机检测逻辑

    • 如果上述条件之一为真,则表示检测到该系统正在运行在虚拟机中(如 VMware、Xen、某些虚拟化平台等)。

    • 在检测到虚拟机后,调用 VariantClear(&pvarg); 清理 pvarg 变量,释放任何占用的资源。

    • 然后调用一个函数(从 v10 指向的对象中获取)来执行某些操作,可能是为了处理虚拟机检测后的逻辑,设置 v0 = 1; 表示发现虚拟机。

清理和结束

    • 在条件判断的外部,再次调用 VariantClear(&pvarg);,确保无论如何都清理资源。

    • 继续处理后续逻辑。

图片[18]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

通过执行 WMI 查询来检测计算机的模型信息,以识别是否在虚拟机环境中运行。通过比较模型名称中的特定字符串,程序能够检测出多个虚拟化平台。

如果发现与虚拟化相关的字符串,程序将执行相应的清理和处理操作。

执行 WMI 查询

    • 代码的第一部分调用一个函数(可能是某种 COM 接口)来获取计算机系统模型的信息。此处使用的字符串为 L”Model”,表示从 Win32_ComputerSystem 类中获取计算机的型号信息。

    • (*(int (__fastcall **)(…))(*(_QWORD *)v10 + 32i64)):这是一个函数指针调用,指向一个执行 WMI 查询的函数,返回值大于或等于0表示成功获取数据。

检查返回的值

    • if (pvarg.vt == 8 …):检查 pvarg 的 vt(变量类型)是否为8,表示 BSTR 类型(字符串)。

    • StrStrIW(pvarg.bstrVal, L”VirtualBox”):检查 pvarg.bstrVal 中是否包含字符串 “VirtualBox”。

    • StrStrIW(pvarg.bstrVal, L”HVM domU”):检查是否包含 “HVM domU”(这通常与 Xen 虚拟化相关)。

    • StrStrIW(pvarg.bstrVal, L”VMware”):检查是否包含 “VMware”。

虚拟机检测逻辑

    • 如果上述条件之一为真,表示检测到该系统正在运行在虚拟机中(如 VirtualBox、Xen 或 VMware)。

    • 在检测到虚拟机后,调用 VariantClear(&pvarg); 清理 pvarg 变量,释放占用的资源。

    • 然后调用一个函数(从 v10 指向的对象中获取)来执行某些操作,可能是为了处理虚拟机检测后的逻辑,设置 v0 = 1; 表示发现虚拟机。

清理和结束

    • VariantClear(&pvarg); 在条件判断的外部再次调用,确保无论如何都清理资源,防止内存泄漏。

图片[19]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

通过执行 WMI 查询来获取系统的内存和连接器信息。如果任何查询失败,程序将进入一个无限循环,持续等待,直到其他条件或外部因素改变程序的执行状态。

执行 WMI 查询

    • 每个 if 语句都调用 sub_140000C040 函数,传递一个 SQL 查询字符串(例如,L”SELECT * FROM Win32_CacheMemory”)。这个函数的目的可能是执行 WMI 查询,获取相关的系统信息。

检查查询结果

    • !(unsigned int)sub_140000C040(…):如果该函数返回的值为假(通常表示查询失败或没有结果),则进入相应的循环。

    • 该函数返回的值被强制转换为 unsigned int,使用逻辑非运算符 ! 进行检测。

无限循环和延迟

    • 如果查询失败,就进入一个无限循环 while (1),在该循环内调用 Sleep(0x9C40u);。这个调用会使当前线程休眠约 40,000 毫秒(或 40 秒),然后再次检查条件。

    • 由于是无限循环,程序在此处将无法继续执行其他代码,直到条件变为真。

查询的各个对象

    • 代码中分别查询了多个 WMI 类:
      • Win32_CacheMemory:表示系统中缓存内存的相关信息。
      • Win32_PhysicalMemory:表示物理内存的信息。
      • Win32_MemoryDevice:表示内存设备的信息。
      • Win32_MemoryArray:表示内存数组的信息。

        • Win32_PortConnector:表示端口连接器的信息。

图片[20]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

通过访问系统注册表中与虚拟化相关的键值,检测当前系统是否在虚拟机环境中运行。通过检查特定的注册表键和子键,程序能够识别出常见的虚拟化平台(例如 VirtualBox、VMware 等)。

注册表查询

    • 代码通过 RegOpenKeyExW 打开 Windows 注册表中的特定键以获取与虚拟机相关的信息。主要路径为 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Disk\Enum,这是存储有关磁盘驱动程序的信息的位置。

检测虚拟机类型

    • v403 数组中定义了一些常见虚拟机的标识符,如 qemu、vmware、vbox(VirtualBox)、xen、VM 和 Virtual。这些字符串用于后续比较,以检测当前系统是否运行在虚拟机中。

计数和循环

    • 使用 RegQueryValueExW 函数查询 Count 值,获取可能的磁盘数量。然后通过遍历 pcBuffer,对每一个可能的磁盘进行检测。

遍历子键

    • 代码定义了一系列与 VirtualBox 相关的注册表子键。这些子键主要包含与 VirtualBox 相关的 ACPI 和服务信息。

    • 在 do 循环中,尝试打开每一个子键,检查是否存在以确认系统是否在虚拟机环境中运行。

错误处理

    • 如果在打开注册表键时发生错误(返回值为假),则使用 RegCloseKey 关闭打开的键,并跳转到 LABEL_725,可能是进行清理或其他后续操作。

图片[21]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

图片[22]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

通过打开与 VirtualBox 相关的管道和设备,检测当前系统是否在运行 VirtualBox 虚拟机。通过查找 VirtualBox 相关的窗口,程序可以进一步确认虚拟机的状态。

打开虚拟机管道

    • 代码定义了一个lpFileName数组,包含了一些与 VirtualBox 相关的命名管道和设备名称。例如:
      • \\.\VBoxMiniRdrDN
      • \\.\VBoxGuest
      • \\.\pipe\VBoxMiniRdr

        • \\.\VBoxTrayIPC

    • 这些名称是 VirtualBox 在宿主机和虚拟机之间进行通信和数据传输时使用的。

创建文件句柄

    • 在 do 循环中,代码尝试通过 CreateFile 函数打开这些命名管道和设备。参数 0x80000000 表示以只读方式打开,1 表示共享模式,其他参数用于指定安全属性和创建方式。

    • 如果 CreateFile 成功(返回的句柄不是 INVALID_HANDLE_VALUE),则关闭该句柄并将 MEMORY[0] 设置为 10。这可能是用于记录成功打开的管道数量或状态。

查找窗口

    • FindWindow 函数用于查找与 VirtualBox 相关的窗口,分别使用 VBoxTrayToolWndClass 和 VBoxTrayToolWnd 作为窗口类名和窗口标题。

    • 如果找到任何一个窗口,则进入一个无限循环,调用 Sleep(0x7Bu); 休眠约 123 毫秒。这样的设计可能是为了等待某个状态或事件。

无限循环

    • 如果找到 VirtualBox 的窗口,代码将会在无限循环中停留,可能是为了防止程序继续执行,保持在这个状态下。

图片[23]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测 vboxservice.exe 和 vboxtray.exe 这两个进程是否在运行,并通过 WMI 查询获取系统中与 PnP 设备相关的信息。通过调用 WMI 查询和检查这些进程,程序可能用于安全监测或虚拟机环境检测,判断当前系统的状态。

检测进程

    • psz2 数组包含两个字符串,分别为 vboxservice.exe 和 vboxtray.exe,这两个进程是 VirtualBox 的组件,分别用于管理虚拟机服务和托盘图标。

循环调用

    • do 循环中调用 sub_140000E0A0 函数,传递 psz2 数组中的每个进程名称。这个函数的具体实现没有展示,但其目的是可能用来检测这些进程是否在运行。

初始化变量

    • dwSize, phkResult, 和 v217 被初始化为 0,这可能是为后续的注册表或 WMI 查询做准备。

WMI 查询

    • 使用 sub_140000E1C0 函数尝试获取 WMI 的 ROOT\\CIMV2 命名空间的句柄。这个函数的返回值决定了后续代码是否执行。

    • SysAllocString 被用来分配 WQL 查询字符串(SELECT * FROM Win32_PnPEntity),这个查询用于获取所有与 PnP(即即插即用)设备相关的信息。

条件检查和调用

    • 检查 v218 和 v219 是否成功分配内存。

    • 如果 v219 不为 NULL,则通过一个函数指针调用执行 WMI 查询的函数。这个函数指针是通过 (*(_QWORD *)dwSize + 160i64) 取得的,表示在 dwSize 指向的某个结构体中的偏移地址为 160 的函数。

错误处理

    • 如果函数调用的返回值小于 0,则表示出现错误,后续处理逻辑可能在此处实现(虽然具体的错误处理代码在这里未显示)。

图片[24]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

识别虚拟机环境中的特定特征,尤其是 VirtualBox 相关的特征。通过使用无限循环和 Sleep 函数,程序可以在满足某些条件时保持挂起状态,从而防止代码被进一步执行或分析。此类技术常见用于检测和规避虚拟机环境。

函数调用

    • 代码中有多个 sub_xxxxxxx 函数调用,这些函数的具体实现没有给出,但它们的返回值被用于判断是否进入后续的无限循环。这些函数可能用于检测某些特定的环境特征,尤其是与 VirtualBox 相关的特征。

无限循环

    • 每个 if 语句后面都有一个无限循环,使用 Sleep(0x9C40u) 来使线程休眠(大约 40000 毫秒,即 40 秒)。这种设计通常用于在检测到特定条件时阻止进一步的代码执行。

    • 这可能是为了防止代码在虚拟环境中执行,或者是为了在检测到虚拟机相关的条件时保持一个挂起状态。

检测特征

    • 每个 sub_14000CFF0、sub_14000D6E0、sub_14000D220、sub_14000D450 和 sub_14000CB60 可能都与特定的虚拟机特征或状态检测有关。这些特征可能包括正在运行的进程、特定的设备或其他环境变量。

目的

    • 这段代码的主要目的是检测当前执行环境是否为 VirtualBox。通过对特定条件的检查,如果满足,程序将进入一个无限循环,从而停止进一步的执行。这种行为通常用于保护自身不被调试或分析。

图片[25]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测 VMware Tools 的安装状态,通过检查注册表中的特定键来确认 VMware 环境是否存在。通过调用 sub_14000DE80 和注册表操作,识别虚拟环境进行某种自我保护机制。

变量初始化

    • v407 数组包含了一些与 VMware 相关的字符串,主要是用于后续的注册表查询和匹配。例如,”VMWARE”、”SystemProductName” 和 “Identifier”。

循环与条件检测

    • do 循环中调用 sub_14000DE80 函数,可能用于检查某个条件或状态,如果该条件返回真,则跳转到 LABEL_725。这可能是用于检测是否在 VMware 环境中。

控制流

    • 通过 ++v231 和 v230 += 3,循环的目的是逐步增加某些索引或指针,可能是为了遍历 v407 中的某些内容。

注册表查询

    • 在 while (v231 < 5); 循环体中,dwSize 被初始化为指向 VMware Tools 的注册表路径 “SOFTWARE\\VMware, Inc.\\VMware Tools”。

    • RegOpenKeyExW 函数用于打开 Windows 注册表中的指定键。它尝试打开指定的 VMware Tools 注册表项,并将返回的句柄存储在 pcbBuffer 中。

图片[26]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

识别 VMware 虚拟机环境,通过检查常见的虚拟机 MAC 地址来实现。结合 MAC 地址的匹配和可能的其他环境检测,以防止在虚拟机中被分析或调试。

MAC 地址初始化

    • v398 数组中包含了多个与 VMware 相关的 MAC 地址字符串。这些地址是虚拟机中常见的 MAC 地址,通常用于识别虚拟环境。

    • 具体的 MAC 地址包括:
      • 00:05:69
      • 00:0c:29
      • 00:1C:14

        • 00:50:56

不明指针

    • unk_140013C88、unk_140013CA8、unk_140013CC8 和 unk_140013CE8 是一些不明确的指针,可能指向某些数据结构或函数。这些指针的具体类型和作用需要结合更多的上下文来理解。

循环结构

    • do…while 循环调用 sub_14000DF70 函数,传入 *v231。v231 可能是一个指向当前处理的 MAC 地址的指针或索引。

    • 在每次迭代中,v231 增加 2,这可能意味着每次处理两个 MAC 地址。

    • –v230 可能是用于控制循环的计数器,直到 v230 变为 0 为止,结束循环。

功能目的

    • 该代码的主要目的可能是检测当前系统的 MAC 地址是否与 VMware 虚拟机的常见 MAC 地址匹配。

    • 函数 sub_14000DF70 的具体实现没有给出,但可以推测它可能用于比较或验证 MAC 地址。

反虚拟化技术

    • 通过检测 MAC 地址,程序可能试图判断其运行环境是否为 VMware。如果检测到匹配的 MAC 地址,程序可能采取特定的行为,例如停止执行或进入错误处理。

图片[27]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测 VMware 虚拟机环境,通过检查进程和注册表项来判断当前的运行环境。通过检测进程 VMSrvc.exe 和 VMUSrvc.exe,程序试图识别是否在 VMware 环境中运行。注册表的检查进一步确认了虚拟机的存在,如果没有找到对应的注册表项,程序将退出。

进程名称初始化

    • v381数组中包含了两个字符串,表示 VMware 的服务进程:
      • VMSrvc.exe

        • VMUSrvc.exe

    • 这些进程是 VMware 虚拟机在运行时常见的服务。

进程检测

    • do 循环中调用 sub_14000E0A0 函数,传入 v381[v267],用于检测当前系统中是否存在这些进程。

    • 如果该函数返回真(即检测到进程存在),则进入一个无限循环,调用 Sleep(0x2B67u),这可能是为了挂起程序或降低 CPU 使用率。

循环控制

    • ++v267 用于控制迭代次数,确保检测两个进程(索引 0 和 1)。

    • while (v267 < 2); 确保只检测这两个进程。

注册表查询

    • v268 被初始化为 0,接下来设置 dwSize 为 VMware 注册表路径 “SOFTWARE\\Microsoft\\Virtual Machine\\Guest\\Parameters”。

    • 在另一个 do 循环中,使用 RegOpenKeyExW 函数尝试打开指定的注册表键。如果打开注册表键失败(返回非零值),则调用 RegCloseKey 并退出程序。

异常处理

    • 如果没有找到预期的注册表项,程序将调用 exit(0),这意味着程序将正常退出,但可能并不是预期的行为。

图片[28]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

检测虚拟机环境,通过检查特定的服务注册表项来判断当前系统是否在虚拟机中运行。注册表中存在这些服务通常表明系统正在虚拟环境中运行,因此这段代码试图确认这一点。如果找不到任何服务,程序将退出。

服务名称初始化

    • v395数组中包含了多个与虚拟机相关的服务路径,这些服务通常用于 VMware 或其他虚拟化技术的功能:
      • vioscsi
      • viostor
      • VirtIO-FS Service
      • VirtioSerial
      • BALLOON
      • BalloonService

        • netkvm

注册表检测

    • do 循环遍历 v395 数组,检查每个服务的注册表项是否存在。

    • RegOpenKeyExW 函数用于打开注册表键,如果无法打开(即服务不存在),则调用 RegCloseKey 并退出程序(exit(1)),表示程序发现了虚拟机环境。

循环控制

    • ++v287 用于控制迭代,确保所有七个服务都被检查。

    • while (++v287 < 7); 确保在检查完所有服务后退出循环。

内存操作

    • memset(pszDir, 0, 0x208ui64); 用于清空 pszDir,这可能是一个用于存储路径或目录的数组。

    • wcscpy(v424, L”Virtio-Win\\”); 将字符串 “Virtio-Win\\” 复制到 v424,这可能是用于指定虚拟机驱动程序的路径。

    • memset(&v424[12], 0, 0x1F0ui64); 清空 v424 中从索引 12 开始的内存部分,可能是用于准备存储其他数据。

变量初始化

    • v289 被初始化为 0,可能是用于后续的逻辑或计数。

图片[29]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

它通过对输入数据(a2)与一个查找表(dword_140030AC0)进行多轮异或和位移操作,来生成一个密钥或哈希值初始化密钥。

变量初始化

    • 代码段中的变量 v2 到 v15 表示一系列的中间结果,通常用于加密或散列算法的计算。

数据来源

    • dword_140030AC0 是一个数组或某种形式的数据源,可能是某种密钥、查找表或预定义的常量。

    • a2 是一个输入数组,可能包含密钥或其他相关数据。

逻辑流程

    • 每一步都使用异或运算符(^)和位移操作(>>)来处理数据:
      • ~LOBYTE(dword_140030AC0[ (unsigned __int8*)*a2]) 获取 dword_140030AC0 中某个元素的低字节并取反。
      • ^ a2[n] 用于将输入数组中的相应字节与计算结果进行异或运算。

        • ^ (vN >> 8) 将当前的计算结果与之前的结果右移后再进行异或,形成一种链式计算。

循环与返回

    • 该段代码并没有显示的循环结构,但它在逐步处理输入数组的每个元素,最终返回一个计算结果。

    • return dword_140030AC0[ (unsigned __int8)(v15 ^ a2[15]) ]; 这一行返回了根据输入数组最后一个元素 a2[15] 计算得到的结果。

图片[30]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

定义一组硬编码的值,用于与计算得到的密钥或哈希值进行比较,常用于安全性检查或防篡改措施。

数组初始化

    • v375 是一个包含 16 个 32 位无符号整数的数组,每个元素都被初始化为一个特定的十六进制值。

    • 这些值很可能是用于验证某种密钥或哈希值的硬编码常量。

硬编码值的用途

    • 这些硬编码的值通常用于比较,通过将计算得到的哈希值或密钥与这些值进行比较,来确定是否匹配。这样的设计常见于软件中,以防止未授权的访问或篡改。

LODWORD 操作

    • LODWORD(pcbBuffer) = 0; 将 pcbBuffer 的低字(低32位)设置为 0。pcbBuffer 可能是一个指针或一个结构体中的字段,这里将其清零可能是为了初始化或重置。

图片[31]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

从程序资源中加载名为 “ICONS” 的数据,并对其进行解密。解密使用了一个硬编码的密钥(存储在 v411 中)。使用 VirtualAlloc 申请内存,利用 FindResourceW 和 LoadResource 加载资源,最后通过异或操作解密数据。

    1. 内存分配
      1. VirtualAlloc(0i64, 0x100000ui64, 0x1000u, 4u); 这行代码申请了一个 1MB 的内存块,可能用于存储解密后的数据。

        1. v331 = VirtualAlloc(0i64, 2 * v329, 0x1000u, 4u); 这行申请了一个内存块,其大小为 2 * v329,用于存储解密后的资源数据。

    1. 资源加载
        1. FindResourceW 和 LoadResource 用于查找和加载名为 “ICONS” 的资源。ResourceW 存储了资源句柄,Resource 存储了加载的资源数据。

    1. 资源大小
        1. v329 = SizeofResource(0i64, ResourceW); 这行代码获取了资源的大小,并将其存储在 v329 中。

    1. 解密过程
      1. if ((unsigned int)v330 >> 2) 这一条件检查 v330 的值(即资源的大小)是否大于0。
      1. v334 = v331; 该行初始化了指向已分配内存的指针 v334。
      1. v335 = Resource – (_BYTE)v331; 计算出资源数据和解密内存之间的偏移量。

        1. 在do…while循环中,使用异或操作解密数据:*(v334 – 1) = *(DWORD *)((char *)v334 + v335 – 4) ^ *((DWORD *)v411 + v336); 这一行从资源中读取数据并与密钥数据(存储在 v411 中)进行异或运算,解密后的数据被写入到 v334 指向的内存中。

    1. 轮询解密
        1. while (v332 < (unsigned int)v330 >> 2); 循环条件判断,确保解密操作遍历所有数据。

图片[32]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

初始化一个包含常见调试和分析工具进程名的字符串数组,以便在运行时进行检测。

数组初始化

    • v402 是一个字符串数组,包含了多个常见的调试和分析工具的进程名。

    • 这些工具包括调试器(如 OllyDbg 和 WinDBG)、资源监控工具(如 Process Hacker 和 Wireshark)、反汇编工具(如 IDA Pro)、以及内存修改工具(如 Cheat Engine)。

用途

    • 这些进程名的列举通常用于检测系统中是否存在这些工具,以防止程序被逆向工程或调试。

    • 这种检测逻辑在恶意软件和某些商业软件中非常常见,目的是增加对逆向工程的难度。

反分析机制

    • 在程序运行时,可能会通过查询系统进程列表,检查是否有与 v402 中的进程名匹配的进程。如果发现有匹配项,程序可能会采取措施,例如退出、隐藏其行为或改变其执行路径。

    • 这种方法能够有效地阻止安全研究人员或攻击者分析程序的行为,从而保护软件的知识产权或防止恶意行为。

图片[33]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

将解密的数据写入到 %temp%\1.msc 文件中。通过打开文件、写入数据和关闭文件的步骤,确保数据的持久化。

文件名初始化

    • memset(FileName, 0, 0x104ui64); 这行代码将 FileName 数组初始化为零,大小为 0x104(260 字节),用于存储文件路径。

    • strcpy(v339, “1.msc”); 这行代码将字符串 “1.msc” 复制到 FileName 中,表示要创建的文件名。

文件打开

    • v341 = fopen(FileName, “wb”); 这行代码以写入模式打开文件 1.msc。如果文件打开失败,程序将调用 exit(1); 终止执行。

写入解密数据

    • fwrite(v333, v330, 1ui64, v341); 这行代码将解密后的数据(存储在 v333 中,大小为 v330 字节)写入到打开的文件中。

文件关闭

    • fclose(v342); 关闭文件,确保所有数据都被写入并释放资源。

结构体初始化

    • StartupInfo.cb = 104; 设置 StartupInfo 结构体的 cb 字段,可能用于后续创建进程或线程时的初始化。

    • memset(&StartupInfo.cb + 1, 0, 0, 100); 这行代码似乎意图清空 StartupInfo 结构体的其他部分,但写法有误,应该是清空从 cb + 1 开始的 100 字节。

    • memset(&ProcessInformation, 0, sizeof(ProcessInformation)); 将 ProcessInformation 结构体的所有字段初始化为零,以确保安全性。

字符编码转换

    • v343 = MultiByteToWideChar(0, 0, FileName, -1, 0); 这行代码将 FileName 从多字节字符转换为宽字符格式,可能用于后续的文件操作或进程创建。

图片[34]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

创建并执行 mmc.exe 进程,这通常是 Windows 的管理控制台(Microsoft Management Console)。

内存分配

    • v347 = (WCHAR *)malloc(2i64 * ((int)v90 + 8)); 这行代码分配了一段内存,用于存储命令行字符串,长度为 2 * ((int)v90 + 8) 字节。v90 很可能是根据程序需要动态计算的值。

命令字符串设置

    • *(DWORD *)v347 = *(DWORD *)L”mmc.exe “; 这行代码将字符串 “mmc.exe ” 存储到 v347 指向的内存中,表示要执行的程序。

    • v347[8] = aMmceExe[8]; 这行似乎是将某个字符数组的第 8 个元素赋值给 v347 的第 8 个位置,具体含义取决于 aMmceExe 的内容。

字符串结束符处理

    • 通过 do…while 循环,代码逐个检查字符,直到找到字符串的结束位置(即 0),用于确保字符串正确终止。

启动进程

返回

    • return 0; 表示程序正常结束。

图片[35]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

通过利用 MMC 的 XSS 漏洞,在加载特定的 MSC 文件时执行 JavaScript 代码。

字符串定义

    • 该 XML 片段是一个 StringTable,包含多个字符串定义。每个 <String> 标签都有一个唯一的 ID 和可能的 Ref 属性。

    • 字符串 ID 23, 24, 和 38 似乎是正常的字符串,可能在 MMC 界面中用作显示文本。

XSS 漏洞利用

    • 第 39 行的字符串内容是关键部分:res://apds.dll/redirect.html?target=javascript:eval(external.Document.ScopeNamespace.GetRoot().Name)。

    • 这段代码利用了 apds.dll 资源中的 redirect.html 文件,并尝试通过 javascript:eval 执行 JavaScript 代码。

    • external.Document.ScopeNamespace.GetRoot().Name 是 JavaScript 代码的一部分,目的是获取当前上下文中的某个对象的名称,可能用于执行额外的恶意操作。

攻击流程

    • 当受害者打开包含此字符串的 1.msc 文件时,MMC 会处理这个字符串,并在其进程上下文中触发 JavaScript 执行。

    • 因此,攻击者可以通过这种方式在受害者的计算机上执行任意 JavaScript 代码,而无需用户的明确同意。

潜在影响

    • 这种 XSS 漏洞的利用可能导致各种安全问题,包括数据泄露、权限提升或执行恶意软件等。

    • 利用 MMC 和相关组件的 XSS 漏洞,攻击者可以在不受保护的环境中进行攻击。

图片[36]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

js代码解密后获得vbs脚本。

图片[37]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

<?xml version=”1.0″?>
<stylesheet xmlns=”http://www.w3.org/1999/XSL/Transform” xmlns:ms=”urn:schemas-microsoft-com:xsl” version=”1.0″>
<output method=”text”>
<m:s:script implements-prefix=”user” language=”VBScript”>
<![CDATA[
Dim msCL
msCL = “v”
For i = 1 To Len(msCL) Step 4
    dLkFNuY = dLkFNuY & Chr(CLng(Chr(Int(&H48))) & Mid(msCL, i, 4))
Next
Set b7kyThoe = CreateObject(Chr(Int(&H77)) & Chr(Int(&H69)) & Chr(Int(&H99)) & Chr(Int(&H72)) & Chr(Int(&H115)) & Chr(Int(2184 – 2073)) & Chr(76) & Chr(3017 – 2949) & “O” & Chr(6337 – 6260))
b7kyThoe.Async = Chr(&H46) & Chr(Int(&H97)) & Chr(Int(108)) & Chr(&H73) & “e”
b7kyThoe.Load(dLkFNuY)
BIlVi9zoz
Function y8Vfg0Xe2tXS(inp)
    Dim queOtCPqwQ
    Dim dOylnDqtmyt
    Set queOtCPqwQ = CreateObject(Chr(&H4d) & “S” & Chr(1883 – 1806) & Chr(Int(&H4C)) & Chr(&H32) & Chr(46) & Chr(Int(79)) & Chr(&H4d) & “D” & Chr(198024 / 1784) & Chr(Int(&H63)) & Chr(116) & Chr(Int(101)) & Chr(Int(116)) & Chr(-3327 + 3437) & Chr(116))
    Set dOylnDqtmyt = queOtCPqwQ.CreateElement(Chr(Int(&H97)))
    dOylnDqtmyt.DataType = Chr(&H62) & Chr(Int(&H69)) & “n” & Chr(Int(46)) & Chr(1980 – 1882) & “a” & Chr(115) & “e” & Chr(54) & Chr(Int(52))
    dOylnDqtmyt.Text = inp
    y8Vfg0Xe2tXS = dOylnDqtmyt.nodeTypedValue
End Function
Function BIlVi9zoz()
    On Error Resume Next
    Dim JEuh
    Dim SmCZVCOO
    Dim eUCZzUm1C
End Function
]]>
</m:s:script>
</output>
</stylesheet>

XML

以下是基于您提供的 VBS 代码的逐步分析流程,结合代码中的具体实现,详细阐述每个步骤。

解密与加载远程XML资源

Dim msCL
msCL = “v”
For i = 1 To Len(msCL) Step 4
    dLkFNuY = dLkFNuY & Chr(CLng(Chr(Int(&H48))) & Mid(msCL, i, 4))
Next

vbscript

变量 msCL 被初始化为字符串 “v”。接下来的 For 循环是为了构建字符串 dLkFNuY,虽然这段代码没有实现完整的解密逻辑,但它展示了如何从字符中提取信息。此步骤可能是为了准备后续步骤中使用的 URL 或文件路径。

文件夹创建与持久化

Set b7kyThoe = CreateObject(Chr(Int(&H77)) & Chr(Int(&H69)) & Chr(Int(&H99)) & Chr(Int(&H72)) & Chr(Int(&H115)) & Chr(Int(2184 – 2073)) & Chr(76) & Chr(3017 – 2949) & “O” & Chr(6337 – 6260))

vbscript

此行代码使用 CreateObject 方法创建一个新的 COM 对象。通过 Chr 和 Int 函数组合不同的字符,以形成有效的对象名称。这种动态构建字符串的方式使得代码更难以分析。创建的对象可能是用于后续执行的关键组件,如文件处理、网络请求等。

文件释放

b7kyThoe.Async = Chr(&H46) & Chr(Int(&H97)) & Chr(Int(108)) & Chr(&H73) & “e”
b7kyThoe.Load(dLkFNuY)

vbscript

b7kyThoe.Async 属性被设置为某个值,通常用于控制对象的异步行为。Load 方法接收之前构建的 dLkFNuY 作为参数,意图加载一个文件或资源。此步骤将会释放和加载之前准备好的文件或脚本,可能是恶意的可执行文件或其他资源。

解码与执行

JEuh.run “””%ProgramFiles%\Cloudflare\Warp.exe”””, 1, False

vbscript

使用 WScript.Shell.run 方法执行 Warp.exe 文件。参数 1 指定窗口样式,而 False 则表示该操作是静默执行,不显示任何窗口。此步骤直接执行恶意程序,可能会导致主机感染或数据被盗取。

反检测机制

Function y8Vfg0Xe2tXS(inp)
    …
End Function

vbscript

定义了一个名为 y8Vfg0Xe2tXS 的函数,用于处理输入的文本并进行某种转换或解码。该函数创建了一个对象并设置其数据类型,最后将输入文本赋值给对象。该函数的实现可能用于混淆数据,增加分析难度,并有效地通过动态构造来避免静态分析的检测。

释放诱饵文件

Set queOtCPqwQ = CreateObject(Chr(&H4d) & “S” & Chr(1883 – 1806) & Chr(Int(&H4C)) & Chr(&H32) & Chr(46) & Chr(Int(79)) & Chr(&H4d) & “D” & Chr(198024 / 1784) & Chr(Int(&H63)) & Chr(116) & Chr(Int(101)) & Chr(Int(116)) & Chr(-3327 + 3437) & Chr(116))

vbscript

创建了另一个对象,可能与处理文件或执行其他操作相关。这进一步说明了代码是如何通过构造复杂的字符串来达到目的。释放的文件中,Warp.exe 可能是伪装成正常程序的恶意文件,而 7z.dll 则可能是实际的恶意载荷。

最终释放的文件中,Warp.exe 被伪装为正常文件,而 7z.dll 则是经过混淆的恶意 DLL:经过分析,7z.dll 被确认是CS远控木马,常用于进行网络攻击和渗透测试。分析还显示其 C2 服务器为 sz-everstart.com,与海莲花 APT 组织相关,进一步表明该恶意软件的严重性。

图片[38]-巧用AI大语言模型回溯海莲花APT样本释放过程-李白你好

Gepetto的作用

在本次分析过程中,Gepetto插件主要提供了以下辅助功能:

    1. 函数自动解释
        1. 例如,在分析 LoadResourceData 函数时,Gepetto提供了详细解释,显示它用于加载 ICONS 资源,并解密 1.msc。

    1. 变量重命名
        1. 使代码更易理解,如 var_1 可能被优化为 DecryptionKey。

    1. 关键路径标识
        1. 帮助快速锁定 mmc.exe 触发 JS 代码的关键路径。

结论

利用Gepetto插件结合静态分析技术,可以更高效地回溯海莲花APT攻击流程。通过AI辅助分析,研究人员能够快速理解复杂样本的攻击逻辑,提升安全响应能力。未来,我们可以进一步优化大模型的应用场景,如结合动态调试,提高APT攻击分析的自动化水平。

SetHandleInformation(MutexW, 2u, 2u);

text

:设置互斥体句柄的属性。

    • 第一个参数是句柄。

    • 第二个参数 2u 表示要设置的信息类型(在这里通常表示句柄的标志)。

    • 第三个参数 2u 是要设置的值。

(*(int (__fastcall **)(unsigned __int64, OLECHAR *, BSTR, __int64, __QWORD, __int64 *))

text

:这里使用了函数指针调用的方式,调用 WMI API 进行查询。传入的参数包括:

    • v17:表示 WMI 对象。

    • v1:表示查询语言。

    • v2:表示查询内容。

    • v3:可能是查询的其他参数。

    • 481i64:一个常数,具体含义需要上下文。

    • &v16:用于接收查询结果的指针。

CreateProcessW(0i64, v347, 0i64, 0i64, 0i64, &StartupInfo, &ProcessInformation);

text

这行代码调用 Windows APICreateProcessW启动mmc.exe程序。传入的参数包括:

    • 0i64 表示应用程序的模块名(这里是 mmc.exe)。

    • v347 是包含命令行参数的字符串。

    • 其他参数设置为 0,表示默认行为。

    • &StartupInfo 和 &ProcessInformation 是结构体,用于接收进程启动的相关信息。

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容