Video Thumbnails Maker 逆向分析 & 补丁记录
目标版本: 27.0.0.0 | 分析日期: 2026/03 | 作者: DEAN
提示:为了提升阅读体验,本文及代码经过 AI 润色处理。受限于 AI 的局限性,部分逻辑或表述可能存在偏差,请以实际情况结果为准,欢迎指正
目录
1 环境准备与解包
该程序使用 ConfuserEx 混淆 + 可能的自定义保护壳。
| 工具 | 用途 | 链接 |
|---|---|---|
| NoFuserEx | 脱 ConfuserEx 壳 | https://github.com/undebel/NoFuserEx |
| de4dot | 去混淆、还原符号 | https://github.com/de4dot/de4dot |
脱壳后使用 dnSpy / ILSpy 打开分析。
2 静态分析
2.1 软件基本信息

软件存在四种授权等级(scheme),内部用整数表示:
| 值 | 名称 | 说明 |
|---|---|---|
| 0 | Common (FREE) | 免费版,默认 |
| 1 | Silver | 银版 |
| 2 | Gold | 金版 |
| 3 | Platinum | 铂金版(全功能) |
2.2 程序退出保护
调试时发现程序启动后自动退出,无异常抛出,时间也不稳定。初步判断为计时器保护。

在 Class31.Main() 中发现启动前设置了一个 6 秒定时器:
1 | // Class31.cs:262-265 (GUI 启动路径) |
smethod_1 78行,判断文件大小受否匹配,检查自身 exe 文件大小是否为 3,939,328 字节,是则停止定时器,否则退出程序


修补方法: 将退出函数调用 NOP 掉即可。
2.3 激活入口定位
关键词搜索 common 找到一些结果。逐一排查后发现 GClass32.smethod_13(int) 是核心激活查询函数:
1 | // GClass32.cs:464-486 |
该函数通过 Class39.method_0() 获取激活数据字符串,按 ; 分割后返回指定索引字段。调用 smethod_13(3) 即获取用户等级(索引 3)。
2.4 混淆与反调试逻辑
Class39.method_0() 是激活数据读取的核心入口,包含三层随机路由(反调试/反静态分析):
1 | // Class39.cs:10-62 |
关键逻辑: 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 | ┌─────────────────────────────────────────────────┐ |
关键验证点:
flag5 = GClass32.smethod_21(array4, array3, dsaParameters, "SHA1")— DSA 签名验证- 若为
false,函数直接返回,激活失败 - 这是核心校验点,可通过替换公钥+自建签名来绕过
- 若为
2.6 密钥文件格式
激活文件 activationKey.vkf 的二进制结构:
1 | activationKey.vkf |
- 用户数据先经 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):
1 | // 输出格式: [4字节原始长度 (int32)] + [GZip压缩数据] |
解压 (GClass32.GClass33.smethod_1):
1 | // 输入格式: [4字节原始长度] + [GZip压缩数据] |
3.2 Rijndael (AES) 加密
程序中使用了两套 Rijndael 加密方案:
方案 A: 简单 Rijndael(用于公钥/字符串加密存储)
1 | // GClass32.GClass34.smethod_0 / smethod_4 |
已知密钥:
| 密文 | 密钥 | 用途 |
|---|---|---|
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(用于用户数据加密)
1 | // GClass32.GClass35.smethod_1 (解密) |
3.3 DSA 签名验证
1 | // GClass32.smethod_21 — 验证签名 |
修补思路: 替换程序内嵌的 DSA 公钥为我们自己生成的密钥对中的公钥,然后用私钥对激活数据签名即可。
3.4 用户数据加密
用户数据使用 PBKDF2 派生密钥 + AES-CBC 加密:
1 | // 加密参数 (从逆向分析得出) |
4 修补方案
4.1 替换 DSA 公钥
程序编译后的 .NET exe 中,字符串常量存储格式为:
1 | [[长度前缀 (可变长)] [UTF-16 字符串内容] [0x00 终止符]] |
长度前缀编码规则 (类似 LEB128):
| 字节数 | 编码方式 | 示例 |
|---|---|---|
| 1 byte | 直接存储长度值 (最高位为 0) | 长度 50 → 0x32 |
| 2 bytes | 首字节 `0x80 | (len >> 8),次字节 len & 0xFF` |
| 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 激活文件生成流程
1 | 用户输入 (email, name, level, serial) |
5.2 字段说明
激活数据字符串格式(6 个分号分隔字段):
1 | email;machineId;unknown;level;name;machineId |
| 索引 | 字段 | 说明 |
|---|---|---|
| 0 | 用户邮箱 | |
| 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 |
