深入理解逆向工程之常见注册类型
字数 2285 2025-08-22 22:47:30

逆向工程中常见注册类型详解

前言

本文旨在系统性地介绍逆向工程中常见的软件注册类型,包括硬编码注册、软编码注册、Name/Serial注册和Key文件注册等机制。通过实际案例分析,帮助读者深入理解各种注册保护机制的原理及破解方法。

一、硬编码注册

1.1 基本概念

硬编码注册是指在软件的源代码中直接嵌入注册密钥或许可证信息。这种注册方式的特点是序列号固定不变,直接存储在程序代码中。

1.2 案例分析

信息收集:

  • 运行程序,输入Serial,点击Check,弹出错误信息

调试步骤:

  1. 使用OllyDbg(OD)加载程序

  2. 识别关键API函数:

    • GetWindowTextA:获取用户输入的Serial
    • lstrcmpA:比较输入的Serial与真实Serial
    • MessageBoxA:根据比较结果弹窗
  3. 设置断点:

    • 可使用BP WindowsAPI快速设置函数断点
    • 重点关注lstrcmpA函数
  4. 分析流程:

    • 输入Serial后点击Check,程序会中断在GetWindowTextA
    • 使用Alt+F9执行完当前函数,返回主函数
    • 观察lstrcmpA函数参数,发现可疑字符串"cannabis"
    • 继续执行到lstrcmpA断点,Alt+F9返回主程序
    • 分析比较结果:EAX非0表示不匹配
    • 观察跳转逻辑:OR EAX,EAX后根据ZF标志位决定跳转

结论:

  • 硬编码的真实序列号为"cannabis"
  • 验证:输入该序列号可成功注册

二、软编码注册(动态生成序列号)

2.1 基本概念

软编码注册通过算法在用户注册时动态生成激活密钥,而非直接存储在代码中。其典型流程:

  1. 用户提供注册信息
  2. 软件用这些信息作为输入,通过算法生成序列号
  3. 用户使用生成的序列号激活
  4. 软件再次校验秘钥合法性

2.2 单Serial注册案例(abexcm5.exe)

信息收集:

  • PE信息:汇编编写,无壳,Windows GUI程序
  • 运行行为:输入Serial后点击Check出现校验弹窗

调试步骤:

  1. 切入点选择:

    • 字符串信息
    • 关键API:MessageBox、GetDlgItemText/GetWindowText
  2. OD分析:

    • 使用Ctrl+N查看程序使用的Windows API
    • 在GetDlgItemTextA设置断点
    • 运行程序,输入Serial后中断在断点处
    • 使用Alt+F9执行到返回主程序
  3. 算法分析:

    • 调用GetVolumeInformationA获取磁盘信息
    • 使用lstrcatA将"4562-ABEX"拼接到VolumeNameBuffer
    • 自定义流程:两次循环,每次将前四个字符ASCII值+1
    • 将处理后的VolumeNameBuffer拼接到"L2C-5781"后面
    • 与用户输入的Serial比较

注册机实现:

#include <iostream>
#include <Windows.h>

int main() {
    char VolumeNameBuffer[0x32] = {0};
    DWORD VolumeNameSize = sizeof(VolumeNameBuffer);
    DWORD VolumeSerialNumber = 0;
    DWORD MaxFilenameLength = 0;
    DWORD FileSystemFlags = 0;
    char Serial[MAX_PATH] = {};
    
    if(GetVolumeInformationA(NULL, VolumeNameBuffer, VolumeNameSize, 
       &VolumeSerialNumber, &MaxFilenameLength, &FileSystemFlags, NULL, NULL)) {
        char str1[MAX_PATH] = "4562-ABEX";
        char str2[MAX_PATH] = "L2C-5781";
        lstrcatA(VolumeNameBuffer, str1);
        
        for(int i = 0; i < 2; ++i) {
            VolumeNameBuffer[0] += 1;
            VolumeNameBuffer[1] += 1;
            VolumeNameBuffer[2] += 1;
            VolumeNameBuffer[3] += 1;
        }
        
        lstrcatA(Serial, str2);
        lstrcatA(Serial, VolumeNameBuffer);
        std::cout << Serial << std::endl;
    }
    return 0;
}

注意事项:

  • 注册机程序需与注册程序放在同一目录,确保获取的文件系统信息一致

三、Name/Serial注册

3.1 基本特点

  • 需要同时输入用户名(Name)和序列号(Serial)
  • 序列号通常基于用户名通过特定算法生成

3.2 案例分析

