最近在研究一些小工具的授权机制时,碰到了一个挺有意思的样本。这就跟大家分享一下分析过程,纯本地校验且没有网络请求,这种“老式”的保护在现代破解眼光下显得颇为脆弱,但其中的算法逻辑还是有值得学习的地方。

目标概况

样本是一个名为 DeviceDataProcessImgui.exe 的程序,界面看起来是个“采集器数据处理”工具(v42版本)。如果不输入正确的卡密,你根本进不去主界面。

软件被锁定无法进入主界面

不输入正确的卡密无法进入主界面

经过初步侦查,发现导表里压根没有 WinHttpInternetOpen 这类网络 API。这就很明确了:授权逻辑全在本地。这意味着只要我们弄懂了它的校验算法,就能自己造“钥匙”。

第一步:搞清楚“我是谁”(设备 ID)

既然是本地校验,程序肯定会绑定你的机器。静态分析后发现,它优先读取注册表里的 MachineGuid

路径是: HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid

读取到 GUID 后,它会拼接一个前缀 MG-

逆向分析代码结构示意图

卡密 Payload 的拆解与重组逻辑示意图

例如: My GUID = 69b65411-d82a-4952-81a5-63f2527a7af3 Final DeviceID = MG-69b65411-d82a-4952-81a5-63f2527a7af3

如果读不到注册表,它还有个 B 计划,就是去读 C 盘的卷序列号,前缀变成 VOL-。不过大多数 Windows 环境下 MachineGuid 都是现成的。

第二步:拆解卡密结构

程序会让你输入一串卡密。分析处理函数发现,不管你输入 2508-4070-0950-9909-909 还是纯数字 2508407009509909909,它会先把所有非数字字符去掉。所以卡密本质上就是一串数字。

这串数字有严格规定:

  1. 长度必须是 19 位
  2. 最后一位是 Luhn 算法校验位(跟银行卡校验原理一样),防手滑输错的。
  3. 前 18 位才是真正的数据载荷。

载荷的拆解方式非常有趣,它是按每 3 位一组进行切割的(A,B,C,...)。然后把每组的前两位和最后一位再分别重组:

  • 所有组的前两位拼成一个 12 位的字符串。
  • 所有组的第三位拼成一个 6 位的字符串。

12位字符串的进一步拆分:

  • 前 8 位:核心校验值(check8)
  • 后 4 位:有效天数

6位字符串:

  • 直接作为 nonce(随机数),用于防止同样的天数生成完全一样的卡密。

第三步:核心算法 HMAC-SHA256

接下来就是重头戏了,那 8 位的 check8 是怎么来的?

在 IDA 里翻了一通,找到了加密用的 Key,居然是硬编码在程序里的(虽然被简单的异或混淆了一下,跑起来就能看到明文): HMJFCIXEENJQFMFSNZWXJKYKRYZS_FEXHOPSRJJAQUGD_QXJ

校验消息的拼接格式为: {DeviceID}|{days:04d}|{nonce:06d}

逻辑如下:

  1. 将上面拼好的字符串作为 Message。
  2. 使用那个超长的 Key 进行 HMAC-SHA256 运算。
  3. 取 Hash 结果的前 8 个字节。
  4. 将这 8 个字节转为大整数,然后对 100,000,000 取模(% 1e8)。
  5. 得到的 8 位数字就是 check8

第四步:写出 KeyGen 生成器

既然逻辑跑通了,我们就可以用 Python 复刻一遍。只要指定好天数和随机数,就能生成对应本机的正版卡密。

这里提供一个简易的生成逻辑思路:

import hmac
import hashlib

def generate_local_license(device_id, days, nonce):
    # 1. 固定的 Key,从程序里逆向出的
    key = b"HMJFCIXEENJQFMFSNZWXJKYKRYZS_FEXHOPSRJJAQUGD_QXJ"

# 2. 拼接消息
    # 注意:days必须是4位数字,nonce必须是6位数字
    message = f"{device_id}|{days:04d}|{nonce:06d}".encode('utf-8')

# 3. HMAC-SHA256 计算
    digest = hmac.new(key, message, hashlib.sha256).digest()

# 4. 取前8字节转整数并取模,得到8位check8
    check8_int = int.from_bytes(digest[:8], 'big') % 100000000
    check8 = f"{check8_int:08d}"

# 5. 组装前18位数据
    # 前8位是check8,后4位是days,最后6位是nonce
    payload_18 = check8 + f"{days:04d}" + f"{nonce:06d}"

# 6. 还原成每3位一组的原始格式以便混淆(逆向工程中的反操作)
    # 这一步需要将18位重新打散,然后加Luhn校验,这里就不展开具体还原函数了
    # 实际操作就是把上面拆解逻辑反过来走一遍

return payload_18 # 这里只是返回核心载荷,实际需还原为19位并补Luhn码

实战成果

针对我的机器,生成的 9999 天超长卡密如下(仅供技术研究):

2508-4070-0950-9909-909

输入进去,直接验证通过,顺利进入主界面。

总结

这个 CrackMe 难度其实适中,它没有加壳,也没用复杂的虚拟机保护(VMProtect),核心就是标准 HMAC 算法。对于逆向新手来说,这是一个非常好的练手素材,涵盖了:

  1. 字符串处理与异或解密。
  2. 动态调试获取运行时解密数据。
  3. 标准加密库 的识别与应用。
  4. 算法逆向还原。

对于开发者而言,如果不想自己的软件被这样轻松破解,切记:核心校验逻辑不要完全放在本地,也尽量不要硬编码 Key。 即使无法联网,也应尝试将校验逻辑与关键业务逻辑深度耦合,增加还原算法的难度。

标签: none

评论已关闭