一、工具及游戏介绍
使用工具:Ollydbg,PEID,Beyond Compare,Cheat Engine
实现功能:无敌,全部通关。
Crimsonland 血腥大地
二、实现无敌:
使用CE查找全局变量。
无敌成功。
三、通关:
1、文件对比
原始关卡:
这里先采用上一篇文章的方法。
渡者方案2:通过文件资源管理器的时间排序,来找到最后修改的文件,确定配置文件。
游戏过关,并查看文件资源管理器。
替换未过关的game.cfg文件,发现关卡回到了之前,可以确定是game.cfg文件。
这里保存了未过关的game.cfg 和 game2.cfg
和 过关的 game3.cfg 和 game4.cfg。
进行两两对比。
game.cfg 和 game2.cfg:
game3.cfg 和 game4.cfg
用010Editor查看。
经过两两比较并多重测试,确定跟关卡相关是这几个点,暂且叫做关卡数据点。
当 关卡数据点 或者 非关卡数据点 的数据改变时,后面方框内的两组数据都会变,且变化无规律。
个人猜测:两组数据类似于对文件的数据计算,类似于哈希值算法。
接下来只能动用OD了。
2、OD调试
2.1、查壳
很可惜,没加壳。
以下PEID 和 OEP特征可验证无壳:
2.2、下断
先查找字符串,在调用字符串资源的地方下断。
再在一些文件操作函数下断。
首先在字符串资源地方断下。
并找到了 字符串拼接路径 函数。
猜测接下来调用CreateFile,果然。
2.3、追踪关键代码
栈回溯返回用户界面,查看文件句柄。
F9接下来是ReadFile
显而易见:
在游戏运行时 就已经读取文件game.cfg到内存了。
此时ReadFile的Buff是配置文件的缓冲区。
目标:从Buff下手,追踪修改数据的关键代码。
2.4、继续追踪
重复以上步骤,确认逻辑正确,现在句柄是0x190
在此块内存上设置内存访问断点:
对断下来的地方进行分析
retn出函数后,似乎进入了循环,但这里只执行一次,把缓冲区的首个字节拷到483220地址上。
暂且把483220处的内存命名为 缓冲区2。
跳到的一个地方,往下执行,直到调用函数,拷贝完接下来的267个字节。
往下分析,发现把存放 缓冲区 和 缓冲区2 的地址都加到了末尾。
接下来,按道理来说:跟踪数据,查看数据头部是否被修改,数据尾部是否增加。
但这里 直接再下一次内存断点,这次往缓冲区2【483220】处下内存写入断点。
断下的地方证明了猜想,执行指令 往存放 缓冲区2 地址加4,也就是说 缓冲区2 总共预定存放0x268+0x4 == 0x26C 个字节。
我们再看看配置文件字节数,正好是0x26C个字节,正好吻合。
2.5、锁定关键代码
再继续分析,真正进入了一个大循环,且不断循环执行。
推断是解密算法。
直接循环后,查看 缓冲区2 【483220】结果:
2.6、OD RUN跟踪
进一步验证,分析循环次数。
此处循环判断是CMP ESI 0x10B18
通过ESI的数值判断。
由图可知比较esi来判断循环结束,esi算法复杂。
直接采用Run跟踪。
全局统计循环了 616次,
616 == 0x268,正好是拷贝的0x268个字节。
实际应该是0x26C个字节,那末尾的四个字节呢?
带着疑问继续跟踪。
发现此处指令往数据末尾写入4个字节,正好凑齐0x26C个字节,Perfect。
2.7、总结与实施方案
解密了0x268个字节,解密后,再写入四个无加密字节。
最后四字节是此处代码生成,写进文件中会覆盖掉原数据,所以后续操作中不用考虑。
所以得出结论:此处就是解密算法 。
所以可以肯定的是 配置文件数据的确经过加密。
往下分析,写入文件时,遇到加密算法:
解决方案:
- 由经过解密算法后的配置文件可以看出,头个字节就是关卡数,所以经过算法修改首个字节就行。
- 直接去掉程序算法,配置文件以原样数据保存。再修改配置文件。
- 逆算法,写一个翻译器。
本文选择高端而又简洁一点的操纵,第二种。
此处直接NOP掉算法代码。
四、路途曲折与反反调试
本文的最高潮来了。
1、出师不利
就在我修改完代码,保存文件,满怀期待地 双击运行的时候,弹出了这个小框框:
说实话,当初这东西对我地信心打击不小呀,但秉着不气馁不认输地倔强脾气,决定继续分析。
但得先思考思考,猜测是exe模块内存方面的反调试,类似于模块的哈希值。
首先,既然弹框,往MessageBox上下断点。然后往上回溯。
可以看到弹框的关键跳转。
往上分析,弹框的条件取决于此函数返回的EAX值。
1. 跟进函数,发现LoadLibrary加载grim.dll后返回EAX为零值。
2. 为了进行比较,打开正常游戏,跟到此处,LoadLibrary加载grim.dll后返回却为正常值。
1. 分析下面发现后面还jmp 隐藏调用了grim.dll里的GRIM_GetInterface函数。
2. 此处为什么要隐藏调用。虽然这一点在文章后面用不上,但猜测是其它功能破解的线索,所以晒出来,以后或许用得上。
总结一下:
正常情况下,加载grim.dll后是有正常返回值的。
当修改了指令也就是模块内存后,保存exe,运行,加载grim.dll后返回空值,导致函数调用失败,外部EAX返回值使弹出MessageBox程序运行失败。
LoadLibrary —失败—>> 函数 —失败—>> EAX返回值 —导致—>>弹出MessageBox 程序运行失败
推测是程序 再次打开exe进程,校验模块内存,所以在进程相关函数下断。特别是CreateProcess
但从程序开始运行到弹窗,无一次断下。
2、峰回路转
不是模块内存,又必须对字节进行校验,剩下的可能,推测是进行了磁盘文件校验。
验证:用正常exe打开游戏进程后,替换dump的exe,发现依然存在错误,可以推断不是依靠进程内存判断的,而是通过磁盘文件。
在CreateFile、ReadFile下断点。并进行参数过滤。
查看ReadFile的Buff,的确读取的游戏exe文件。
ReadFile一次读取0x1000个字节,需要调用多次。
接下来要找到反调试的关键代码。
回溯:找到调用ReadFile的循环。
3、直击敌营
分析到这里就可以一口气开干了。
逆向分析代码:
目的:分析出算法计算最后得出的值,值的传递,并与谁比较。
分析可以得出此算法是计算文件的哈希值【MD5】。
并拿计算好的MD5与原MD5进行比较。
进行修改指令,dump grim.dll即可。
再替换原有的游戏exe和grim.dll。
成功。
最后一步:
修改配置文件
可以看出修改后的exe用WriteFile写出来的配置文件 清晰明了。
4、大获全胜
通关成功。
个人总结:灵活运用硬件断点、内存断点、栈回溯。
硬件断点和内存断点 |
硬件断点能快速定位关键点 |
方便,但逻辑较乱 |
栈回溯 |
栈回溯能顺藤摸瓜 |
需要耐心,但逻辑清晰 |
附件:
KID