恶意代码分析:最近帮朋友分析了一个APT组织海莲花样本,尝试使用AI插件来分析完整的文件释放过程,留此记录。海莲花(OceanLotus,APT32)是一支活跃在东南亚地区的APT(高级持续性威胁)组织,主要针对政府、军工、科研、能源等关键基础设施进行攻击。本次分析使用了IDA Pro的Gepetto插件,该插件通过集成大语言模型(LLM),可帮助研究人员更高效地进行恶意软件的静态分析。
文章作者:网络安全情报攻防站
原文链接:https://www.libaisec.com/1181.html
1►
Gepetto插件介绍
Gepetto是一个Python插件,支持多个LLM(如GPT-4o、Llama 3),可用于分析IDA Pro反编译的函数代码,并提供函数解释和变量重命名功能。主要特点包括:
●通过LLM自动解释函数逻辑,减少人工分析负担。
●变量、函数命名优化,提高代码可读性。
●兼容OpenAI、Ollama、Groq等多种AI模型。
安装Gepetto后,可在IDA Pro的伪代码窗口中右键调用它来分析目标函数,同时支持CLI模式用于查询特定代码片段。
2►
样本分析
本次分析的样本涉及多个变种,其中一个核心文件为 s.exe.v(MD5: 7A0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)。
沙箱检测什么都没出,看到存在反逆向工程。
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资源中的二进制数据。
●在 ProgramFilesCloudflare目录下创建Warp.exe(白文件)和7z.dll(恶意DLL)。
●通过 WScript.Shell.run运行 Warp.exe,实现持久化。
(4)最终Payload加载与C2通信
●7z.dll通过 DLL劫持 被 Warp.exe载入。
●该DLL 进一步解密 ShellCode并执行。
●连接C2服务器 sz-everstar[.]com,进行远控通信。
3►
详细静态分析过程
样本代码显示出种多反调试机制,在确保程序在被调试时不会正常运行。
检测调试器:
●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函数,阻止进一步的调试操作和分析。
采用了通过触发异常来进行反调试的技术
设置异常处理程序:使用 SetUnhandledExceptionFilter函数注册一个顶级异常过滤器TopLevelExceptionFilter,以便在发生未处理异常时执行自定义的处理逻辑。
触发异常:调用 RaiseException(0xC000008E, 0, 0, 0)以人为方式引发异常。这个异常代码对应于特定类型的错误,通常与内存访问或状态相关。
重新设置异常处理:在引发异常后,再次调用 SetUnhandledExceptionFilter来设置新的异常处理程序。
异常条件判断:接下来,通过检查 dword_14001C8E4的值来决定是否进入无限循环。如果该值为真,程序将进入一个无限循环,每次循环中调用Sleep(0x9C40u)(即约 40 秒),进一步减缓分析速度。
修改 OS 版本信息:之后,程序还会对 dwOSVersionInfoSize和其他操作系统版本信息字段进行修改,以伪装其运行环境,从而增加分析难度。
通过植入 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)。
通过检查 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 秒。
●这段代码的意图是,如果检测到进程被调试,则释放内存并使程序进入休眠状态,以防止进一步的调试。
创建一个名为 "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,表示正常结束。
监视对动态分配内存块的写入操作,通过 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。
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 秒)。
●由于这是一个无限循环,程序将持续休眠直到被外部干预或进程结束。
检测当前运行的程序是否是已知的沙箱、如果发现当前程序的名称与已知的沙箱或恶意软件匹配,程序将进入一个无限循环,以防止执行进一步的操作。
字符串数组初始化:
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循环中自增,具体用途和上下文不清楚,可能用于某种计数或状态跟踪。
检测当前运行程序的用户名,以判断是否在虚拟机、沙箱或由安全研究人员使用的环境中。如果用户名与数组中列出的任何名称匹配,程序可以采取适当的措施,例如进入无限循环或终止运行,以防止在这些环境中被分析。
字符串数组初始化:
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数组中的每个字符串,以判断当前用户名是否匹配其中之一。
检测当前计算机的名称,以识别是否在沙箱或虚拟机环境中运行。通过检查计算机名称是否与预定义的名称列表相匹配,采取措施,阻止程序运行或执行特定逻辑,以防止在这些环境中被分析。
字符串数组初始化:
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。
通过 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 操作后进行。
检测并访问系统中的物理驱动器。通过获取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,并关闭句柄。
检测当前环境是否为虚拟机或沙箱。通过检查设备描述字符串,程序可以识别出常见的虚拟化环境,如 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 秒)。这表示如果鼠标位置在这段时间内没有变化,程序将持续休眠,可能是为了阻止进一步的操作或监控。
检测当前环境是否为虚拟机。通过 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。
●循环将持续执行,可能是为了不断检查或处理虚拟机相关的数据。
检测与虚拟机相关的服务是否在系统中运行。通过打开服务控制管理器并尝试获取服务信息,程序可以识别出特定的虚拟机服务。如果服务控制管理器的句柄获取失败,程序将返回错误,表明无法继续运行。
虚拟机服务名称数组:
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,这些变量可能在后续处理中用于存储服务信息或控制服务的恢复。
检测 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);,确保无论如何都清理资源。
●继续处理后续逻辑。
通过执行 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);在条件判断的外部再次调用,确保无论如何都清理资源,防止内存泄漏。
通过执行 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:表示端口连接器的信息。
通过访问系统注册表中与虚拟化相关的键值,检测当前系统是否在虚拟机环境中运行。通过检查特定的注册表键和子键,程序能够识别出常见的虚拟化平台(例如 VirtualBox、VMware 等)。
注册表查询:
●代码通过 RegOpenKeyExW打开 Windows 注册表中的特定键以获取与虚拟机相关的信息。主要路径为 HKEY_LOCAL_MACHINESystemCurrentControlSetServicesDiskEnum,这是存储有关磁盘驱动程序的信息的位置。
检测虚拟机类型:
●v403数组中定义了一些常见虚拟机的标识符,如 qemu、vmware、vbox(VirtualBox)、xen、VM和 Virtual。这些字符串用于后续比较,以检测当前系统是否运行在虚拟机中。
计数和循环:
●使用 RegQueryValueExW函数查询 Count值,获取可能的磁盘数量。然后通过遍历 pcBuffer,对每一个可能的磁盘进行检测。
遍历子键:
●代码定义了一系列与 VirtualBox 相关的注册表子键。这些子键主要包含与 VirtualBox 相关的 ACPI 和服务信息。
●在 do循环中,尝试打开每一个子键,检查是否存在以确认系统是否在虚拟机环境中运行。
错误处理:
●如果在打开注册表键时发生错误(返回值为假),则使用 RegCloseKey关闭打开的键,并跳转到LABEL_725,可能是进行清理或其他后续操作。
通过打开与 VirtualBox 相关的管道和设备,检测当前系统是否在运行 VirtualBox 虚拟机。通过查找 VirtualBox 相关的窗口,程序可以进一步确认虚拟机的状态。
打开虚拟机管道:
●代码定义了一个lpFileName数组,包含了一些与 VirtualBox 相关的命名管道和设备名称。例如:
●\.VBoxMiniRdrDN
●\.VBoxGuest
●\.pipeVBoxMiniRdr
●\.VBoxTrayIPC
●这些名称是 VirtualBox 在宿主机和虚拟机之间进行通信和数据传输时使用的。
创建文件句柄:
●在 do循环中,代码尝试通过 CreateFile函数打开这些命名管道和设备。参数 0x80000000表示以只读方式打开,1表示共享模式,其他参数用于指定安全属性和创建方式。
●如果 CreateFile成功(返回的句柄不是 INVALID_HANDLE_VALUE),则关闭该句柄并将MEMORY[0]设置为 10。这可能是用于记录成功打开的管道数量或状态。
查找窗口:
●FindWindow函数用于查找与 VirtualBox 相关的窗口,分别使用 VBoxTrayToolWndClass和 VBoxTrayToolWnd作为窗口类名和窗口标题。
●如果找到任何一个窗口,则进入一个无限循环,调用 Sleep(0x7Bu);休眠约 123 毫秒。这样的设计可能是为了等待某个状态或事件。
无限循环:
●如果找到 VirtualBox 的窗口,代码将会在无限循环中停留,可能是为了防止程序继续执行,保持在这个状态下。
检测 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,则表示出现错误,后续处理逻辑可能在此处实现(虽然具体的错误处理代码在这里未显示)。
识别虚拟机环境中的特定特征,尤其是 VirtualBox 相关的特征。通过使用无限循环和Sleep函数,程序可以在满足某些条件时保持挂起状态,从而防止代码被进一步执行或分析。此类技术常见用于检测和规避虚拟机环境。
函数调用:
●代码中有多个 sub_xxxxxxx函数调用,这些函数的具体实现没有给出,但它们的返回值被用于判断是否进入后续的无限循环。这些函数可能用于检测某些特定的环境特征,尤其是与 VirtualBox 相关的特征。
无限循环:
●每个 if语句后面都有一个无限循环,使用 Sleep(0x9C40u)来使线程休眠(大约 40000 毫秒,即 40 秒)。这种设计通常用于在检测到特定条件时阻止进一步的代码执行。
●这可能是为了防止代码在虚拟环境中执行,或者是为了在检测到虚拟机相关的条件时保持一个挂起状态。
检测特征:
●每个 sub_14000CFF0、sub_14000D6E0、sub_14000D220、sub_14000D450和 sub_14000CB60可能都与特定的虚拟机特征或状态检测有关。这些特征可能包括正在运行的进程、特定的设备或其他环境变量。
目的:
●这段代码的主要目的是检测当前执行环境是否为VirtualBox。通过对特定条件的检查,如果满足,程序将进入一个无限循环,从而停止进一步的执行。这种行为通常用于保护自身不被调试或分析。
检测 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中。
识别 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 地址,程序可能采取特定的行为,例如停止执行或进入错误处理。
检测 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),这意味着程序将正常退出,但可能并不是预期的行为。
检测虚拟机环境,通过检查特定的服务注册表项来判断当前系统是否在虚拟机中运行。注册表中存在这些服务通常表明系统正在虚拟环境中运行,因此这段代码试图确认这一点。如果找不到任何服务,程序将退出。
服务名称初始化:
●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,可能是用于后续的逻辑或计数。
它通过对输入数据(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]计算得到的结果。
定义一组硬编码的值,用于与计算得到的密钥或哈希值进行比较,常用于安全性检查或防篡改措施。
数组初始化:
●v375是一个包含16 个 32 位无符号整数的数组,每个元素都被初始化为一个特定的十六进制值。
●这些值很可能是用于验证某种密钥或哈希值的硬编码常量。
硬编码值的用途:
●这些硬编码的值通常用于比较,通过将计算得到的哈希值或密钥与这些值进行比较,来确定是否匹配。这样的设计常见于软件中,以防止未授权的访问或篡改。
LODWORD操作:
●LODWORD(pcbBuffer) = 0;将 pcbBuffer的低字(低32位)设置为 0。pcbBuffer可能是一个指针或一个结构体中的字段,这里将其清零可能是为了初始化或重置。
从程序资源中加载名为 "ICONS" 的数据,并对其进行解密。解密使用了一个硬编码的密钥(存储在v411中)。使用 VirtualAlloc申请内存,利用 FindResourceW和 LoadResource加载资源,最后通过异或操作解密数据。
-
1.内存分配:
●VirtualAlloc(0i64, 0x100000ui64, 0x1000u, 4u);这行代码申请了一个 1MB 的内存块,可能用于存储解密后的数据。
●v331 = VirtualAlloc(0i64, 2 * v329, 0x1000u, 4u);这行申请了一个内存块,其大小为 2 * v329,用于存储解密后的资源数据。
-
2.资源加载:
●FindResourceW和 LoadResource用于查找和加载名为 "ICONS" 的资源。ResourceW存储了资源句柄,Resource存储了加载的资源数据。
-
3.资源大小:
●v329 = SizeofResource(0i64, ResourceW);这行代码获取了资源的大小,并将其存储在 v329中。
-
4.解密过程:
●if ((unsigned int)v330 >> 2)这一条件检查v330的值(即资源的大小)是否大于0。
●v334 = v331;该行初始化了指向已分配内存的指针v334。
●v335 = Resource - (_BYTE)v331;计算出资源数据和解密内存之间的偏移量。
●在do...while循环中,使用异或操作解密数据:*(v334 - 1) = *(DWORD *)((char *)v334 + v335 - 4) ^ *((DWORD *)v411 + v336);这一行从资源中读取数据并与密钥数据(存储在v411中)进行异或运算,解密后的数据被写入到 v334指向的内存中。
-
5.轮询解密:
●while (v332 < (unsigned int)v330 >> 2);循环条件判断,确保解密操作遍历所有数据。
初始化一个包含常见调试和分析工具进程名的字符串数组,以便在运行时进行检测。
数组初始化:
●v402是一个字符串数组,包含了多个常见的调试和分析工具的进程名。
●这些工具包括调试器(如 OllyDbg 和 WinDBG)、资源监控工具(如 Process Hacker 和 Wireshark)、反汇编工具(如 IDA Pro)、以及内存修改工具(如 Cheat Engine)。
用途:
●这些进程名的列举通常用于检测系统中是否存在这些工具,以防止程序被逆向工程或调试。
●这种检测逻辑在恶意软件和某些商业软件中非常常见,目的是增加对逆向工程的难度。
反分析机制:
●在程序运行时,可能会通过查询系统进程列表,检查是否有与 v402中的进程名匹配的进程。如果发现有匹配项,程序可能会采取措施,例如退出、隐藏其行为或改变其执行路径。
●这种方法能够有效地阻止安全研究人员或攻击者分析程序的行为,从而保护软件的知识产权或防止恶意行为。
将解密的数据写入到 %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从多字节字符转换为宽字符格式,可能用于后续的文件操作或进程创建。
创建并执行 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;表示程序正常结束。
通过利用 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 漏洞,攻击者可以在不受保护的环境中进行攻击。
js代码解密后获得vbs脚本。
<?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>
以下是基于您提供的 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
变量 msCL被初始化为字符串 "v"。接下来的 For 循环是为了构建字符串 dLkFNuY,虽然这段代码没有实现完整的解密逻辑,但它展示了如何从字符中提取信息。此步骤可能是为了准备后续步骤中使用的 URL 或文件路径。
文件夹创建与持久化
Setb7kyThoe = 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))
此行代码使用 CreateObject方法创建一个新的COM 对象。通过 Chr和 Int函数组合不同的字符,以形成有效的对象名称。这种动态构建字符串的方式使得代码更难以分析。创建的对象可能是用于后续执行的关键组件,如文件处理、网络请求等。
文件释放
b7kyThoe.Async = Chr(&H46) &Chr(Int(&H97)) &Chr(Int(108))&Chr(&H73) &"e"
b7kyThoe.Load(dLkFNuY)
b7kyThoe.Async属性被设置为某个值,通常用于控制对象的异步行为。Load方法接收之前构建的 dLkFNuY作为参数,意图加载一个文件或资源。此步骤将会释放和加载之前准备好的文件或脚本,可能是恶意的可执行文件或其他资源。
解码与执行
JEuh.run """%ProgramFiles%CloudflareWarp.exe""", 1, False
使用 WScript.Shell.run方法执行 Warp.exe文件。参数 1指定窗口样式,而 False则表示该操作是静默执行,不显示任何窗口。此步骤直接执行恶意程序,可能会导致主机感染或数据被盗取。
反检测机制
Functiony8Vfg0Xe2tXS(inp)
...
End Function
定义了一个名为 y8Vfg0Xe2tXS的函数,用于处理输入的文本并进行某种转换或解码。该函数创建了一个对象并设置其数据类型,最后将输入文本赋值给对象。该函数的实现可能用于混淆数据,增加分析难度,并有效地通过动态构造来避免静态分析的检测。
释放诱饵文件
SetqueOtCPqwQ = 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))
创建了另一个对象,可能与处理文件或执行其他操作相关。这进一步说明了代码是如何通过构造复杂的字符串来达到目的。释放的文件中,Warp.exe可能是伪装成正常程序的恶意文件,而 7z.dll则可能是实际的恶意载荷。
最终释放的文件中,Warp.exe被伪装为正常文件,而 7z.dll则是经过混淆的恶意 DLL:经过分析,7z.dll被确认是CS远控木马,常用于进行网络攻击和渗透测试。分析还显示其 C2 服务器为 sz-everstart.com,与海莲花 APT 组织相关,进一步表明该恶意软件的严重性。
4►
Gepetto的作用
在本次分析过程中,Gepetto插件主要提供了以下辅助功能:
-
1.函数自动解释
●例如,在分析 LoadResourceData函数时,Gepetto提供了详细解释,显示它用于加载 ICONS资源,并解密 1.msc。
-
2.变量重命名
●使代码更易理解,如 var_1可能被优化为 DecryptionKey。
-
3.关键路径标识
●帮助快速锁定 mmc.exe触发 JS代码的关键路径。
5►
总结
利用Gepetto插件结合静态分析技术,可以更高效地回溯海莲花APT攻击流程。通过AI辅助分析,研究人员能够快速理解复杂样本的攻击逻辑,提升安全响应能力。未来,我们可以进一步优化大模型的应用场景,如结合动态调试,提高APT攻击分析的自动化水平。
SetHandleInformation(MutexW, 2u, 2u)
:设置互斥体句柄的属性。
●第一个参数是句柄。
●第二个参数 2u表示要设置的信息类型(在这里通常表示句柄的标志)。
●第三个参数 2u是要设置的值。
(*(int (__fastcall **)(unsigned __int64,
OLECHAR *, BSTR, __int64, __QWORD, __int64 *))
:这里使用了函数指针调用的方式,调用 WMI API 进行查询。传入的参数包括:
●v17:表示 WMI 对象。
●v1:表示查询语言。
●v2:表示查询内容。
●v3:可能是查询的其他参数。
●481i64:一个常数,具体含义需要上下文。
●&v16:用于接收查询结果的指针。
CreateProcessW(0i64, v347, 0i64, 0i64,
0i64, &StartupInfo, &ProcessInformation);
这行代码调用 Windows APICreateProcessW启动mmc.exe程序。传入的参数包括:
●0i64表示应用程序的模块名(这里是 mmc.exe)。
●v347是包含命令行参数的字符串。
●其他参数设置为 0,表示默认行为。
●&StartupInfo和 &ProcessInformation是结构体,用于接收进程启动的相关信息。
6►
李白你好网安社区V1.0上线
7►
往期精彩
网络安全情报攻防站V1.0 正式上线【网空数据泄露监测引擎抢先限时免费用】 !!!
内网横向扩大战果,RDP远程桌面密码凭证获取
原文始发于微信公众号(云淡纤尘):巧用AI回溯海莲花APT样本释放过程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论