先看一个未注册的截图:
从提示信息中我们可以得知当程序发现未注册时会弹出“用户名、密码错误......”的消息框。
用od载入。
由于是MFC封装过的消息框,直接下断MessageBox的方法就失效了。
所以现在内存中找到字符串“用户名、密码错误,请联系作者”,要注意用od时在正确的数据段里寻找,记得刚开始学习的时候总是找不到要找的字符串,原因是字符串在.rdata段中,而od内存窗口中默认打开的是.data段中的数据。
找到字符串的RVA是0x4b60(由于模块每次加载的地址都不一样,所以这里用RVA来描述,模块的基地址+RVA就是实际的地址,写这篇文章的时候是0x94b60),找到后再怎么办呢?
虽然我没学过MFC,但是程序想让MFC里的消息框函数显示一个字符串,就得把字符串的地址传给它让它显示,根据这个原理,我们在od中用Ctrl+F搜索指令:push 94b60 如图:
在push指令往上一点点我们发现如下指令:
00092480 /$ 833D E0760900>CMP DWORD PTR DS:[976E0],0
00092487 |. 56 PUSH ESI
00092488 |. 8BF1 MOV ESI,ECX
0009248A |. 75 11 JNZ SHORT lianlian.0009249D
现在我们有两种方法使程序不提示对话框 一种是修改jnz指令或者修改cmp指令使其与1比较改变程序执行流程,达到绕过注册的目的。一种是修改976e0处的数据,让程序错误的以为我们已经注册了。(没猜错的话,976e0处存放的应该是一个bool型全局变量,初始化为0,如果检测到已注册就改为非零,我们暂且叫它bRegistry)
很显然第一种方法治标不治本,第二种方法更有效些(说更有效些是因为不确定程序在检测到没注册时是否会把它改为0,而不是初始化为0,这个可以通过设置硬件写入断点来验证它确实是初始化为0的) 。
现在我们只需要在程序运行之前把这个bRegistry的值修改成1,就可以达到破解的目的了。
每次加载程序bRegistry的地址都会变化,但是它相对于模块的基地址也就是RVA不变,所以我们要改写bRegistry就得先获得程序的基地址,再用基地址加上它的RVA -76e0就可以得到它的实际地址了。
分析完了思路,我们就要编程实现了,我们单独写一个程序用WriteProcessMemory来改写,也可以直接用od在外挂里直接写入汇编代码来改写bRegistry的值。不过谁也不想让外挂再拖着一个补丁吧,所以我在这里只说第二种方法,第一种方法我在附件里给出了源代码。
上截图:
在一个高级语言编译器生成的汇编代码里往往会有许多没用的代码,比如一片的nop或INT3。 我选择了一处有一片INT3的地方写下了这些代码:图中的红色代码就是。
图中没显示的改动还有这一句:
00093138 > $ E8 59EEFFFF CALL lianlian.00091F96;这是修改后的入口代码,使其先调用我们的代码
下面分析图中的代码:
00091F96 50 PUSH EAX ;保存eax的值(说好听点就是保护进程的执行环境)
00091F97 6A 00 PUSH 0 ;压入GetModuleHandleA的参数
00091F99 B8 B7280976 MOV EAX,kernel32.GetModuleHandleA
00091F9E FFD0 CALL EAX ;调用GetModuleHandleA获得模块基址
00091FA0 05 E0760000 ADD EAX,76E0 ;获得bRegistry的地址
00091FA5 FF00 INC DWORD PTR DS:[EAX];改写bRegistry的值
00091FA7 58 POP EAX ;恢复进程的执行环境
00091FA8 E9 1C160000 JMP lianlian.000935C9 ;跳到真正的代码处执行
执行到这bRegistry里的值就应该是1了,继续运行就可以正常使用了。
不要忘记右击od里的代码-复制到可执行文件-全部修改。
新的exe就是已经破解了的外挂了。
最后我要告诫保护自己软件的朋友,尽量不要直接暴露你的字符串地址,这样很容易找到你判断是否注册的分支指令。还有不要简单的把是否注册的信息放在一个全局变量里。
连连看外挂及破解.rar
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)