《游戏修改器DIY之二》 

                     源码下载 


  上次我们以一款名叫《潜艇大战》的小游戏作为目标,制作了其相应的修改器,使之成为了不死版的游戏,相信大家在这过程中学到了相关的知识(一年前写的东西了:))。这次我们的目标是《红色警戒2》,制作她的金钱修改器。之所以选择这款游戏作为演示,一方面是出于个人原因:我很喜欢这款游戏;而另一方面则是:这款游戏很具有代表性,相信大多数修改过其金钱数的玩家们都会注意到:她存放金钱的内存地址并不是固定不变的,哪怕重新开始一次任务,她的地址都会改变。与之类似的游戏还有《Mech Commander》等。本篇将教你如何制作此类游戏的修改器。

  目标:《红色警戒2》(繁体中文版:1.006)
  所需工具:《金山游侠III》、
  SOFTICE(版本:4.0.5 Build334+IceDump)、LordPE1.4、
  Microsoft Visual C++ (版本:6.0+SP6)

  第一部分:获得游戏中关键的数据——这是制作游戏修改器的前提。

  首先用《游侠》找到存放金钱数的内存地址(一般情况下会找到三个地址,其中最另类的那个就是我们所需要的):0x68A1B5C,调出SOFTICE,输入下面的命令:bpm 68a1b5c r,回到游戏后SI就产生了中断,你可以看到下面这条指令:

0187:004E2B5A FIADD DWORD PTR [ESI+00000228]

  其中ESI的值为0x068A1934,下指令:d esi+228 即得:0x068A1B5C,其中便是当前的金钱数。追溯这段代码可得:

0187:00494664 MOV EDX,[ECX+24]
0187:00494667 LEA EAX,[ECX+24]
0187:0049466A PUSH EAX
0187:0049466B CALL [EDX+18]
0187:0049466E XOR ECX,ECX
0187:00494670 MOV [ESI],EAX

  在0187:0049466A处下断点,SI每次中断后,EAX的值始终是:0x068A1934,如此时下指令:d eax+228 同样可得:0x068A1B5C。现在情况很明了:其中0x228是个定值,我们现在不必理会,关键的问题是EAX的值,就像前面说到的那样:她的值并不是固定不变的,哪怕重新开始一次任务她的值都会随之改变。我们需要做的工作就是写一段自己的代码,将EAX的值保存在一个固定的位置,然后从该位置取得结果,再加上0x228即可得到存放金钱数的内存地址了。所以,我们需要找一个可写的空白区,来存放我们自己的代码和EAX的值。

  用LordPE查看Game.exe(一定要做好备份!!!用LordPE查看文件后,即使什么也没做,他也会修改我们的文件……),发现.data段符合我们的要求,我选的位置是:0x42ed60(存放EAX的值),和0x42ed80(存放自己的代码),在0x42ed60处写一些字符串,如:vegeta,这样一来,我们可以用s命令快速找到其在内存中的位置,还可以判断该位置是否被程序所用。

  重新开始游戏,SI会在0x0049466A处产生中断。在SI中下指令:

s ds:400000 l ffffffff 'vegeta'

SI提示找到了一个内存地址:018F:0082ED60(0042ED60),这段内存程序并没有占用,现在就可以在0x0082ED80(0042ED80)处写我们自己的代码了。在这之前,我们先修改0x0049466A,让他跳到我们的代码,下指令:

a 49466a

输入:

jmp 82ed80 //跳到我们的代码处执行
nop //补指令长度

修改后的代码为:
0187:0049466A E911A73900 JMP 0082ED80
0187:0049466F 90 NOP
0187:00494670 8906 MOV [ESI],EAX


现在写入我们的代码:

a 82ed80

依次输入:

mov dword ptr [82ed60],eax //将EAX的值保存到:0x82ed60
push eax //\
call [edx+18] //恢复被JMP 0082ED80破坏掉的指令
xor ecx,ecx ///
jmp 00494670 //返回游戏程序继续执行

修改后的代码为:
0187:0082ED80 A360ED8200 MOV [0082ED60],EAX
0187:0082ED85 50 PUSH EAX
0187:0082ED86 FF5218 CALL [EDX+18]
0187:0082ED89 33C9 XOR ECX,ECX
0187:0082ED8B E9E058C6FF JMP 00494670

  检查无误后记下机器码(我习惯用IceDump的抓屏),在0x0082ED85处设断点,F5返回游戏后便会产生中断,此时下指令:

