从 NOP 爆破到写出注册机:记一次对某软件注册机制的逆向实战
#前言
以下内容仅供动态分析学习交流
小学习了一下软件动态分析技术,迫不及待拿了一个简单的取模软件进行分析

这是一个比较经典的生成C语言数组的取模软件,在嵌入式开发时很常用,但是这个软件太老了,网上也有很多可以使用的注册码能搜到,这个软件大概是在0几年时发布的,当时大多数注册码保护的软件基本都是这种离线校验注册码的逻辑。接下来就准备使用OllyDbg对它进行逆向分析,争取做出来一个去掉注册码的“爆破版”即去掉注册码验证环节和写出一个注册机,生成可以使用的注册码
#断点分析
首先,先要找到注册码的验证汇编位置,窗口上有5个文本窗口+一个注册按钮,首先进入OllyDbg的Windows窗口,刷新并找到注册按钮,设置一个注册按钮松开消息断点(202 WM_LBUTTONUP)

随便填入注册码并点击注册

可以看到此时断点断在了Windows处理按钮松开的事件位置,接下来我们要返回到程序领空,因此我们在内存中主程序的.text区块中添加断点

#程序跟踪
来到了此处,经过分析,可以看到0030CCED CMP EAX,111其中的 0x111是 Windows 消息 WM_COMMAND 的十六进制代码,即检查返回的消息是否是“命令”(按钮点击)因此继续设置断点并跟随下一句JE Img2Lcd.0040CE1C 进行跳转
观察此处代码,根据ai辅助判断,可以分析出
AND EDX, 0FFFF(地址0040CE22):- 这是在提取 控件ID (Control ID)。
- 当按钮被点击时,系统会发送一个 ID 过来。这段代码把这个 ID 拿出来放在
EDX里。
JMP DWORD PTR DS:[EDX*4+40D0C8](地址0040CE3F):- 关键点在这里! 这是一个 Switch 跳转表。
- 程序根据按钮的 ID (
EDX),计算出一个地址,然后直接跳过去。 - 不同的按钮 ID 会跳去不同的代码段。其中某一个跳转目标,就是“注册”按钮的处理函数。
因此继续追踪
按F8继续追踪,发现程序在0040CEF3 - 0040CF1E之间循环读取了我们输入的5组注册码


观察EBX寄存器就会发现填入的注册数字被分别读取出来后在0040CC10处将十六进制的字符串(Hex String)转换为对应的 32 位整数(Integer)
#程序爆破
程序爆破的原理是将程序的验证逻辑直接跳过,让其一直处于验证通过状态

继续追踪程序
可以看到0040CF44处程序调用了一个叫sss运行库内的函数
接着往下面读,很快就能看到紧跟着一个CMP比较指令和一个JNZ如果不相等则跳转指令,很明显
这几个指令就是判断输入的注册码是否正确的逻辑,我们使用F8跟踪到JNZ指令处,可以看到跳转会被触发
为了证实猜想,双击Z (ZF - Zero Flag)零标志位将其从0置为1,以手动阻止JNZ指令触发跳转,接下来按下F9让程序正常运行,可以看到,注册成功的提示出现

此时我们就找到了一个程序的爆破点,制作爆破注册机制的程序思路就很简单了,只需将JNZ这行命令使用nop(什么也不做)命令替换即可

接下来选中修改的代码,右键菜单执行Copy to executable-Selection后在打开的文件编辑窗口右键选择Save File导出程序,这样我们就成功获得了一个不管输入什么注册码均可注册成功的爆破版软件。
#注册机编写
在进入sss运行库之前,我们可以看到有四句命令对接下来要给到sss库的数据添加了一些小料
为了编写出注册机,我们要跟进sss运行库观察代码
进入sss运行库
1 | 10007090 > 83EC 18 SUB ESP,18 |
我们先观察整个代码,可以不难发现从10007184到100071A9连续4个判断逻辑,很明显,这四个判断逻辑正是在依次对照第5、4、3、2组注册码是否与前面计算出来的数据是否一致,所以不难猜想,在10007184前面的代码基本就是注册码生成的逻辑了,这个库根据注册码的第一位和前面传入的小料进行了各自复杂的运算,得出了注册码后四位来进行逐个对比,如果不对就提示错误。但是我在跟进汇编时发现一个异常现象,每次程序都从10007166 JE SHORT sss.10007184直接快进到了检查注册表的逻辑,但可以发现10007166地址的指令下面还有一段汇编代码,直到RETN返回,一直没有被执行。此时已经初露端倪了,这个库似乎想要回传什么数据。我们对这两个跳转打断点后更改标志位使两个跳转不动作,接下来到达10007175 LEA ESI,DWORD PTR SS:[ESP+C]这一行,在此处dump一下[ESP+C],看看发现了什么!

读一下此处红色的内存部分:1234 2EBE 63E7 BCE9 CE6D 11D2 F62F 50AE 15F8 2063如果你还记得的话,没错,其中11D2 F62F 50AE 15F8 2063就是程序在调用该库前传递的小料,那么前面的几位字符,不难看出,就是我们期待的以1234为第一组的正确注册码!没错,作者把注册机的逻辑也写在了这个库内,虽然添加了两个判断防止进入注册机逻辑。这样一来就好说了,也不需要修改dll库的汇编代码让它强制吐出注册码了。这样我们只需编写代码调用这个库里的注册机逻辑的程序即可
这里直接使用AI写了一个简单的Python脚本供参考
注意,由于该程序是32位程序,所以也需要使用对应的32位python环境运行,这里极力推荐使用uv python库管理器,输入命令
uv venv --python 3.12-x86即可在当前文件夹下安装32位python虚拟环境,不会污染你电脑上的python环境,接着输入uv run register.py即可使用虚拟环境运行该程序,而无需麻烦的挂载虚拟环境脚本
1 | import ctypes |

#后记
至此,本程序的逆向过程暂时告一段落,由于时间问题,没能将注册码生成逻辑重写回高级语言有些遗憾,但是这次逆向过程还是学到了很多知识和技巧,在动态调试中寻找突破口打断点是一个很重要的环节,一旦找到这个突破口,成功定位到关键代码,一切问题就迎刃而解了。希望这篇文章能够对您有所帮助,感谢您的阅读。本篇文章仅作为学习交流使用!!!!
评论区请友善交流,无关话题将会被删除