深入理解逆向工程之常见注册类型
字数 2285 2025-08-22 22:47:30
逆向工程中常见注册类型详解
前言
本文旨在系统性地介绍逆向工程中常见的软件注册类型,包括硬编码注册、软编码注册、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比较
注册机实现:
#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 案例分析
调试过程:
- 导入OD,在GetDlgItemTextA设置断点
- 输入Name/Serial后中断:
- 第一次调用:存储Name到0x00403080
- 第二次调用:存储Serial到0x00403280
- 校验流程:
- 检查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 基本概念
使用特定文件作为注册凭证,典型流程:
- 用户从供应商处获取Key文件
- 保存Key文件到本地
- 软件读取并验证Key文件
- 验证成功则激活软件
4.2 案例分析
调试过程:
- 识别关键API:
- CreateFileA:尝试打开"CRACKME3.KEY"
- ReadFile:读取18个字节(0x12)
- 校验流程:
- 成功打开时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文件 | 使用外部文件作为凭证 | 高 | 较难 | 检查特定文件的存在和内容 |
六、逆向工程实用技巧
-
API断点设置:
- 注册相关:GetWindowTextA/GetDlgItemTextA
- 比较相关:lstrcmpA/memcmp
- 提示相关:MessageBoxA
-
关键点定位:
- 查找字符串引用
- 跟踪用户输入后的处理流程
- 关注跳转指令周围的逻辑
-
算法分析:
- 记录关键数据变换过程
- 注意循环和数学运算
- 跟踪自定义函数调用
-
注册机编写:
- 准确还原算法流程
- 注意数据类型的处理
- 确保运行环境一致性
通过系统学习这些注册机制及其逆向分析方法,可以全面提升软件逆向工程能力,为进一步研究更复杂的保护机制奠定基础。