d *0082ED60+228

  这时你就会看到金钱数了……注意:0x0082ED60可是个定值啊!


  第二部分:用VC制作游戏修改器。

  本程序每次运行将增加金钱至60000$。

  程序源码中的重点部分我已经做了相应的注释,这里要说的是:

  1:这段代码同时支持Win9x和NT系统,由于要找到目标进程的ID,所以我枚举了系统中的所有进程,然后根据进程名来确定我们的目标进程,但是,ToolHelp API中的szExeFile在这两种不同的系统中的返回值是有很大差异的:一个带有路径,另一个不带路径(有兴趣的朋友可以参照《如何在NT下获取进程的路径》一文)。所以,在Win9x系统中,本程序需要拷贝到游戏的所在目录中才能正确运行。

  2:要先写我们的代码(0x0082ED80),然后再改写要跳转到我们代码中去的代码(0x0049466A)。

  3:由于《红色警戒2》在程序切换的过程中会暂停游戏,所以在修改金钱数的地方做了一个延迟:以便用户有足够的时间返回游戏,这样游戏才会执行我们的代码,把金钱的内存地址放到0x0082ED60中去。如果没有这个延迟,游戏还没有将金钱地址放到0x0082ED60,ReadProcessMemory()就去获取金钱地址了,那WriteProcessMemory()也就写错了地方……

  4:用Game.exe的备份替换掉修改后的程序,因为在0x0042ED60处我们写了一些字符串。如果不这样做,该修改器第一次运行时则不会正确修改你的金钱。想想为什么会这样?当家庭作业了。

  5:最重要的一点!我十分喜欢这个游戏。做这个修改器只是练手,我平时可是不用她的…… 

//////////////////////////////////////////////////////////////////////////////////////////


/*
* --《Red Alert 2》v1.006 繁体中文版金钱修改器Demo--
* 作者:赵春生
* 制作时间:15:30 2005-01-27
* 主页: http://timw.yeah.net http://timw.126.com
* 本程序适用于:Win9x/NT
* 代码在Win2000P+SP4 + VC6+SP6测试通过
*/

#include<windows.h>
#include <tlhelp32.h>

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)

{

HANDLE handle=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
PROCESSENTRY32* info=new PROCESSENTRY32;
info->dwSize=sizeof(PROCESSENTRY32);
BOOL report;
DWORD PID;
HANDLE hProcess;

char *szName="game.exe";//目标进程名

int i=0;

//OBJ_INFO
unsigned int OBJ_mycode[4]={0x82ED60A3,0x52FF5000,0xE9C93318,0xFFC658E0};
unsigned int OBJ_jumptomycode[2]={0x39A711E9,0x9000};
unsigned int OBJ_money[1]={0xEA60};//60000$
unsigned int OBJ_money_ip[1]={0x0};
unsigned int OBJ_code[1]={0x0};
char OBJ_path[255];//目标所在目录

GetCurrentDirectory(255,OBJ_path);//获取当前目录
strcat(OBJ_path,"\\game.exe");//目标所在目录

report=Process32First(handle,info);

while(report)
{

if((strncmp(strlwr(info->szExeFile), strlwr(szName), strlen(szName)) == 0)||(strncmp(strlwr(info->szExeFile), strlwr(OBJ_path), strlen(OBJ_path)) == 0))
{
PID=info->th32ProcessID;
//打开游戏进程
hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,PID);

ReadProcessMemory(hProcess,(int *)0x0049466A,OBJ_code,1,0);
//获取0x0049466A代码:如为修改前的0x50或修改后的0xE9则继续运行,
//否则可认定游戏版本不符。
if (OBJ_code[0]==0x50||OBJ_code[0]==0xE9)
{
//写入代码
WriteProcessMemory(hProcess,(int *)0x0082ED80,OBJ_mycode,16,0);
WriteProcessMemory(hProcess,(int *)0x0049466A,OBJ_jumptomycode,6,0);


for (i=0;i<10;i++)
{
//取金钱地址
ReadProcessMemory(hProcess,(int *)0x0082ED60,OBJ_money_ip,4,0);

if (OBJ_money_ip[0]==0x0)
{

Sleep(5000);
}
else
{
//如果得到金钱地址则写入“60000$”
WriteProcessMemory(hProcess,(int *)(OBJ_money_ip[0]+0x228),OBJ_money,2,0);
i=10;
}

}

}
else
MessageBox(NULL,"请检查游戏版本。","错误!",NULL);

break;

}

report=Process32Next(handle,info);

if (report==0)
MessageBox(NULL,"游戏还没有启动吧?","错误!",NULL);
}

CloseHandle(hProcess);
return 0;
}


//////////////////////////////////////////////////////////////////////////////////////////


三:以上代码在Win2000P+SP4 + VC6+SP6测试通过。
  源码可从我的个人主页下载。
  http://timw.yeah.net
  http://timw.126.com

  
12:02 2005-1-28