调试过程:

  1. 导入OD,在GetDlgItemTextA设置断点
  2. 输入Name/Serial后中断:
    • 第一次调用:存储Name到0x00403080
    • 第二次调用:存储Serial到0x00403280
  3. 校验流程:
    • 检查Name长度需≥5
    • 循环处理:
      00401148 |. B8 80304000 MOV EAX,AD_CM#2.00403080 ; ASCII "Reverse"
      0040114D |. BB 80324000 MOV EBX,AD_CM#2.00403280 ; ASCII "88888888"
      00401152 |. 8BCE MOV ECX,ESI ; Name长度
      00401154 |> 8A10 /MOV DL,BYTE PTR DS:[EAX] ; 取Name字符
      00401156 |. 2AD1 |SUB DL,CL ; DL减CL
      00401158 |. 3813 |CMP BYTE PTR DS:[EBX],DL ; 与Serial字符比较
      0040115A |. 75 18 |JNZ SHORT AD_CM#2.00401174 ; 不等则跳出
      0040115C |. 40 |INC EAX ; 遍历Name
      0040115D |. 43 |INC EBX ; 遍历Serial
      0040115E |.^ E2 F4 \LOOPD SHORT AD_CM#2.00401154 ; 循环
      
    • 算法:Serial[i] = Name[i] - (NameLength - i)

注册机实现:

#include <iostream>
#include <vector>
#include <string>

int main() {
    std::string Name;
    std::string Serial;
    
    std::cout << "Please enter your name(length >= 5): ";
    while(std::getline(std::cin, Name)) {
        if(Name.length() < 5) {
            std::cout << "Your name must be at least five characters! Enter your Name: ";
            continue;
        } else {
            for(int i = 0; i < Name.length(); ++i)
                Serial.push_back(Name[i] - (Name.length() - i));
            break;
        }
    }
    
    std::cout << "Your Serial is: \"" << Serial << "\"" << std::endl;
    return 0;
}

四、Key文件注册

4.1 基本概念

使用特定文件作为注册凭证,典型流程:

  1. 用户从供应商处获取Key文件
  2. 保存Key文件到本地
  3. 软件读取并验证Key文件
  4. 验证成功则激活软件

4.2 案例分析

调试过程:

  1. 识别关键API:
    • CreateFileA:尝试打开"CRACKME3.KEY"
    • ReadFile:读取18个字节(0x12)
  2. 校验流程:
    • 成功打开时EAX为文件句柄,失败为FFFFFFFF
    • 自定义算法函数处理Key文件内容:
      • 前14个字符与'A'-'N'异或
      • 将异或结果的Hex值累加
      • 累加结果与0x12345678异或
    • 比较后4字符的Hex值与处理结果

注册机实现:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>

int main() {
    std::string username;
    std::cout << "请输入用户名(不超过14位):" << std::endl;
    std::getline(std::cin, username);
    
    if(username.length() > 14) {
        std::cout << "请输入小于14位的用户名!" << std::endl;
        return 0;
    }
    
    username.resize(14, ' '); // 不足14位用空格填充
    int tmp_sum = 0;
    std::vector<char> crypt_result;
    
    for(int i = 0; i < 14; ++i) {
        char xor_key = 'A' + i;
        char user_chr = username[i];
        char xor_user_char = xor_key ^ user_chr;
        crypt_result.push_back(xor_user_char);
        tmp_sum = tmp_sum + static_cast<int>(user_chr);
    }
    
    int key = tmp_sum ^ 0x12345678;
    std::ofstream outfile("CRACKME3.KEY", std::ofstream::binary | std::ofstream::out);
    outfile.write(reinterpret_cast<const char*>(&crypt_result[0]), crypt_result.size());
    outfile.write(reinterpret_cast<const char*>(&key), sizeof(key));
    outfile.close();
    
    std::cout << "密钥文件已生成!请拷贝到软件所在目录下使用!" << std::endl;
    return 0;
}

五、总结与对比

注册类型 特点 安全性 破解难度 典型识别特征
硬编码 序列号直接存储在代码中 容易 固定的字符串比较
软编码 运行时动态生成序列号 中等 涉及系统信息获取和算法处理
Name/Serial 序列号基于用户名生成 中高 中等 需要同时输入用户名和序列号
Key文件 使用外部文件作为凭证 较难 检查特定文件的存在和内容

六、逆向工程实用技巧

  1. API断点设置

    • 注册相关:GetWindowTextA/GetDlgItemTextA
    • 比较相关:lstrcmpA/memcmp
    • 提示相关:MessageBoxA
  2. 关键点定位

    • 查找字符串引用
    • 跟踪用户输入后的处理流程
    • 关注跳转指令周围的逻辑
  3. 算法分析

    • 记录关键数据变换过程
    • 注意循环和数学运算
    • 跟踪自定义函数调用
  4. 注册机编写

    • 准确还原算法流程
    • 注意数据类型的处理
    • 确保运行环境一致性

通过系统学习这些注册机制及其逆向分析方法,可以全面提升软件逆向工程能力,为进一步研究更复杂的保护机制奠定基础。

