[逆向工程]160个CrackMe入门实战之aLoNg3x.2解析(七)
一.aLoNg3x.2功能
作者说不要打补丁,找出用户名和注册码,隐藏下面的按钮,算成功

还是用补丁方式,验证是否逆向成功

二.查壳
无壳,32位程序,Delphi语言

DeDe查看事件及控件相关信息:


事件:
00442B98 取消按钮事件
00442CC8 关于按钮事件
00442F28 注册按钮事件
004430BC 重试按钮事件
控件ID:
2E8 重试按钮控件ID
2E4 取消控件ID
2DC 注册码控件ID
2D8 输入名控件ID
2CC 注册控件ID
三.逆向分析
3.1 00442F28 注册按钮事件
还是使用nop爆破方法分析事件,注册按钮入口地址00442F28处下断点
根据DeDe以及dbg分析以下是爆破点注释
00442F9F处nop填充
00442FC0处nop填充
00442F9F处nop填充
00442FCF处mov dl,1-----修改为mov dl,0
修改后将该文件打补丁为3.exe
备注:源程序中对注册码也做了一些简单校验,本次爆破输入时全部为数字
shell
00442F28 | 55 | push ebp | 注册按钮入口
00442F29 | 8BEC | mov ebp,esp |
00442F2B | 83C4 F8 | add esp,FFFFFFF8 |
00442F2E | 53 | push ebx |
00442F2F | 56 | push esi | esi:"U嬱兡舾(3D"
00442F30 | 33C9 | xor ecx,ecx | ecx:"U嬱兡舾(3D"
00442F32 | 894D F8 | mov dword ptr ss:[ebp-8],ecx |
00442F35 | 8BD8 | mov ebx,eax |
00442F37 | 33C0 | xor eax,eax |
00442F39 | 55 | push ebp |
00442F3A | 68 22304400 | push 3.443022 |
00442F3F | 64:FF30 | push dword ptr fs:[eax] |
00442F42 | 64:8920 | mov dword ptr fs:[eax],esp |
00442F45 | 8D55 F8 | lea edx,dword ptr ss:[ebp-8] |
00442F48 | 8B83 DC020000 | mov eax,dword ptr ds:[ebx+2DC] |
00442F4E | E8 ED02FEFF | call 3.423240 |
00442F53 | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] |
00442F56 | 8D55 FC | lea edx,dword ptr ss:[ebp-4] | [ebp-04]:BaseThreadInitThunk
00442F59 | E8 FAF9FBFF | call 3.402958 |
00442F5E | 8BF0 | mov esi,eax | esi:"U嬱兡舾(3D"
00442F60 | 837D FC 00 | cmp dword ptr ss:[ebp-4],0 | [ebp-04]:BaseThreadInitThunk
00442F64 | 74 37 | je 3.442F9D |
00442F66 | B8 38304400 | mov eax,3.443038 | 443038:"You MUST insert a valid Long Integer Value in the Code Editor... Thank you :)"
00442F6B | E8 00F6FFFF | call 3.442570 |
00442F70 | 8D55 F8 | lea edx,dword ptr ss:[ebp-8] |
00442F73 | 8B83 DC020000 | mov eax,dword ptr ds:[ebx+2DC] |
00442F79 | E8 C202FEFF | call 3.423240 |
00442F7E | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] |
00442F81 | E8 06FBFFFF | call 3.442A8C |
00442F86 | A3 30584400 | mov dword ptr ds:[445830],eax |
00442F8B | BA 90304400 | mov edx,3.443090 | edx:"U嬱兡舾(3D"
00442F90 | 8B83 DC020000 | mov eax,dword ptr ds:[ebx+2DC] | 2DC 注册码控件ID
00442F96 | E8 D502FEFF | call 3.423270 |
00442F9B | EB 6F | jmp 3.44300C |
00442F9D | 85F6 | test esi,esi | esi:"U嬱兡舾(3D"
00442F9F | 90 | nop | 跳过隐藏注册按钮,显示Again按钮
00442FA0 | 90 | nop |
00442FA1 | 8D55 F8 | lea edx,dword ptr ss:[ebp-8] |
00442FA4 | 8B83 D8020000 | mov eax,dword ptr ds:[ebx+2D8] | 2D8 输入名字控件id
00442FAA | E8 9102FEFF | call 3.423240 |
00442FAF | 8B4D F8 | mov ecx,dword ptr ss:[ebp-8] |
00442FB2 | 8BD6 | mov edx,esi | edx:"U嬱兡舾(3D", esi:"U嬱兡舾(3D"
00442FB4 | A1 30584400 | mov eax,dword ptr ds:[445830] |
00442FB9 | E8 EAF9FFFF | call 3.4429A8 |
00442FBE | 84C0 | test al,al |
00442FC0 | 90 | nop | 第三处判断 跳过隐藏注册按钮 显示重试按钮
00442FC1 | 90 | nop |
00442FC2 | 33D2 | xor edx,edx | edx:"U嬱兡舾(3D"
00442FC4 | 8B83 CC020000 | mov eax,dword ptr ds:[ebx+2CC] | 2CC 注册控件ID
00442FCA | E8 6101FEFF | call 3.423130 |
00442FCF | B2 00 | mov dl,0 | 修改常量1 为0
00442FD1 | 8B83 E8020000 | mov eax,dword ptr ds:[ebx+2E8] | 2E8 重试按钮控件ID
00442FD7 | E8 5401FEFF | call 3.423130 |
00442FDC | 33D2 | xor edx,edx | 2D8 输入名控件ID
其中修改常量方式如下:ctrl+e 之前01修改为00

3.2 代码分析过程
shell
004429A8 | 55 | push ebp | 关键call
004429A9 | 8BEC | mov ebp,esp |
004429AB | 83C4 F4 | add esp,FFFFFFF4 |
004429AE | 53 | push ebx |
004429AF | 56 | push esi |
004429B0 | 57 | push edi |
004429B1 | 894D F8 | mov dword ptr ss:[ebp-8],ecx | 用户名入栈
004429B4 | 8955 FC | mov dword ptr ss:[ebp-4],edx |
004429B7 | 8BF8 | mov edi,eax |
004429B9 | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] | 从栈中取用户名
004429BC | E8 2712FCFF | call along3x.2.403BE8 |
004429C1 | 33C0 | xor eax,eax |
004429C3 | 55 | push ebp |
004429C4 | 68 7A2A4400 | push along3x.2.442A7A |
004429C9 | 64:FF30 | push dword ptr fs:[eax] |
004429CC | 64:8920 | mov dword ptr fs:[eax],esp |
004429CF | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] | [ebp-08]:"11111"
004429D2 | E8 5D10FCFF | call along3x.2.403A34 | 跟进去 取用户名长度 返回eax
004429D7 | 83F8 04 | cmp eax,4 |
004429DA | 0F8E 82000000 | jle along3x.2.442A62 | 小于等于4则跳转,那么输入用户名必须大于4
004429E0 | 33DB | xor ebx,ebx |
004429E2 | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] | [ebp-08]:"11111"
004429E5 | E8 4A10FCFF | call along3x.2.403A34 |
004429EA | 85C0 | test eax,eax |
004429EC | 7E 38 | jle along3x.2.442A26 |
004429EE | 8945 F4 | mov dword ptr ss:[ebp-C],eax | 用户名长度入栈
004429F1 | BE 01000000 | mov esi,1 | 循环起始
004429F6 | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] | [ebp-08]:"11111"
004429F9 | E8 3610FCFF | call along3x.2.403A34 |
004429FE | 83F8 01 | cmp eax,1 |
00442A01 | 7C 1D | jl along3x.2.442A20 | 小于则跳转,如果不跳转 则eax也就是用户名长度要大于1
00442A03 | 8B55 F8 | mov edx,dword ptr ss:[ebp-8] | [ebp-08]:"11111"
00442A06 | 0FB65432 FF | movzx edx,byte ptr ds:[edx+esi-1] | 例如用户名输入为11111则从11111 中取1 显示31也就是第一个字符的ASCII值
00442A0B | 8B4D F8 | mov ecx,dword ptr ss:[ebp-8] | ecx等于用户名值
00442A0E | 0FB64C01 FF | movzx ecx,byte ptr ds:[ecx+eax-1] | 每次循环最后一个字符16进制ASCII值
00442A13 | 0FAFD1 | imul edx,ecx | edx=edx*ecx 第一位和最后一位相乘
00442A16 | 0FAFD7 | imul edx,edi | edx=edx*edi
00442A19 | 03DA | add ebx,edx | ebx=ebx+edx
00442A1B | 48 | dec eax | eax递减
00442A1C | 85C0 | test eax,eax | eax是否为0
00442A1E | 75 E3 | jne along3x.2.442A03 |
00442A20 | 46 | inc esi |
00442A21 | FF4D F4 | dec dword ptr ss:[ebp-C] |
00442A24 | 75 D0 | jne along3x.2.4429F6 | 循环结果
00442A26 | 8BC3 | mov eax,ebx |
00442A28 | 99 | cdq | 将 eax 中的有符号数符号扩展为 64 位,结果保存在 edx:eax 中(edx 存放高位,eax 存放低位)
00442A29 | 33C2 | xor eax,edx |
00442A2B | 2BC2 | sub eax,edx |
00442A2D | B9 2A2C0A00 | mov ecx,A2C2A |
00442A32 | 99 | cdq |
00442A33 | F7F9 | idiv ecx | eax / ecx eax=(商),edx=(余数)
00442A35 | 8BDA | mov ebx,edx |
00442A37 | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
00442A3A | B9 59000000 | mov ecx,59 | 59:'Y'
00442A3F | 99 | cdq |
00442A40 | F7F9 | idiv ecx | ecx/59 商eax 余数 edx
00442A42 | 8BC8 | mov ecx,eax |
00442A44 | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
00442A47 | BE 50000000 | mov esi,50 | 50:'P'
00442A4C | 99 | cdq |
00442A4D | F7FE | idiv esi | esi/50 商eax 余数edx
00442A4F | 03CA | add ecx,edx |
00442A51 | 41 | inc ecx | ecx = ecx+1
00442A52 | 894D FC | mov dword ptr ss:[ebp-4],ecx |
00442A55 | 3B5D FC | cmp ebx,dword ptr ss:[ebp-4] | ebx和注册码比较
00442A58 | 75 04 | jne along3x.2.442A5E | 不相等则直接跳转了
00442A5A | B3 01 | mov bl,1 | bl等于1正确
3.2.1 隐藏条件(必须先触发)
- 原因 :程序内部有一个全局变量
[0x445830],初始值为0。
如果这个值是0,所有算法计算结果都是0,永远无法注册成功。 - 触发方法 :
在 Codice(注册码)输入框 中输入 至少 6 个英文字母 (例如AAAAAA),然后点击 Register 按钮。
此时会弹出错误提示(因为纯字母不是有效注册码),但程序内部调用call 00442A8C将[0x445830]赋值为0x1686(十进制 5766)。
这一步只需做一次,之后整个程序运行期间该值保持有效。
3.2.2 隐藏 Register 按钮(第一次成功)
- 输入用户名 :在 Name 框中输入任意 长度大于4 的字符串(例如
admin)。 - 输入正确注册码 :在 Codice 框中输入根据用户名计算出的纯数字注册码(由注册机生成)。
- 点击 Register :
- 程序用
0x1686和用户名经过双层循环算出nTemp。 - 验证
注册码 / 89 + 注册码 % 80 + 1 == nTemp。 - 验证通过后,Register 按钮隐藏 ,同时 Again(重试)按钮出现。
- 程序用
此时你看到的界面:Register 按钮消失,多了 Again 按钮。
3.2.3 隐藏 Again 按钮(第二次成功)
为了完全破解,还需要把 Again 按钮也隐藏掉。方法类似:
- 再次输入假注册码 :
在 Codice 框中再次输入 至少6个英文字母 (例如AAAAAA),点击 Again 按钮。
会弹出错误提示(Again 按钮不会消失,但内部状态被重置)。 - 输入正确注册码 :
在 Codice 框中重新输入刚才的纯数字正确注册码 ,再次点击 Again 按钮。 - 验证通过后,Again 按钮隐藏,最终界面只剩下 Logo 和 Cancella(取消)按钮。

