利用反汇编技术将游戏《烈火文明》修改为硬盘版


最近偶然得到一款叫《烈火文明》的游戏的 ISO 镜像文件,图1为游戏开始界面。这是一款1999年发行的游戏,主要运行平台是 Windows 98,只是没想到在 Windows 10 上还能运行,不过效果打了些折扣,这些不是重点,重点是它是那个年代典型的光盘版游戏,要将光盘插入光驱或用虚拟光驱才能运行,而我偏偏有将光盘版改成硬盘版的强迫症,所以才有了这篇文章。
image
图1

1.实现思路
这是我改的第二个游戏,上次是《英雄无敌3之埃拉西亚的光复》,但上次是有一定运气成分的,可以参见我专栏里的另一篇文章《利用反汇编技术将游戏《英雄无敌3》修改为硬盘版》,这次决定换个思路。既然游戏在插入光盘的情况下可以运行,而没光盘就不能运行,说明在这两种情况下,程序走了两个不同分支,只要将这个分支找出来,然后找到分支前面的判断条件,将条件修改为无论什么情况都走有光盘的分支就可以了。

2.搭建调试环境
这部分主要写给没有 Windows 反汇编经验的朋友,有经验的朋友可以跳过。

2.1.安装 Visual Studio Community
可以到微软官网下载,网址是

Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器

安装时要选中“使用 C++ 的桌面开发”,如图2所示,已经安装过 VS 但没安装 C++ 组件的可以到控制面板里更改。
image
图2

2.2.新建项目
打开 VS,新建一个空项目,如图3所示。
image
图3

建好项目后,单击 VS 主菜单的“项目“->”属性”,如图4所示。
image
图4

在属性窗口中,将“调试”下的“命令”设置为游戏主 exe 文件的绝对路径,“工作目录”为游戏主 exe 所在的文件夹,如图5所示。
image
图5

3.调试与修改
按 F11 进行单步调试。一开始可能显示如图6所示的界面,单击“查看反汇编”右边的“查看”按钮即可显示汇编指令。
image
图6

按 F10 可以单步执行。遇到 call 指令,可以按 F11 进入 call 的地址内部执行。接下来就需要耐心和细心了,要一行一行执行并观察是否有条件跳转指令,如 je、jne、jz 之类的。最好有两台电脑,配置相同的调试环境,一台运行有光盘的,一台运行无光盘的,两边同时运行,这样就能最直观地看出在哪条指令上产生了分支。如果只有一台电脑,可以安装一个虚拟机。最笨的办法就是只在一台电脑上,先跑有光盘的程序,再跑无光盘的程序,分别记住执行了哪些指令。可以在执行了一段指令后下一个断点,下次再执行的时候按 F5 直接跳到断点上,节约时间。
经过一番单步执行后,最终发现在以下指令处两程序产生了分支。
00427188 call 0041C3B0
0042718D test eax,eax
0042718F jne 004271A9
00427191 push ebp
00427192 push 4BBE24h
00427197 push 0DEC8h
0042719C call 00428060
有光盘的程序在执行了 jne 004271A9 后,会跳转到 004271A9 继续执行,而无盘的程序不跳转,它会顺序执行到 call 00428060,在 00428060 里弹出对话框提示没有光盘并退出游戏。所以修改方案就是:将条件跳转指令 jne 改成无条件跳转指令 jmp。可以用 C32asm 这款静态反汇编软件,在该软件的主界面上右击,弹出的菜单上选择“跳到”,如图7所示。
image
图7

在弹出的对话框的 “EIP"中输入 jne 指令的地址 0042718F,如图8所示。
image
图8

这样就跳到了要修改的代码,如下所示:
::00427188:: E8 2352FFFF CALL 0041C3B0
::0042718D:: 85C0 TEST EAX,EAX
::0042718F:: 75 18 JNZ SHORT 004271A9
在 VS 里的 jne 指令到 C32asm 里变成了 jnz,不过这没什么影响。jne 指令对应的16进制为75,jmp 指令对应的16进制为 EB,在要修改的那一行右击,在弹出的菜单中选择“对应 HEX 编辑”,如图9所示。
image
图9

将图10中的75改成 EB 并保存即可。
image
图10

这个游戏在安装时选择了“全部安装”,游戏的所有内容都在硬盘上,程序只是检测一下是否有光盘在光驱中,并不读取光盘中的任何内容,所以上述修改可以保存游戏脱离光盘也能正常运行。

4.相关汇编指令的详细说明
4.1.call 0041C3B0
这条指令调用了地址 0041C3B0 的代码,相当于高级语言里的函数调用。因为下一条指令是 test eax, eax,所以这个函数的作用就是将计算的结果保存在 EAX 寄存器中,至于这个结果代表什么意思并不重要,因为本次修改策略就是以机器指令的角度来看待程序,我们只需要找出产生分支的指令,并了解为什么产生了分支就可以了。

4.2.test eax, eax
test 指令的作用是将两个操作数进行逻辑与运算,将结果保存到 ZF 标志位中,但操作数(在这里是 eax) 中的内容不变。如果运算结果为 0,则 ZF 标志位被设置为 1;如果运算结果为 1,则 ZF标志位被设置为 0。

4.3.jne 004271A9
jne 指令的作用是根据 ZF 标志位的值决定是否跳转到指定的地址。如果 ZF 标志位为 0 则跳转,1 则不跳转。

在执行了 call 0041C3B0 后,有光盘的程序会将 eax 置为 1,接下来 test eax, eax 会将 ZF 标志位置为 0,然后 jne 指令在发现 ZF 为 0 时就跳转到 004271A9,这样游戏就能正常运行。而无光盘的程序会将 eax 置为 0,test eax, eax 会将 ZF 置为 1, jne 指令不跳转,顺序执行到 call 00428060 并退出程序。整个逻辑如图11所示。
image
图11

全文完。

顺便说一下这游戏真 TM 烂!

posted @ 2025-07-21 12:25  安联酋长  阅读(30)  评论(0)    收藏  举报