前段时间的学习中,我尝试破解的程序基本都是固定账号密码的类型,但在实际中,我们遇到的大部分注册程序都是密钥依据自创算法随注册用户名变化而变化的,例如一些注册机等等。所以这类注册程序无法使用查找字符串的方式进行破解,这无疑给破解带来了不小的难度。所以这次,我们依然使用动态分析技术来研究一下注册程序动态密钥的破解。
寻找核心代码
这里,我们将《加密与解密(第四版)》中第2章的学习样本拿来作为这次研究的对象。该程序是一个非常普通的注册程序,具有动态密钥的特性:
使用Exeinfo PE打开,该程序的相应参数如下:
然后,我们依旧使用x64dbg进行调试。
在Windows提供的API中,GetDlgItemText或者GetWindowText函数通常是程序获取文本框中的字符所使用的常用函数。对于注册机来说,检查密钥是否正确的关键是捕获用户输入的内容,然后用输入的用户名通过特殊算法生成密钥,然后用这个密钥和输入的密钥进行比较,从而判断是否注册成功。所以,我们通过定位到这两个函数,可以快速找到关键代码,实现破解。
关于GetDlgItemText函数,因为位数以及编码的不同,大体有这几种类型:
因为该程序是32位ANSI版的程序,所以程序调用的应该是GetDlgItemTextA函数。
首先,我们Ctrl+G键进入查找界面,然后这里我们查找GetDlgItemTextA函数:
然后,我们转到user32.dll模块里的该函数开头,在76614000h处F2设置断点,用于后面捕获调用的代码:
接下来继续F9运行程序,随便输入用户名和序列号,直到在上面的断点处停止,然后就能按Alt+F9回到主程序模块里调用该函数的地方:
我们发现,调试器回到的地方是调用该函数代码的下面一行00FC11BFh,且函数调用代码周围的代码段应该就是该程序的核心所在。
我们在函数调用处00FC11BDh设置新的断点,并将之前设置的断点在断点选项卡里禁止掉,然后继续F9运行程序,会得到该程序序列号错误的提醒。我们不用理睬,然后继续在该程序里按Check键执行运行命令,调试器会在前面设置的新的断点处00FC11BDh停止:
分析动态数据
接下来,我们在寄存器面板里将ESP数据转到内存窗口中,然后一步一步步过,观察寄存器面板、内存窗口和栈窗口的数据变化:
当步过到00FC11BFh处,内存窗口出现了我们刚开始输入的用户名,证明了前面汇编指令Call是用来获取用户输入的用户名:
当步过到00FC11D2h处,内存窗口出现了我们前面随意输入的序列号,也证明了前面汇编指令Call是用来获取用户输入的序列号:
当步过到00FC11E7h处,寄存器面板中的EAX显示出了我们刚开始输入的用户名,证明00FC11EEh处push指令将用户名寄存到EAX中:
当步过到00FC11EEh处,寄存器面板中的EAX显示出了我们前面随意输入的序列号,证明00FC11EEh处的push指令和00FC11EEh处起到的作用一样:
当步过到00FC11F4h处,内存窗口以及栈窗口同时出现了另一串序列号,也就是我们正在寻找的正确的密钥:
在00FC11F4h的前一行,也就是00FC11EFh处,是一个Call汇编指令,指向的是主程序模块中的00FC1086h位置:
我们按Ctrl+G搜索地址00FC1086h,然后跳转到了核心代码的前面不远处,略微往下也能看到提示类中文字符串,这里或许就是计算序列号的算法代码,可以通过修改这里的代码爆破程序:
最后,我们把前面获得的序列号输入程序中,成功破解!
关于加壳破解
如果该程序被加壳了,依然可以使用前面学过的附加调试进行破解,步骤和前面无壳的一样。
到这里,动态密钥的破解过程就结束了,我们可以轻松获取用户名对应的序列号实现注册了!