3.2.4 Cancella(取消)按钮的彩蛋
作者在取消按钮的事件(call 00442B30)中隐藏了一个特殊分支:
-
普通情况:点击 Cancella 会把 Codice 输入框的内容清零。
-
特殊情况 :
当 Name 框的内容 与 Codice 框的内容 完全相等 (字符串相同)时,点击 Cancella 会弹出两个信息框(而不是清空注册码)。
注意:Codice 框通常要求纯数字,所以要想触发这个彩蛋,用户名也必须是一个纯数字字符串(例如
12345)。此时点击 Cancella 会弹出两个提示框,算是作者留下的一个"惊喜"。
四、总结操作流程(完整版)
| 步骤 | 操作 | 结果 |
|---|---|---|
| 前置 | 在 Codice 框输入 AAAAAA,点击 Register |
弹出错误,但内部 [0x445830] = 0x1686 |
| 1 | 输入用户名(>4位)和正确纯数字注册码,点击 Register | Register 按钮隐藏,Again 按钮出现 |
| 2 | 在 Codice 框再次输入 AAAAAA,点击 Again |
弹出错误(Again 按钮还在) |
| 3 | 在 Codice 框重新输入正确纯数字注册码,点击 Again | Again 按钮隐藏,破解完成 |
| 彩蛋 | 用户名和 Codice 框输入相同数字(如 12345),点击 Cancella |
弹出两个消息框(不清零) |
五、注册机核心公式
正确注册码(纯数字整数)serial 满足:
serial / 89 + serial % 80 + 1 == nTemp
其中 nTemp = abs( sum ) % 666666,
sum = Σ (0x1686 * username[j] * username[i]),双层循环 i=1...len, j=len...1。
用 C++ 注册机即可根据任意用户名(长度>4)计算出唯一正确的 serial。
注册机源码如下:
c++
#include <iostream>
#include <string>
#include <cctype>
#include <limits>
// 检查字符串是否全为数字
bool isAllDigits(const std::string& str) {
for (char c : str) {
if (!std::isdigit(static_cast<unsigned char>(c))) return false;
}
return true;
}
int main() {
std::string username, fakeSerial;
int lenx = 0, leny = 0;
// 输入用户名(长度至少5)
while (lenx < 5) {
std::cout << "请输入用户名(5位以上): ";
std::getline(std::cin, username);
lenx = username.length();
if (lenx < 5) {
std::cout << "用户名太短,请重新输入。\n";
}
}
// 输入错误注册码(不能全数字,长度至少6)
while (leny < 6) {
std::cout << "请输入错误注册码(不能全数字6位以上): ";
std::getline(std::cin, fakeSerial);
leny = fakeSerial.length();
if (leny < 6) {
std::cout << "长度不足6,请重新输入。\n";
continue;
}
if (isAllDigits(fakeSerial)) {
std::cout << "不能全为数字,请重新输入。\n";
leny = 0; // 重置,继续循环
}
}
// 计算隐藏值 edi
int edi = 0x37B; // 891
for (size_t i = 1; i < fakeSerial.length(); ++i) {
unsigned char cur = static_cast<unsigned char>(fakeSerial[i]);
unsigned char prev = static_cast<unsigned char>(fakeSerial[i - 1]);
edi += (cur % 0x11 + 1) * prev;
}
edi %= 0x7148; // 29000
std::cout << "EDI为: " << edi << std::endl;
// 根据用户名和 edi 计算 ebx
long long ebx = 0;
size_t len = username.length();
for (size_t i = 0; i < len; ++i) {
for (size_t j = len; j > 0; --j) {
unsigned char ch_i = static_cast<unsigned char>(username[i]);
unsigned char ch_j = static_cast<unsigned char>(username[j - 1]);
ebx += static_cast<long long>(ch_i) * ch_j * edi;
}
}
ebx %= 0xA2C2A; // 666666
std::cout << "EBX为: " << ebx << std::endl;
ebx -= 1;
int code = 0;
// 原C代码中的近似求解
code = static_cast<int>(ebx * 0x59); // 取最大值
code = code - (code % 0x50) + 1; // 调整满足 %0x50 条件
std::cout << "正确注册码为: " << code << "\n\n";
// 枚举所有可能的解
std::cout << "所有可能的注册码:\n";
int start = static_cast<int>((ebx - 0x50) * 0x59);
int end = static_cast<int>(ebx * 0x59);
for (int i = start; i < end; ++i) {
if (i / 0x59 + i % 0x50 == ebx) {
std::cout << i << std::endl;
}
}
std::cout << "\n按回车键退出...";
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.get();
return 0;
}