《东方绯想天》是最近一个很流行的格斗游戏,还支持网络对战。一时感兴趣就拿这个游戏搞一下了。游戏中的界面如下:

    由于本来就只是打算研究一下玩玩看的,所以调试的过程中也没做什么详细的笔记,大虾们飘过,新手耶随便看看好了。
一、  简单的修改
由于这个是格斗游戏,所以存放数据的地址很难找,现在可以知道的信息只有:显示面板上显示的连击伤害点数,还有卡片数目。我用CheatEngine将卡片数目的地址找出来后,再用od挂接,下内存访问断点,在一句这样的代码中停了下来mov cx,word ptr:[esi+0x56c]。而往上回溯会发现很多操作都是以esi的指为基址加上偏移来修改的,所以可以判断这个是玩家数据的基址。
但esi的值是0x014XXXXX这样的,这不是在游戏中的常量区段中而是在进程的堆存储区中,并且每次进行游戏都会改变的,所以要往上回溯,寻找保存着临时数据基址的空间。一直往上可以总结出寻址的规律为:
Mov eax,[0x6e6244]
Mov eax,[eax+0x0C]
此时eax中的即为1p的数据基址。
尽管找到了数据基址,但像HP还有灵力等数据仍然未能确定。我比较笨,只好根据连击的伤害点数扣除的血量估计总血量的大概数值,和在上面那个内存断点中断下来的地方找一下相关的代码。于是找到了以下几个数值的偏移:
对方数据基址:+0x170
HP:+0x174
灵力:+0x482
顺带还找到了扣除HP的过程是先找到玩家的数据基址,在从0x170的地方取出敌对玩家的数据基址,再对对方的HP血量进行修改,其中我找到关键的一句代码就是在0x46baf1处的:
sub     word ptr [esi+174], bx
将这句代码nop掉,双方就都不会掉血了。
而到了这里,写一个锁定HP的修改器就再简单不过了:写一个dll注入到游戏中,将这句代码改成一个跳转跳到自己的处理函数中,判断esi是不是1P的数据基址,是的话则跳过不进行处理,否则就执行这句代码。不过我比较懒,就用了另外一个比较笨的方法来实现:开启一条线程,隔一段时间往游戏进程中的内存地址中写入固定的数值,这样就等于是变相锁定HP了,具体代码如下:

代码:
DWORD WINAPI WorkThread(LPVOID)
{
  DWORD dataaddr;
  DWORD readbytes;
  WORD hp = 0x7fff;
  while(ischeat)
  {
    ReadProcessMemory(thhandle,(LPVOID)0x6e6244,&dataaddr,4,&readbytes);
    dataaddr+=0x0c;
    ReadProcessMemory(thhandle,(LPVOID)dataaddr,&dataaddr,4,&readbytes);
    dataaddr+=0x174;
    WriteProcessMemory(thhandle,(LPVOID)dataaddr,&hp,2,&readbytes);
    Sleep(1000);

  }
  return 0;
}
二、  输入IP的辅助工具
由于这个游戏是支持网站的,而我周围的同学有不少都是用教育网的,于是就写了一个小程序用来保存他们的IP地址然后要对战的时候直接在这个程序中选IP就行了。
寻找游戏中显示IP的方法也是用CheatEngine查找,找到地址后就用OD附加再在那个地址下内存访问断点,然后找到寻址规律如下:
mov     eax, dword ptr [860E08]
mov     esi, dword ptr [eax+4]
mov     ecx, dword ptr [esi+8]
mov     esi, ecx
lea     ebx, dword ptr [esi+420]
内存中的IP地址结构位:
Struct ip{
  WORD ip[4];
  DWORD port;
};
有了这些信息,就可以用ReadProcessMemory跟WriteProcessMemory来修改游戏进程中对应的内存数值了。
具体代码为:
代码:
SetTouhouIP proc index
      local @readdata
      local @readbytes
      local @writedata[12]:BYTE
      local @ipdbase
      local @readbase
      local thhandle
      assume esi:ptr IPDATA
      invoke GetTHProcess
      .if eax == INVALID_HANDLE_VALUE
   invoke MessageBox,NULL,addr error3,addr errinfo,0
   ret
      .endif
      mov thhandle,eax
      invoke ReadProcessMemory,thhandle,database,addr @readdata,4,addr @readbytes
      mov eax, @readdata
      add eax,4
      mov @readbase,eax      
      invoke ReadProcessMemory,thhandle,@readbase,addr @readdata,4,addr @readbytes
      mov eax,@readdata
      add eax,8
      mov @readbase,eax
      invoke ReadProcessMemory,thhandle,@readbase,addr @readdata,4,addr @readbytes
      mov eax,@readdata
      add eax,420h
      mov @ipdbase,eax
      invoke RtlZeroMemory,addr @writedata,12
      mov ecx,index
      mov eax,offset ipdat
      imul ecx,ecx,sizeof IPDATA
      lea esi,[eax+ecx]
      add esi,8
      mov ecx,3
      xor ebx,ebx
      xor eax,eax
      lea edi,@writedata
      .while ecx < -1
            mov al,byte ptr [esi+ecx]
            mov byte ptr [edi+ebx],al
            dec ecx
            add ebx,2
      .endw
      mov eax,dword ptr [esi-8]
      mov dword ptr [edi+ebx],eax
      invoke WriteProcessMemory,thhandle,@ipdbase,addr @writedata,0ch,addr @readbytes
      invoke CloseHandle,thhandle
      ret
SetTouhouIP endp
而至于保存IP地址方法则是建立了一个文件,文件的头一个双字(32位)是一个位图,用来表示空闲的块,然后程序会从中确认哪些块中保存有IP地址。具体的请查看附件的源代码。
程序效果如图
http://i447.photobucket.com/albums/qq192/3927aaa/thz.jpg
上传的附件 src.rar