目标版本: 27.0.0.0 | 分析日期: 2026/03 | 作者: DEAN
提示:为了提升阅读体验,本文及代码经过 AI 润色处理。受限于 AI 的局限性,部分逻辑或表述可能存在偏差,请以实际情况结果为准,欢迎指正
1 环境准备与解包
该程序使用 ConfuserEx 混淆 + 可能的自定义保护壳。
脱壳后使用 dnSpy / ILSpy 打开分析。
2 静态分析
2.1 软件基本信息
软件存在四种授权等级(scheme),内部用整数表示:
| 值 |
名称 |
说明 |
| 0 |
Common (FREE) |
免费版,默认 |
| 1 |
Silver |
银版 |
| 2 |
Gold |
金版 |
| 3 |
Platinum |
铂金版(全功能) |
2.2 程序退出保护
调试时发现程序启动后自动退出,无异常抛出,时间也不稳定。初步判断为计时器保护。
在 Class31.Main() 中发现启动前设置了一个 6 秒定时器:
// Class31.cs:262-265 (GUI 启动路径)
Class31.timer_0 = new Timer();
Class31.timer_0.Interval = 6000;
Class31.timer_0.Tick += Class31.smethod_1;
Class31.timer_0.Start();
smethod_1 78行,判断文件大小受否匹配,检查自身 exe 文件大小是否为 3,939,328 字节,是则停止定时器,否则退出程序
修补方法: 将退出函数调用 NOP 掉即可。
2.3 激活入口定位
关键词搜索 common 找到一些结果。逐一排查后发现 GClass32.smethod_13(int) 是核心激活查询函数:
// GClass32.cs:464-486
public static string smethod_13(int int_0)
{
int num = 2;
Class39 @Class = new Class39();
bool flag = false;
string text;
do
{
text = @class.method_0(ref flag);
}
while (!flag);
if (text == null) return null;
num++;
string[] array = text.Split(new char[] { ';' });
if (array.Length == 6)
{
return array[int_0];
}
return null;
}
该函数通过 Class39.method_0() 获取激活数据字符串,按 ; 分割后返回指定索引字段。调用 smethod_13(3) 即获取用户等级(索引 3)。
2.4 混淆与反调试逻辑
Class39.method_0() 是激活数据读取的核心入口,包含三层随机路由(反调试/反静态分析):
// Class39.cs:10-62
public string method_0(ref bool bool_0)
{
int num = new Random(DateTime.Now.Millisecond).Next(3);
// 路径 0 (num==0): 从 activationKey.vkf 文件读取
if (num == 0) {
// 读取 activationKey.vkf → 返回原始内容
// 如果内容 == GClass37.string_10 (CPU ID),则 flag 翻转后返回
}
// 路径 1 (num==1): 从注册表读取
if (num == 1) {
// HKCU\Software\SUU Design\Video Thumbnails Maker
// 读取加密的注册表值
}
// 路径 2 (num==2): 真正激活验证入口
if (num == 2) {
bool_0 = true; // ← 标记为"完成"
return GClass59.smethod_22(); // ← 真正的验证函数
}
}
关键逻辑: bool_0 参数是循环控制标志。当 num != 2 时 bool_0 = false,外层 do-while(!flag) 会继续循环;只有 num == 2 时 bool_0 = true,循环才会退出并返回真正的验证结果。
Class40.method_0() 是类似的混淆函数,用于等级验证分支,包含 4 条随机路径(num = 0,1,2,3),其中 num == 1 是等级判断的真正路径。
2.5 核心验证流程
进入 GClass59.smethod_22() 后(混淆还原后的核心逻辑),验证流程如下:
┌─────────────────────────────────────────────────┐
│ 1. 检查 activationKey.vkf 文件是否存在 │
│ └─ 存在 → 读取文件内容 │
├─────────────────────────────────────────────────┤
│ 2. GZip 解压 (GClass33.smethod_1) │
│ └─ 格式: [4字节原始长度] + [GZip压缩数据] │
├─────────────────────────────────────────────────┤
│ 3. 分割数据 │
│ ├─ array2 = 前 (总长-40) 字节 → 用户数据区 │
│ └─ array3 = 后 40 字节 → DSA 签名 │
├─────────────────────────────────────────────────┤
│ 4. 从 array2 中提取: │
│ ├─ 前 4 字节 = 解压后用户数据长度 (userInfoLen)│
│ └─ 剩余 = GZip(用户数据) │
├─────────────────────────────────────────────────┤
│ 5. GZip 解压 → 得到 AES 加密后的用户数据 │
├─────────────────────────────────────────────────┤
│ 6. AES/Rijndael 解密 → 得到 DSA 公钥 XML │
│ └─ smethod_3(base64, key="Ueiekfnv3G") │
├─────────────────────────────────────────────────┤
│ 7. 解析 DSA 公钥参数 (DSAParameters) │
├─────────────────────────────────────────────────┤
│ 8. 对用户数据计算 SHA1 哈希 │
├─────────────────────────────────────────────────┤
│ 9. DSA 签名验证 │
│ └─ smethod_21(hash, signature, pubkey, "SHA1")│
│ └─ flag5 = true → 验证通过 │
├─────────────────────────────────────────────────┤
│ 10. PBKDF2 + AES 解密用户数据 │
│ └─ smethod_1(cipher, pwd, salt, ...) │
│ → 得到 "email;machineId;?;level;name;machineId" │
└─────────────────────────────────────────────────┘
关键验证点:
flag5 = GClass32.smethod_21(array4, array3, dsaParameters, "SHA1") — DSA 签名验证
- 若为
false,函数直接返回,激活失败
- 这是核心校验点,可通过替换公钥+自建签名来绕过
2.6 密钥文件格式
激活文件 activationKey.vkf 的二进制结构:
activationKey.vkf
├── [4 bytes] 外层 GZip 解压后的数据总长度 (innerLen)
└── [N bytes] GZip 压缩的内层数据
│
└── 解压后 (innerLen 字节)
├── [4 bytes] 用户数据明文长度 (userInfoLen)
├── [M bytes] GZip 压缩的用户数据
│ │
│ └── 解压后 (userInfoLen 字节)
│ └── AES 加密的用户数据 (Rijndael-CBC)
│ │
│ └── 解密后 (UTF-8 明文)
│ "email;machineId;0;level;name;machineId"
│
└── [40 bytes] DSA 签名 (SHA1)
- 用户数据先经 AES 加密
- AES 密文经 GZip 压缩
- 拼接
[userInfoLen | Gzip(AES(userinfo)) | DSA_Signature]
- 整体再经一次 GZip 压缩
- 最外层追加
[innerLen] 前缀
2.7 机器码验证
GClass37.string_10 存储的是 CPU ID(通过 WMI 查询 Win32_Processor 获取)。
验证逻辑分布在三个函数中:
| 函数 |
文件 |
作用 |
smethod_7() |
GClass32.cs:333 |
比较密钥文件中的机器码与 CPU ID 是否完全匹配 |
smethod_8() |
GClass32.cs:340 |
同上,额外统计 'a' 字符数量(可能是调试/混淆残留) |
smethod_9() |
GClass32.cs:359 |
同上,增加了一个 flag 翻转逻辑 |
这些函数最终都调用 smethod_12(5) 获取密钥文件中索引 5 的字段(第二个机器码),与 GClass37.string_10 比较。
3 加密算法梳理
3.1 GZip 压缩
压缩 (GClass32.GClass33.smethod_0):
// 输出格式: [4字节原始长度 (int32)] + [GZip压缩数据]
public static byte[] smethod_0(byte[] byte_0)
{
MemoryStream ms = new MemoryStream();
GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true);
gzip.Write(byte_0, 0, byte_0.Length);
gzip.Close();
byte[] compressed = ms.ToArray();
byte[] result = new byte[compressed.Length + 4];
Buffer.BlockCopy(compressed, 0, result, 4, compressed.Length);
Buffer.BlockCopy(BitConverter.GetBytes(byte_0.Length), 0, result, 0, 4);
return result;
}
解压 (GClass32.GClass33.smethod_1):
// 输入格式: [4字节原始长度] + [GZip压缩数据]
public static byte[] smethod_1(byte[] byte_0)
{
int originalLen = BitConverter.ToInt32(byte_0, 0);
MemoryStream ms = new MemoryStream();
ms.Write(byte_0, 4, byte_0.Length - 4);
byte[] result = new byte[originalLen];
ms.Position = 0;
GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress);
gzip.Read(result, 0, result.Length);
return result;
}
3.2 Rijndael (AES) 加密
程序中使用了两套 Rijndael 加密方案:
方案 A: 简单 Rijndael(用于公钥/字符串加密存储)
// GClass32.GClass34.smethod_0 / smethod_4
// 加密:
ICryptoTransform enc = Rijndael.Create().CreateEncryptor(
new PasswordDeriveBytes(key, null).GetBytes(16), // 密钥: 从密码派生16字节
new byte[16] // IV: 全零
);
// 解密:
ICryptoTransform dec = Rijndael.Create().CreateDecryptor(
new PasswordDeriveBytes(key, null).GetBytes(16),
new byte[16]
);
已知密钥:
| 密文 |
密钥 |
用途 |
yOteMUrL6y97WNI0fjKqO3Ia0SNQGFpfGRL/xGaMNwU0n2hsEU4NMeb4sel3AtdQiOH+JD9Djo/6tWea3q5BP0M6b+Y32zKjobwawFPr+ijPZqBTSTpabFedbGWz8LyKMdkgwp2UPPaHYXF+dN+aEVV7RJ3v74QuCYATjYJeYINED+jaVzzCmJpmBuXwnE4tsOXfbBGqnTujkd1ju4ALYSyPJYnmdFRxnx6fdepdcZv5gA0rhw6BoW7aH8W4+uumtxgIfKWCVULBqnArQTnmaglkBZAhZh2dhSfsQgC3G0M1aaVETLyl5awlz7yaRl8j+pemjLPkBa1wMYgSs6gfQ6EWGe+cLEfzLT2AQDeF9aNOzFplKg1eGjE62TdAEu1ukFkPBfj0nqMOAJdYWwZrQlw0F0uDPtvPia24suG6fDv5Ny9WZg/YqWp9i5paOveirXTAgPCg/FBOpeYwtPmX8YrwlcqmnnBugmY658ANdwespat7kY51VL5A3UbL6lf20xlDsAuXlc8Y9hCptx6ZFA6cFA9ZqU2GoXwnQILoweA/9F9Acaf+8YQGzb11T1Ken5vBv9ewlmpoU16rk7+YlHGahue8seccOPFbuVG2mLMfNeJIAUWAy+iMeJsKfilv5XbWKMthKqrEpqDkjq8Cax/CpFipSnsM/sXovipYVF/gHM6eQAT7PIhd2rvdcRAuWGKop1Y75iaRKS6uCGrFTy/LEzaWdevfO4lfyUutA6BDtF1wApJbpgtItD92fT60WnSyvNOi0fien2ttXRyURqGu60rNGAGTKaGzSKT0AUJv+vHyakXeP/Lbpvs6jcm64UQ1wDWkl2+rIyI8V8o9UKo56Um9+XQtr4W/AR1tf94jLsgkB8LRgJgbPTvc7fL1LERim5kn9eQYtmfrFHmKX7L6mn60PX6Lqu/X7NLhQVTbPFOdbDOwd2bNtbQCFFh2j2aO52i3iRh0nHHXMotRdncdMBjiYn7Hjdl3JLNXhzoSOxLFphKUH0bq5uHJ6QRw78D0BHAu56Hjww7OeU1D96DGXN90uc2wgprykHjvocjVpXEx7q/pXaLwUKaO7T+lgVBHVmfirmrBNjnLQ4MNCg== |
Ueiekfnv3G |
存储的 DSA 公钥 XML |
NDjhKgxArf2+imyKVp6R9g== |
Am4ehpMT |
注册表键名 |
03GLb1NkWGQYpKek94kcYQ== |
6DAnjEv48H |
WMI 类名 (Win32_Processor) |
qy7IRYdbBkGLZ9Ll9EIrfg== |
6DAnjEv48H |
WMI 属性名 (ProcessorId) |
方案 B: PBKDF2 + Rijndael-CBC(用于用户数据加密)
// GClass32.GClass35.smethod_1 (解密)
byte[] keyBytes = new PasswordDeriveBytes(
password, // 密码
Encoding.ASCII.GetBytes(salt), // 盐
hashAlgorithm, // 哈希算法 (SHA1)
iterations // 迭代次数
).GetBytes(keySize / 8);
ICryptoTransform dec = new RijndaelManaged {
Mode = CipherMode.CBC
}.CreateDecryptor(keyBytes, Encoding.ASCII.GetBytes(iv));
3.3 DSA 签名验证
// GClass32.smethod_21 — 验证签名
public static bool smethod_21(byte[] hash, byte[] signature,
DSAParameters pubKey, string hashAlg)
{
using (DSACryptoServiceProvider dsa = new DSACryptoServiceProvider())
{
dsa.ImportParameters(pubKey);
DSASignatureDeformatter verifier = new DSASignatureDeformatter(dsa);
verifier.SetHashAlgorithm(hashAlg);
return verifier.VerifySignature(hash, signature);
}
}
修补思路: 替换程序内嵌的 DSA 公钥为我们自己生成的密钥对中的公钥,然后用私钥对激活数据签名即可。
3.4 用户数据加密
用户数据使用 PBKDF2 派生密钥 + AES-CBC 加密:
// 加密参数 (从逆向分析得出)
password = "k5xfF62JFkhYc"
salt = "Sirhjd6S3m237"
hashAlg = "SHA1"
iterations = 6
iv = "@DC2c3W2s5M3x5LT" // 16字节
keySize = 256
4 修补方案
4.1 替换 DSA 公钥
程序编译后的 .NET exe 中,字符串常量存储格式为:
[[长度前缀 (可变长)] [UTF-16 字符串内容] [0x00 终止符]]
长度前缀编码规则 (类似 LEB128):
| 字节数 |
编码方式 |
示例 |
| 1 byte |
直接存储长度值 (最高位为 0) |
长度 50 → 0x32 |
| 2 bytes |
首字节 0x80 | (len >> 8),次字节 len & 0xFF |
长度 300 → 0x81 0x2C |
| 3 bytes |
首字节 0xC0 | (len >> 16),后续为 len 的高低位 |
- |
| 4 bytes |
首字节 0xE0 | (len >> 24),后续3字节为 len |
- |
修补步骤:
- 生成新的 DSA 1024 密钥对
- 将公钥 XML 用
Ueiekfnv3G 作为密钥进行 Rijndael 加密 → Base64
- 在 exe 中定位原始公钥字符串(方案 A 中的那个超长 Base64 串)
- 计算新字符串的 UTF-16 长度
- 确保新长度 ≤ 原长度(不足用
\0 填充)
- 写入新的长度前缀 + 新字符串 + 终止符
4.2 .NET 字符串常量存储格式
详见 4.1 节
5 Keygen 实现
5.1 激活文件生成流程
用户输入 (email, name, level, serial)
│
▼
┌──────────────────────────────────────┐
│ 1. 拼接用户数据字符串 │
│ "email;machineId;0;level;name;machineId" │
├──────────────────────────────────────┤
│ 2. PBKDF2 + AES-CBC 加密用户数据 │
│ → Base64 编码 │
├──────────────────────────────────────┤
│ 3. 对加密后的用户数据计算 SHA1 哈希 │
├──────────────────────────────────────┤
│ 4. 用 DSA 私钥对哈希签名 → 40字节签名 │
├──────────────────────────────────────┤
│ 5. 拼接: [userInfoLen | Gzip(AES密文) | 签名] │
├──────────────────────────────────────┤
│ 6. 整体 GZip 压缩 │
├──────────────────────────────────────┤
│ 7. 输出: [innerLen | GZip(步骤5数据)] │
│ → activationKey.vkf │
└──────────────────────────────────────┘
5.2 字段说明
激活数据字符串格式(6 个分号分隔字段):
email;machineId;unknown;level;name;machineId
| 索引 |
字段 |
说明 |
| 0 |
email |
用户邮箱 |
| 1 |
machineId |
机器码(CPU ID,从 serial 解密得到) |
| 2 |
unknown |
未知字段(我设定为为 0,未找到任何调用处) |
| 3 |
level |
授权等级: 0=Common, 1=Silver, 2=Gold, 3=Platinum |
| 4 |
name |
用户名 |
| 5 |
machineId |
机器码(与索引 1 相同) |
机器码来源: 用户输入的 serial number 是加密的 CPU ID,使用密钥 KJdPXjdu 通过 Rijndael 解密得到。
5.3 测试代码
论坛不能发布成品参考:Github
注意:本工具仅供学习研究使用,请勿用于非法用途。
6 附录
6.1 关键类索引
| 类名 |
文件 |
作用 |
Class31 |
Class31.cs |
程序入口 Main(),定时器保护,命令行处理 |
Class39 |
Class39.cs |
激活数据读取(混淆路由:注册表/文件/核心验证) |
Class40 |
Class40.cs |
等级验证(混淆路由) |
GClass32 |
GClass32.cs |
核心验证逻辑、加密工具类集合 |
GClass32.GClass33 |
GClass32.cs:649 |
GZip 压缩/解压 |
GClass32.GClass34 |
GClass32.cs:687 |
Rijndael 加密/解密(方案 A) |
GClass32.GClass35 |
GClass32.cs:765 |
PBKDF2 + Rijndael 加密/解密(方案 B) |
GClass37 |
GClass37.cs |
全局状态类,存储 CPU ID (string_10) 等 |
GClass59 |
GClass59.cs |
注册表读写、设置加载、核心激活验证 smethod_22() |
6.2 加密参数表
| 用途 |
算法 |
密钥 |
IV |
盐 |
迭代 |
哈希 |
| 公钥存储加密 |
Rijndael-128 |
Ueiekfnv3G |
全零 16B |
- |
- |
- |
| 注册表键名加密 |
Rijndael-128 |
Am4ehpMT |
全零 16B |
- |
- |
- |
| WMI 类名加密 |
Rijndael-128 |
6DAnjEv48H |
全零 16B |
- |
- |
- |
| 用户数据加密 |
Rijndael-256-CBC |
k5xfF62JFkhYc |
@DC2c3W2s5M3x5LT |
Sirhjd6S3m237 |
6 |
SHA1 |
| 机器码加密 |
Rijndael-128 |
KJdPXjdu |
全零 16B |
- |
- |
- |
| 签名算法 |
DSA-1024 |
自定义密钥对 |
- |
- |
- |
SHA1 |
6.3 成功测试
贴两张注册成功测试图和使用效果
使用软件制作效果(主要用于创建视频文件的缩略图(截图、预览、屏幕列表)、动画和屏幕截图。该实用程序可用于家庭视频编目和网络视频共享,更多内容请参考官网说明)