吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1425|回复: 1
上一主题 下一主题
收起左侧

[.NET逆向] Video Thumbnails Maker 逆向分析 & 补丁记录

[复制链接]
跳转到指定楼层
楼主
JonesDean 发表于 2026-3-31 21:51 回帖奖励
本帖最后由 JonesDean 于 2026-4-2 20:08 编辑

目标版本: 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 秒定时器:

// 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 != 2bool_0 = false,外层 do-while(!flag) 会继续循环;只有 num == 2bool_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)
  1. 用户数据先经 AES 加密
  2. AES 密文经 GZip 压缩
  3. 拼接 [userInfoLen | Gzip(AES(userinfo)) | DSA_Signature]
  4. 整体再经一次 GZip 压缩
  5. 最外层追加 [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 -

修补步骤:

  1. 生成新的 DSA 1024 密钥对
  2. 将公钥 XML 用 Ueiekfnv3G 作为密钥进行 Rijndael 加密 → Base64
  3. 在 exe 中定位原始公钥字符串(方案 A 中的那个超长 Base64 串)
  4. 计算新字符串的 UTF-16 长度
  5. 确保新长度 ≤ 原长度(不足用 \0 填充)
  6. 写入新的长度前缀 + 新字符串 + 终止符

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 成功测试

贴两张注册成功测试图和使用效果



使用软件制作效果(主要用于创建视频文件的缩略图(截图、预览、屏幕列表)、动画和屏幕截图。该实用程序可用于家庭视频编目和网络视频共享,更多内容请参考官网说明)

免费评分

参与人数 4吾爱币 +6 热心值 +4 收起 理由
hello95271 + 1 + 1 我很赞同!
3yu3 + 1 + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
qqycra + 3 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
qqycra 发表于 2026-4-3 12:20
看看思路,C#的app
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-21 19:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表