1、Acid burn
1、Acid burn
简述:
难度:一颗星
操作系统: Windows(XP)[I386, 32 位, GUI]
链接程序: Turbo Linker(2.25*,Delphi)[GUI32]
编译器: Borland Delphi(3)[Standard]
语言: Object Pascal(Delphi)
准备工作
这里直接使用 die 进行查即可
发现无壳子,这时候就可以直接进行逆向了。
首先看一下该函数调用了那些库函数。
其实就这些基础函数,这里我们就先不理会了。
直接打开界面
我们会发现有两个。一个是序列号,一个是用户名+序列号
我们先整序列号的
序列号破解分析
直接拖入x32dbg,因为是32位程序
这里说一下一般破解的思路。其实就是直接将其运行起来。点击对应的功能查看是否弹窗,有弹窗基本就好办了。开始操作。
我们直接点击check 不管他输入的是什么
发现是这样的。
直接点暂停
然后堆栈回调
要注意关注 用户模块。系统模块无需理会
回一层。发现有调用messagebox。回两层便可以看到字符串了。
这里直接,在call 这里打一个断点。,重新走到这里
eax 和edx传参 内平栈 fastcall 通过jne跳转可以看出来,zf 标志位为0时不跳转。
eax 指向 [ebp-10]: "Enter the serial here !!!!!"
edx 指向 [ebp-C]: "Hello Dude!"
保护现场
4039FC | 53 | push ebx
4039FD | 56 | push esi
4039FE | 57 | push edi
把两个参数分别赋值给 esi edi
4039FF | 89C6 | mov esi,eax
403A01 | 89D7 | mov edi,edx
403A03 | 39D0 | cmp eax,edx
403A05 | 0F84 8F000000 | je acid burn.403A9A
....
403A9A | 5F | pop edi
403A9B | 5E | pop esi
403A9C | 5B | pop ebx
403A9D | C3 | ret
这里开始分析这两行。。可以看出来。
当输入的字符串为Hello Dude! 时可以正确打印。
所以我们进行尝试一下
我们继续分析 Serial/Name
直接按照之前的方法跳转到关键点。
0042FAF | 8B55 F0 | mov edx,dword ptr ss:[ebp-10] | [ebp-10]:"Enter your serial here !"
0042FAF | 8B45 F4 | mov eax,dword ptr ss:[ebp-C] | [ebp-C]:"CW-4018-CRACKED"
0042FAF | E8 F93EFDFF | call acid burn.4039FC |
0042FB0 | 75 1A | jne acid burn.42FB1F |
即可以看到这里。这时候就可以知道是比较两个字符串的大小,找一下这个字符串是从哪里来的。其实多调试几次就可以发现,两边的 CW
和 CRACKED
都没有改变过。这里只是纯碰运气,并没有技术含量,下面开始做有技术含量的事情把。
这里定位关键函数的方法就不说了。上面也说了,一致回调,如何每个函数都调一下就知道了。
下面就开始逐行分析汇编逻辑了。
这几行都和我们的注册机没啥关系
0042F9A | 55 | push ebp |
0042F9A | 68 67FB4200 | push acid burn.42FB67 |
0042F9A | 64:FF30 | push dword ptr fs:[eax] |
0042F9B | 64:8920 | mov dword ptr fs:[eax],esp |
0042F9B | C705 50174300 290 | mov dword ptr ds:[431750],29 | 29:')'
0042F9B | 8D55 F0 | lea edx,dword ptr ss:[ebp-10] | [ebp-10]:"112233"
0042F9C | 8B83 DC010000 | mov eax,dword ptr ds:[ebx+1DC] | [ebx+1DC]:&"d稝"
0042F9C | E8 8BB0FEFF | call acid burn.41AA58 |
0042F9C | 8B45 F0 | mov eax,dword ptr ss:[ebp-10] | [ebp-10]:"112233"
0042F9D | E8 DB40FDFF | call acid burn.403AB0 | 判断是否为空
0042F9D | A3 6C174300 | mov dword ptr ds:[43176C],eax |
0042F9D | 8D55 F0 | lea edx,dword ptr ss:[ebp-10] | [ebp-10]:"112233"
0042F9D | 8B83 DC010000 | mov eax,dword ptr ds:[ebx+1DC] | [ebx+1DC]:&"d稝"
0042F9E | E8 70B0FEFF | call acid burn.41AA58 |
其实就知道。ebp - 10 是我们的字符串, eax指向的地址也是我们的字符串即可,
下面开始第一块算法,
0042F9E | 8B45 F0 | mov eax,dword ptr ss:[ebp-10] | [ebp-10]:"112233"
byte 是一个字节,所以是把eax的第一个字节给eax高位补0 也就是字符串1 十进制的48
0042F9E | 0FB600 | movzx eax,byte ptr ds:[eax] |
把eax赋值给esi
0042F9E | 8BF0 | mov esi,eax | esi:&"d稝"
esi 左移3位 esi = eax << 3
0042F9F | C1E6 03 | shl esi,3 | esi:&"d稝"
esi = eax << 3 - eax
0042F9F | 2BF0 | sub esi,eax | esi:&"d稝"
下面这些似乎并没有什么用处,他调几遍看看
0042F9F | 8D55 EC | lea edx,dword ptr ss:[ebp-14] |
0042F9F | 8B83 DC010000 | mov eax,dword ptr ds:[ebx+1DC] | [ebx+1DC]:&"d稝"
0042F9F | E8 55B0FEFF | call acid burn.41AA58 |
0042FA0 | 8B45 EC | mov eax,dword ptr ss:[ebp-14] |
第一块算法用C语言代码来编写如下
char* str = "112233"
int nEax = *(str)
int nEsi = nEax << 3 - nEax
开始分析第二块算法
0042FA0 | 0FB640 01 | movzx eax,byte ptr ds:[eax+1] |
取第二个字节
0042FA0 | C1E0 04 | shl eax,4 |
右移四位
0042FA0 | 03F0 | add esi,eax | esi:&"d稝"
再加上一个esi 也就是第一块算法的结果
0042FA0 | 8935 54174300 | mov dword ptr ds:[431754],esi | esi:&"d稝"
把结果存入一个变量中
后面就是没啥用的东西了
0042FA1 | 8D55 F0 | lea edx,dword ptr ss:[ebp-10] | [ebp-10]:"112233"
0042FA1 | 8B83 DC010000 | mov eax,dword ptr ds:[ebx+1DC] | [ebx+1DC]:&"d稝"
0042FA1 | E8 35B0FEFF | call acid burn.41AA58 |
0042FA2 | 8B45 F0 | mov eax,dword ptr ss:[ebp-10] | [ebp-10]:"112233"
还原还原如下:
char* str = "112233"
int nEax = *(str)
int nEsi = nEax << 3 - nEax
int nTmp = *(str+1) << 4 + nEsi
这里其实nTmp才是唯一的一个全局变量,其他的都不是,。只是为了方便看,所以我这样写的。
继续往下写
0042FA2 | 0FB640 03 | movzx eax,byte ptr ds:[eax+3] |
取出第四个字节
0042FA2 | 6BF0 0B | imul esi,eax,B | esi:&"d稝"
乘以0xB 注意这个B是十六进制的
后面又是无用代码了。
0042FA2 | 8D55 EC | lea edx,dword ptr ss:[ebp-14] |
0042FA3 | 8B83 DC010000 | mov eax,dword ptr ds:[ebx+1DC] | [ebx+1DC]:&"d稝"
0042FA3 | E8 1DB0FEFF | call acid burn.41AA58 |
0042FA3 | 8B45 EC | mov eax,dword ptr ss:[ebp-14] |
这里就先不还原了。,后面一起还原
第三块算法
0042FA3 | 0FB640 02 | movzx eax,byte ptr ds:[eax+2] |
0042FA4 | 6BC0 0E | imul eax,eax,E |
第三个数据乘以0xE 然后加上esi 这一点要细细斟酌,我等下还原算法。
0042FA4 | 03F0 | add esi,eax | esi:&"d稝"
0042FA4 | 8935 58174300 | mov dword ptr ds:[431758],esi | esi:&"d稝"
0042FA4 | A1 6C174300 | mov eax,dword ptr ds:[43176C] |
0042FA5 | E8 D96EFDFF | call acid burn.406930 |
如果小于四个字节就直接爆出两个字符串
0042FA5 | B9 74FB4200 | mov ecx,acid burn.42FB74 | 42FB74:"Try Again!"
0042FA6 | BA 80FB4200 | mov edx,acid burn.42FB80 | edx:&"d稝", 42FB80:"Sorry , The serial is incorect !"
406930 这里是我x32dbg的bug无需理会
0040693 | 89FA | mov edx,edi | edi:&"d稝"
0040693 | 89C7 | mov edi,eax | edi:&"d稝"
0040693 | B9 FFFFFFFF | mov ecx,FFFFFFFF |
0040693 | 30C0 | xor al,al |
0040693 | F2:AE | repne scasb |
0040693 | B8 FEFFFFFF | mov eax,FFFFFFFE |
0040694 | 29C8 | sub eax,ecx |
0040694 | 89D7 | mov edi,edx | edi:&"d稝"
0040694 | C3 | ret |
这一小段汇编其实主要是repne scasb 指令没有见到过。其实他是计算字符串的长度的。遇到0 就停止,结果在ecx里面
这里有一个小插曲
edi = 02C91100
0040693 | F2:AE | repne scasb |
内存中这一行指令 02C91100 31 31 33 33 14 00 00 00 1B 00 00 00 01 00 00 00 1133............
也就是说,后面多出来一个14。所以这里他的代码应该是直接这样写的。
int arr[3] = "123" 或者直接malloc字符串个数,这样来计算的
这里我们不管。只需要知道,如果小于四个字节就直接报错了。
后面继续还原, 无用函数这里就不复制了。后面有人能看到github。看一下原汇编指令就知道了
0042FA8 | 0FB600 | movzx eax,byte ptr ds:[eax] | eax:"1133"
0042FA8 | F72D 50174300 | imul dword ptr ds:[431750] |
取出第一个字符,与0x431750的值相乘 往上翻看看这个值哪里出现了 其实就是初始值,。赋值了一个0x29
把结果在存到这个里面,然后再乘以2
0042FA9 | A3 50174300 | mov dword ptr ds:[431750],eax | eax:"1133"
0042FA9 | A1 50174300 | mov eax,dword ptr ds:[431750] | eax:"1133"
0042FA9 | 0105 50174300 | add dword ptr ds:[431750],eax | eax:"1133"
后面就是字符串拼接一下了。
"CW-4018-CRACKED"
下面开始还原算法了。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
const char* cw = "CW";
const char* crackme = "CRACKED";
int main()
{
char str[10];
scanf("%s", str);
int nTmp = *(str + 1) << 4 + *(str) << 3 - *(str);
int nTmp2 = *(str + 3) * 0xB + *(str + 2) * 0xE;
if (strlen(str) <= 4) {
printf("Try Again!");
printf("Sorry , The serial is incorect !");
}
int nTmp3 = *(str) * 0x29 * 2;
char key[50];
sprintf(key, "%s-%d-%s", cw, nTmp3, crackme);
printf("密钥为:%s\n", key);
system("pause");
return 0;
}
测试一下
weiran
密钥为:CW-9758-CRACKED
看看是否可行
到这里就结束了。后面还会有,160个刷完为止。