逆向工程中常见注册类型详解 前言 本文旨在系统性地介绍逆向工程中常见的软件注册类型,包括硬编码注册、软编码注册、Name/Serial注册和Key文件注册等机制。通过实际案例分析,帮助读者深入理解各种注册保护机制的原理及破解方法。 一、硬编码注册 1.1 基本概念 硬编码注册是指在软件的源代码中直接嵌入注册密钥或许可证信息。这种注册方式的特点是序列号固定不变,直接存储在程序代码中。 1.2 案例分析 信息收集: 运行程序,输入Serial,点击Check,弹出错误信息 调试步骤: 使用OllyDbg(OD)加载程序 识别关键API函数: GetWindowTextA:获取用户输入的Serial lstrcmpA:比较输入的Serial与真实Serial MessageBoxA:根据比较结果弹窗 设置断点: 可使用 BP WindowsAPI 快速设置函数断点 重点关注lstrcmpA函数 分析流程: 输入Serial后点击Check,程序会中断在GetWindowTextA 使用Alt+F9执行完当前函数,返回主函数 观察lstrcmpA函数参数,发现可疑字符串"cannabis" 继续执行到lstrcmpA断点,Alt+F9返回主程序 分析比较结果:EAX非0表示不匹配 观察跳转逻辑:OR EAX,EAX后根据ZF标志位决定跳转 结论: 硬编码的真实序列号为"cannabis" 验证:输入该序列号可成功注册 二、软编码注册(动态生成序列号) 2.1 基本概念 软编码注册通过算法在用户注册时动态生成激活密钥,而非直接存储在代码中。其典型流程: 用户提供注册信息 软件用这些信息作为输入,通过算法生成序列号 用户使用生成的序列号激活 软件再次校验秘钥合法性 2.2 单Serial注册案例(abexcm5.exe) 信息收集: PE信息:汇编编写,无壳,Windows GUI程序 运行行为:输入Serial后点击Check出现校验弹窗 调试步骤: 切入点选择: 字符串信息 关键API:MessageBox、GetDlgItemText/GetWindowText OD分析: 使用Ctrl+N查看程序使用的Windows API 在GetDlgItemTextA设置断点 运行程序,输入Serial后中断在断点处 使用Alt+F9执行到返回主程序 算法分析: 调用GetVolumeInformationA获取磁盘信息 使用lstrcatA将"4562-ABEX"拼接到VolumeNameBuffer 自定义流程:两次循环,每次将前四个字符ASCII值+1 将处理后的VolumeNameBuffer拼接到"L2C-5781"后面 与用户输入的Serial比较 注册机实现: 注意事项: 注册机程序需与注册程序放在同一目录,确保获取的文件系统信息一致 三、Name/Serial注册 3.1 基本特点 需要同时输入用户名(Name)和序列号(Serial) 序列号通常基于用户名通过特定算法生成 3.2 案例分析 调试过程: 导入OD,在GetDlgItemTextA设置断点 输入Name/Serial后中断: 第一次调用:存储Name到0x00403080 第二次调用:存储Serial到0x00403280 校验流程: 检查Name长度需≥5 循环处理: 算法:Serial[ i] = Name[ i ] - (NameLength - i) 注册机实现: 四、Key文件注册 4.1 基本概念 使用特定文件作为注册凭证,典型流程: 用户从供应商处获取Key文件 保存Key文件到本地 软件读取并验证Key文件 验证成功则激活软件 4.2 案例分析 调试过程: 识别关键API: CreateFileA:尝试打开"CRACKME3.KEY" ReadFile:读取18个字节(0x12) 校验流程: 成功打开时EAX为文件句柄,失败为FFFFFFFF 自定义算法函数处理Key文件内容: 前14个字符与'A'-'N'异或 将异或结果的Hex值累加 累加结果与0x12345678异或 比较后4字符的Hex值与处理结果 注册机实现: 五、总结与对比 | 注册类型 | 特点 | 安全性 | 破解难度 | 典型识别特征 | |---------|------|-------|---------|-------------| | 硬编码 | 序列号直接存储在代码中 | 低 | 容易 | 固定的字符串比较 | | 软编码 | 运行时动态生成序列号 | 中 | 中等 | 涉及系统信息获取和算法处理 | | Name/Serial | 序列号基于用户名生成 | 中高 | 中等 | 需要同时输入用户名和序列号 | | Key文件 | 使用外部文件作为凭证 | 高 | 较难 | 检查特定文件的存在和内容 | 六、逆向工程实用技巧 API断点设置 : 注册相关:GetWindowTextA/GetDlgItemTextA 比较相关:lstrcmpA/memcmp 提示相关:MessageBoxA 关键点定位 : 查找字符串引用 跟踪用户输入后的处理流程 关注跳转指令周围的逻辑 算法分析 : 记录关键数据变换过程 注意循环和数学运算 跟踪自定义函数调用 注册机编写 : 准确还原算法流程 注意数据类型的处理 确保运行环境一致性 通过系统学习这些注册机制及其逆向分析方法,可以全面提升软件逆向工程能力,为进一步研究更复杂的保护机制奠定基础。