160道题目网盘链接
链接:https://pan.baidu.com/s/15bqvffOLM6V5bff98DkT6w
提取码:3hc6
据说做完这160个练习题,90%的软件你都能搞定了,我也不清楚哈,也是才开始练习。
今天写的是第一道题,比较简单,但因为是第一次做,也是有点磕磕绊绊,所以过程会写的很详细,加深理解。
这道题属于Name\Serial类型的题,即根据你输入的Name可以理解为账号,进行一些运算,然后得到Serial可以理解为注册序列号,然后和自己输入的Serial进行比较,相同则成功,如果写注册机的话就需要在程序获取Name的时候分析它的算法过程。
两种验证方式
先打开CrackMe熟悉下功能,我们发现该程序有两种验证方式
直接验证你输入的序列号跟在程序中的原本保存的序列号序列号
自己输入Name,根据输入的Name经过运算生成的序列号和输入的序列号比较,相同则成功。
第一种Serial
拖进OD,运行程序,很明显这个错误弹窗是一个MessageBox的弹窗,直接Ctrl+G搜索MessageBoxA,这种老程序一般使用的是单字节版本也就是MessageBoxA
在此处下断点,随便输入一个数123456尝试一下,发现程序在此断下,我们开始单步走一下看看有什么有用信息,不可能性不大,这只是个标准的MessageBox,也没有重写。
果然没有发现什么有用的东西,走出MessageBox看看是谁调用了它
跳出MessageBox任然没有发现什么有价值的信息,但当我们往下翻堆栈的时候,发现在调用我们当前位置的函数的函数的函数已将我们输入的“123456”取出来并当作参数压入栈中了
箭头指向的堆栈所代表的函数才是真正调用这个错误信息框的函数
现在我们就明确目标了,找出已将我们输入123456取出的函数的位置,即这个真正调用我们这个消息框的函数,因为既然取出了我们的输入,那么大概率就要进行验证了,不断单步走,走到该函数为止。
并且这时候我们发现,还出现了不同于错误提示消息的字符,并且还有一个大跳转。
那么这个大跳转上面的这个CALL很有可能就是判断输入的Serial是否正确的函数了
0042F4EA |. E8 81ACFFFF call 0042A170
我们直接往上翻,拉到这个函数开始扩展栈空间即函数开始的位置,在这个位置上下断点
F9运行程序,在此输入123456,发现程序在此断下我们开始逐行分析
发现运行到如下指令取得用户输入
CALL 0041AA58
调试到之前所说的猜测是判断用户输入的CALL函数前我们发现这是个Fastcall规范的函数即通过寄存器传递参数
通过寄存器EAX和EDX传递了两个参数一个是我们输入的"123456" 还有一个是"Hello Dude!"
这个Hello Dude!很有可能就是Serial
我们进入到函数内部看一下,可以看到该CMP就是比较用户输入是否等于"Hello Dude!“的。
再结合上面的分析我们可以肯定Serial就是"Hello Dude!”。等于则提示God Job dude! 不等于则提示Try Again!
验证是否成功
第二种Name/Serial
这次我们要编写注册机
也是在MessageBoxA函数下断点,输入Name输入123456,Serial输入789 ,进行尝试
同样的走出MessageBox查看是谁调用了他
还是将堆栈往下翻一下,我们又发现我们输入的123456和789以及被取出了还有一些字符串也在堆栈中,
同样的,不断步过到达该函数,又看到了我们熟悉的字段
老样子在该函数开始处下断点,F9运行开始分析
发现也是通过CALL 0041A58取用户输入Name
不断往下走,这有个判断是否大于等于四位的条件
开始取用户输入的序列号
又来到了熟悉的比较函数,这里发现之前发现的CW-XXXX的字符串就是程序根据用户输入的Name进行运算后生成的序列号
进入该函数查看,发现果然如此
我们这里直接修改下ZF标志位让其相等验证所说是否正确
发现修改后果然提示正确
开始编写注册机
经过多次试验发现注册码规律为CW-XXXX-CRACKED是有 “CW-“前缀和”-CRACKED后缀”,因此我们只需要判断中间的XXXX是怎么来的就行了
重新加载一下
0042FA57 |. 83F8 04 cmp eax, 4 ; 判断用户输入是否大于或等于四位
0042FA5A |. 7D 1D jge short 0042FA79
0042FA5C |. 6A 00 push 0
0042FA5E |. B9 74FB4200 mov ecx, 0042FB74 ; ASCII "Try Again!"
0042FA63 |. BA 80FB4200 mov edx, 0042FB80 ; ASCII "Sorry , The serial is incorect !"
0042FA68 |. A1 480A4300 mov eax, dword ptr [430A48]
0042FA6D |. 8B00 mov eax, dword ptr [eax]
0042FA6F |. E8 FCA6FFFF call 0042A170
0042FA74 E9 BE000000 jmp 0042FB37
0042FA79 |> 8D55 F0 lea edx, [local.4]
0042FA7C |. 8B83 DC010000 mov eax, dword ptr [ebx+1DC]
0042FA82 |. E8 D1AFFEFF call 0041AA58
0042FA87 |. 8B45 F0 mov eax, [local.4] ; EAX=11223344
0042FA8A |. 0FB600 movzx eax, byte ptr [eax] ; 取11223344第一个字节 ASCII '1'=0x31 EAX=0X31
0042FA8D |. F72D 50174300 imul dword ptr [431750] ; 有符号乘法 [431750]=0x29
0042FA93 |. A3 50174300 mov dword ptr [431750], eax ; EAX=0X31*0X29=0X7D9
0042FA98 |. A1 50174300 mov eax, dword ptr [431750] ; 再把结果取出来
0042FA9D |. 0105 50174300 add dword ptr [431750], eax ; 再相加 [431750]=0X7D9+0X7D9=0XFB2
0042FAA3 |. 8D45 FC lea eax, [local.1]
0042FAA6 |. BA ACFB4200 mov edx, 0042FBAC ; ASCII "CW" 前缀
0042FAAB |. E8 583CFDFF call 00403708
0042FAB0 |. 8D45 F8 lea eax, [local.2]
0042FAB3 |. BA B8FB4200 mov edx, 0042FBB8 ; ASCII "CRACKED"
0042FAB8 |. E8 4B3CFDFF call 00403708
0042FABD |. FF75 FC push [local.1]
0042FAC0 |. 68 C8FB4200 push 0042FBC8
0042FAC5 |. 8D55 E8 lea edx, [local.6]
0042FAC8 |. A1 50174300 mov eax, dword ptr [431750]
0042FACD |. E8 466CFDFF call 00406718 ; 生成注册码 将EAX的值转化为十进制并且将其转化为字符串
0042FAD2 |. FF75 E8 push [local.6] ; 4018
该CALL将计算完毕的[431750]中的值转化为十进制,并且以字符串的形式储存
0042FACD |. E8 466CFDFF call 00406718
算法
1.判断用户输入Name是否大于等于四位数
2.去用户输入Name的第一个字符,将其乘与0x29
3.将相乘的结果再乘以2
4.将所得到的十六进制数字转化为十进制,再将其转化为字符串
5.拼接前缀和后缀
#include <cstdio>
#include <string>
#include <iostream>
using namespace std;
int main()
{
cout << "请输入Name" << endl;
int cName;
string nameStr;
cin >> nameStr;
if (nameStr.length()<4)
{
cout << "请输入长度大于4的序列号" <<endl;
return 0;
}
cName = nameStr[0];//只取第一个字符
if (cName > 0x21) // 只处理可见字符
{
cName *= 0x29; // 乘法
cName *= 2; // 自增一倍
printf("Serial: CW-%4d-CRACKED\r\n", cName);
}
else {
printf("input error!\r\n");
}
return 0;
}