[旧帖]
[原创]CrackMe入门——序列号保护机制
0.00雪花
发表于:
2015-10-9 15:37
1972
[旧帖] [原创]CrackMe入门——序列号保护机制
0.00雪花
发此贴的目的主要是总结一下最近学习的东西,如果能结识相同爱好的朋友再好不过了
本人也是新手,如有错误之处欢迎诸位大侠赐教!
-----------------------------------------------------------------------------------------------
序列号保护机制大致有四种
[*]以用户名等信息作为自变量,通过函数F变换之后得到注册码
破解方法:只需要把F 函数的实现代码从软件中提取出来
[*]通过注册码验证用户名的正确性
1)由于F‾¹ 的实现代码是包含在软件中的,所以可以通过F‾¹ 来找出其逆变换即F 函数,从而写出正确的注册机
2)给定一个用户名,利用穷举法找到一个满足公式的序列号
3)给定一个序列号,利用F‾¹(序列号)公式得出一个用户名,从而得到一个正确的用户名/序列号对
[*]通过对等函数检查注册码
[*]同时采用用户名和序列号作为自变量,即采用二元函数
接下来我会用一个实例来具体分析
首先运行程序,单击菜单 Help,会弹出一个注册框,随便输入一组数据,点击OK,会弹出一个提示对话框 “Incorrect!, Try Again”
大致了解了程序的运行流程,下一步要做的就是找到程序中关键的代码段,有几种方法,下面一一来尝试。
[*]在OD里利用 API 断点来定位关键代码段:
如何下API 断点:
在用户代码的反汇编窗口,使用"Ctrl+N"快捷键打开应用程序的输入表,找到要下断点的API函数,在这个函数上按Enter键或右键菜单执行"Find references to import(查找输入函数参考)"命令或按Enter键打开调用此函数的参考代码窗口,即可找到调用此API的相关地址,在反汇编窗口中定位到此地址即可下断点
在单击菜单Help后,程序弹出了一个模态对话框,等待用户的输入,先去输入表中看看程序调用了哪些创建对话框的函数
发现了DialogBoxParamA函数,然后在反汇编窗口中找到调用此函数的地址,在参考窗口中发现有三处调用了此函数(其实只有两处)
依次分析,很容易可以发现第二个才是我们要找的
连窗口处理过程的地址都有了,在它的下一条指令(CMP EAX,0)处下好断点
函数说明:
DialogBoxParamA函数会阻塞,直到窗口处理过程中调用了EndDialog函数后,才会返回,并且EndDialog函数的参数会当作DialogBoxParamA函数的返回值
在上一步中获取到了窗口处理过程的函数地址,然后可以直接在反汇编窗口中定位到窗口处理过程函数,进行具体的分析;这里为了练习API断点,就不采用这种方法了。
继续分析程序的执行流程,在点击OK按钮后,程序要读取EDIT控件中的内容,然后才能对输入的名称和注册码进行处理,所以可以通过对调用读取控件内容的API函数处下断点,打开程序的输入表,发现了GetDlgItemTextA函数
在反汇编窗口中找到调用GetDlgItemTextA函数的地址
在004012C4处下断点,然后在OD中调试程序,输入一组测试数据,可以发现上面的GetDlgItemTextA函数是将输入的Name读取到0040218E处,长度为11个字节,下面的GetDlgItemTextA函数是将输入的Serial读取到0040217E处,长度也为11个字节,接着程序调用EndDialog函数结束窗口过程函数。程序的执行流程到了上一步操作的断点处,对此处的反汇编代码进行初略的分析,很容易定位出关键代码段是如下被标出的两个函数
[*]在OD里利用字符串来定位关键代码段:
在我们注册失败后,程序弹出了一个提示框
先去查找Incorrect!,Try Again 字符串,在反汇编窗口单击鼠标右键,选择“字符串参考/查找ASCII”
发现有两处,只能逐个分析,双击就可跳转到反汇编窗口中去,我们最终要定位到的是下面这里,这段代码首先调用MessageBeep函数播放一段系统音乐,然后调用MessageBoxA函数弹出失败对话框
接着在反汇编窗口中寻找哪里调用了00401362处的代码(使用IDA的交叉引用参考很方便就能找到)
经过分析,我们也能够定位出两个关键函数的地址 [*]在OD里利用消息断点加内存断点定位关键代码段:
如何下消息断点:
单击菜单"View/Windows(查看/窗口)"可以列出窗口相关参数,比如按钮、对应的ID、以及句柄等,在相应的条目上单击右键,执行"Message breakpoint on ClassProc(在ClassProc上设置消息断点)",再选择要断的消息,此断点会断点系统底层代码,可以在用户程序.text区段下一个内存访问一次性断点
在OD里运行调试程序,点击菜单Help,弹出注册对话框,然后切换到OD用户反汇编代码区,点击W图标或者菜单“查看/窗口”,会列出当前程序中所用到的资源信息
在此窗口中可以看到控件的句柄、风格、类、ID等等,给On按钮下一个消息断点,由于我们要捕捉的是点击消息,所以选择断在WM_LBUTTONUP消息上
接着输入一组测试数据,点击OK按钮,程序断在了系统底层代码,按Alt+M快捷键打开内存映射窗口,在用户代码代码区段下一个内存访问一次性断点,按F9让程序继续执行,程序会断在对话框的窗口过程函数入口处
在窗口中得到的控件的ID在此处可以派上用场了,窗口处理过程的第三个参数传进来的是此消息所属的控件ID,这里OK按钮的控件ID是3EAh,所以可以定位OK按钮的点击消息响应代码段从004012B5处开始,很明显这里是读取用户输入,可以在此处下一个断点,然后让程序继续执行,直到读取完毕,程序将输入的用户名存放在0040218E处,将输入的注册码存放在0040217E处,在内存窗口中定位到其中一个地址,下一个内存访问断点,这里我选择的是0040218E,按F9继续运行,程序会断在此处
ESI寄存器里存放的值就是我们所下断点的地址了,按Alt+K快捷键打开堆栈窗口,查看调用堆栈
发现此函数的返回地址是00401232,在反汇编窗口中定位到00401232地址处,进行分析仍然可以定位出两个关键函数的地址
[*]在OD里利用消息断点加RUN跟踪定位关键代码段:
下消息断点的步骤和上面一种方法是一样的,这里我们不通过内存断点,改用RUN跟踪的方法定位关键代码段,RUN跟踪的作用是将程序执行过的指令记录下来,所以使用RUN跟踪的关键点是如何缩小要跟踪的范围,这里我们就结合消息断点来使用。
在OK按钮上下好消息断点,输入一组测试数据,点击OK,程序断在了系统代码区,然后开始设置RUN跟踪
第一步:点击菜单“选项/调试设置/跟踪”中设置相关配置,注意缓冲区大小,如果执行的指令太多,缓冲区满了的话,会自动丢弃前面老的记录
第二步:点击菜单“调试/打开或清除RUN跟踪”
第三步:在用户代码反汇编窗口中鼠标右键,在弹出菜单中选择“RUN跟踪/添加所有函数过程的入口”
RUN跟踪设置好之后,按F9继续运行程序,会弹出提示对话框,不要点确定,点击OD菜单中的“查看/RUN跟踪”,这个窗口中显示的就是打开RUN跟踪后,到当前程序执行过的指令
可以看到程序执行的最后一条指令是地址00401378处,CALL <JMP.&USER32.MessageBoxA>指令,双击跳转到反汇编窗口,可以得知此处就是弹出失败对话框的代码,继续分析程序中哪里跳转到了00401362处,最终我们仍然可以定位到此处
可以在此处下一个断点,然后输入一组测试数据,查看0040218E和0040217E处存放的数据,便可得知两个关键函数,0040137E是对输入的名称进行处理,004013D8是对输入的注册码进行处理。
利用上面几种方法可以定位出两个关键函数,接下来要做的就是逆向出这两个函数的功能,写出注册机。首先分析0040137E
刚开始看反汇编的代码时候,单个指令很容易能看懂,但是组合在一起的时候,就不知道这段代码是做什么的了,只能多看、多练、多总结了。这段代码对名称字符串进行了一些简单的运算,并将运算结果保存在EAX寄存器中返回。
上述反汇编代码用C语言表示如下:
DWORD DealName(BYTE* name)
{
BYTE *p = name;
BYTE ch;
while (true)
{
ch = *p;
if (ch == 0)
{
break;
}
else if (ch < 'A')
{
MessageBox(NULL, "Incorrect!,Try Again", "Error!", MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL);
return 0;
}
else if (ch >= 'z')
{
ch -= 0x20;
*p = ch;
}
++p;
}
p = name;
DWORD num = 0;
while (true)
{
ch = *p;
if (ch == 0)
{
break;
}
else
{
num += ch;
p++;
}
}
num ^= 0x5678;
return num;
}
接着再看看另一个关键函数004013D8,反汇编代码如下:
这段代码对输入的注册码字符串进行了一些简单的运算,并将运算结果保存在EBX寄存器中返回。用C语言表示如下:
DWORD DealSerial(BYTE* code)
{
DWORD i;
DWORD num = 0;
for (i = 0; code[i] != 0; ++i)
{
num = num * 0x0A + code[i] - '0';
}
num ^= 0x1234;
return num;
}
再看这两个函数执行完后,程序的处理逻辑
结合前面可以知道,EAX的值是对名称处理后的结果,EBX的值是对注册码处理后的结果,只要EAX等于EBX就能注册成功。这个就是典型的最开始提到的第三种序列号保护机制:通过对等函数检查注册码,F1(用户名)= F2(序列号)。
现在只要求出DealName的逆函数或者DealSerial的逆函数就可以写出注册机了,结合上下文可以将DealSerial函数理解成:将字符串转换成数字,然后将转换后的数字与0x1234异或,所以要让EAX和EBX的值相等,只要将EAX的值与0x1234进行异或,得到的值就是要求的注册码,很容易就能写出注册机,关键函数:
DWORD Cracker(DWORD num)
{
num ^= 0x1234;
return num;
}
参考资料:加密与解密
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
上传的附件: