设为首页
收藏本站
本站论坛
   
3
3
   
  文章列表      

[分享]如何在内存中修改数据的网游外挂?

现在很多游戏都是把一些信息存入内存单元的,那么我们只需要修改具体内存值就能修改游戏中的属性,很多网络游戏也不外于此。 =========================================================== 曾几何时,一些网络游戏也是可以用内存外挂进行修改的,后来被发现后,这些游戏就把单一内存地址改成多内存地址校验,加大了修改难度,不过仍然可以通过内存分析器可以破解的。诸如“FPE”这样的软件便提供了一定的内存分析功能。   “FPE”是基于内存外挂的佼佼者,是家喻户晓的游戏修改软件。很多同类的软件都是模仿“FPE”而得到玩家的认可。而“FPE”实现的技术到现在都没有公开,很多人只能够通过猜测“FPE”的实现方法,实现同类外挂。笔者也曾经模仿过“FPE”实现相应的功能,如“内存修改”、“内存查询”等技术。稍后会对此技术进行剖析。   既然要做内存外挂,那么就必须对Windows的内存机制有所了解。计算机的内存(RAM)总是不够用的,在*作系统中内存就有物理内存和虚拟内存之分,因为程序创建放入物理内存的地址都是在变化的,所以在得到游戏属性时并不能够直接访问物理内存地址。在v86模式下,段寄存器使用方法与实模式相同,那么可以通过段寄存器的值左移4位加上地址偏移量就可以得到线性地址,而程序创建时在线性地址的中保留4MB-2GB的一段地址,游戏中属性便放于此。在windows中把虚拟内存块称之为页,而每页为4KB,在访问内存时读取游戏属性时,为了不破坏数据完整性的快速浏览内存地址值,最好一次访问一页。   在*作进程内存时,不需要再使用汇编语言,Windows中提供了一些访问进程内存空间的API,便可以直接对进程内存进行*作。但初学者一般掌握不了这一项技术,为了使初学者也能够对内存进行*作,做出基于内存控制的外挂,笔者把一些内存*作及一些内存*作逻辑进行了封装,以控件形式提供给初学者。控件名为:MpMemCtl。   初学者在使用此控件时,要先安装外挂引擎控件包(在此后的每篇文章中外挂引擎控件包仅提供与该文章相应的控制控件),具体控件安装方式,请参阅《Delphi指南》,由于篇幅所限,恕不能详细提供。   在引擎安装完成后,便可以在Delphi中的组件栏内,找到[MP GameControls]控件组,其中可以找到[MpMemCtl]控件。初学者可以使用此控件可以对内存进行控制。   一、 得到进程句柄   需要*作游戏内存,那么首先必须确认要*作的游戏,而游戏程序在运行时所产生的每一个进程都有一个唯一的句柄。   使用控件得到句柄有三种方法:   1、 通过控件打开程序得到句柄。   在控件中,提供了startProgram方法,通过该方法,可以打开程序得到进程句柄,并且可以返回进程信息。 PProcInf PROCESS_INFORMATION; MpMemCtl.startProgram(  FilePath:String; //程序路径 var aProc_Info:PROCESS_INFORMATION //进程信息 ):BOOLEAN   该方法提供了两个参数,第一个参数为要打开的程序路径,第二个参数为打开程序后所创建进程的进程信息。使用这个方法在得到进程信息的同时,并给控件的ProcHandle(进程句柄)属性进行了附值,这时可以使用控件直接对内存进程读写*作。其应用实例如下: Var  PProcInf PROCESS_INFORMATION; begin  MpMemCtl1.startProgram(edit1.Text, PProcInfo)   2、通过控件根据程序名称得到句柄。   在控件中,对系统运行进程也有了相应的描述,控件提供了两个方法,用于根据程序名称得到相应的进程句柄。getProcIDs()可以得到系统现在所运行的所有程序的名称列表。getProcID()可以通过所运行程序名称,得到相应进程的句柄。 getProcIDs():TStrings //所返回为多行字符串型 getProcID( aProcName:String //应用程序名称 ):Thandle; //应用程序进程句柄   其应用实例如下:   首先可以通过getProcIDs()并把参数列表返回ComboBox1.Items里: ComboBox1.Items:=MpMemCtl1.getProcIDs();   接着可以通过getProcID()得到相应的进程句柄,并给控件的ProcHandle(进程句柄)属性进行了附值,这时可以使用控件直接对内存进程读写*作。 MpMemCtl1.getProcID(ComboBox1.Text)   3、通过控件根据窗口名称得到句柄。   在控件中,控件提供了两个方法,用于根据窗口名称得到相应的进程句柄。可以通过getALLWindow()得到所有在进程中运行的窗口。getWinProcHandle()可以通过相应的窗口名称,得到相应的进程的句柄。 getALLWindow( aHandle:THandle //传入当前窗口的句柄 ):TStrings; //返回当前所有运行窗口的名称 getWinProcHandle( aWindowName:String //传入当前窗口名称 ):Thandle; //返回窗口的句柄   其应用实例如下:   首先可以通过getALLWindow ()并把参数列表返回ComboBox1.Items里: ComboBox1.Items:=MpMemCtl1. getALLWindow(Handle);   接着可以通过getWinProcHandle ()得到相应的进程句柄,并给控件的ProcHandle(进程句柄)属性进行了附值,这时可以使用控件直接对内存进程读写*作。 MpMemCtl1. getWinProcHandle (ComboBox1.Text);   二、使游戏暂停   在程序中,为了便于更好的得到游戏的当前属性。在控件中提供了游戏暂停方法。只需要调用该方法,游戏便可以自由的暂停或启动。该方法为:pauseProc() pauseProc(  aType:integer //控制类型 )   控制类型只能够传入参数0或1,0代表使游戏暂停,1代表取消暂停。其应用实例如下: MpMemCtl1.pauseProc(0); //暂停游戏 MpMemCtl1.pauseProc(1); //恢复暂停   三、读写内存值   游戏属性其实寄存在内存地址值里,游戏中要了解或修改游戏属性,可以通过对内存地值的读出或写入完成。   通过控件,要读写内存地址值很容易。可以通过调用控件提供的getAddressValue()及setAddressValue()两个方法即可,在使用方法之前,要确认的是要给ProcHandle属性进行附值,因为对内存的*作必须基于进程。给ProcHandle属性附值的方法,在上文中已经介绍。无论是对内存值进行读还是进行写,都要明确所要*作的内存地址。 getAddressValue( //读取内存方法 aAddress:pointer; //*作的内存地址 var aValue:integer //读出的值 ):Boolean; setAddressValue( //写入内存方法 aAddress:pointer; //*作的内存地址 aValue:integer //写入的值 ):Boolean;   要注意的是,传入内存地址时,内存地址必须为Pointer型。其应用实例如下:   读取地址值(如果“主角”等级所存放的地址为4549632): var  aValue:Integer; begin  MpMemCtl1.getAddressValue(Pointer(‘4549632’),aValue);   这时aValue变量里的值为内存地址[4549632]的值。   写入地址值: MpMemCtl1.setAddressValue(Pointer(Strtoint(‘4549632’)),strtoint(87));   通过该方法可以把要修改的内存地址值改为87,即把“主角”等级改为87。   四、内存地址值分析   在游戏中要想要到游戏属性存放的内存地址,那么就对相应内存地址进行内存分析,经过分析以后才可得到游戏属性存放的人存地址。   控件提供两种基于内存地址的分析方法。一种是按精确地址值进行搜索分析,另一种是按内存变化增减量进行搜索分析。   1、 如果很明确的知道当前想要修改的地址值,那么就用精确地址值进行搜索分析   在游戏中,需要修改人物的经验值,那么首先要从游戏画面上获得经验值信息,如游戏人物当前经验值为9800,需要把经验值调高,那么这时候就需要对人物经验值在内存中搜索得到相应的内存地址,当然很可能在内存中地址值为9800的很多,第一次很可能搜索出若干个地址值为9800的地址。等待经验值再有所变化,如从9800变为了20000时,再次进行搜索,那么从刚刚所搜索到的地址中,便可以进一步获得范围更少的内存地址,以此类推,那么最后可得到经验值具体存放的地址。   如要用控件来实现内存值精确搜索,其实方法很简单,只需要调用该控件的Search()方法即可。但是在搜索之前要确认搜索的范围,正如前文中所说:“而程序创建时在线性地址的中保留4MB-2GB的一段地址”,所以要搜索的地址应该是4MB-2GB之间,所以要把控件的MaxAddress属性设为2GB,把控件的MinAddress属性设为4MB。还有一个需要确认的是需要搜索的值,那么应该把SearchValue属性设置为当前搜索的值。如果需要显示搜索进度那么可以把ShowGauge属性挂上一个相应的TGauge控件(该控件为进度条控件)。 search(  isFirst:Boolean //是否是第一次进行搜索 ):Boolean   在搜索分析时为了提高搜索效率、实现业务逻辑,那么需要传入一个参数,从而确认是否是第一次进行内存。其应用实例如下: maxV:=1024*1024*1024; maxV:=2*MaxV; minV:=4*1024*1024; V:=StrToInt(Edit1.Text); with MpMemCtl1 do begin  MaxAddress:=maxV;  MinAddress:=minV;  SearchValue:=SeaarchV;  ShowGauge:=Gauge1;  Search(first) end; if first then first:=false;   2、 如果不明确当前想要修改的地址值,只知道想要修改的值变大或变小,那么就按内存变化增减量进行搜索分析。   如有些游戏的人物血值不显示出来,但要对人物血值进行修改,那么只有借助于内存量增减变化而进行搜索分析出该人物血值存放的地址。如果人物被怪物打了一下,那么人物血值就会减少,那么这时候就用减量进行搜索分析,如果人物吃了“血”人物血值就会增加,那么这时候就用增量进行搜索分析。经过不断搜索,最后会把范围放血值的内存地址给搜索出来。   如要用控件来实现内存值精确搜索,其实方法很简单,只需要调用该控件的compare()方法即可。MaxAddress、MinAddress属性设置上面章节中有详细介绍,在此不再重提。在此分析中不需要再指定SearchValue属性。如果需要显示搜索进度那么可以把ShowGauge属性挂上一个相应的TGauge控件。 compare (  isFirst:Boolean //是否是第一次进行搜索 aType:Integer //搜索分析类型 ):Boolean   在搜索分析时为了提高搜索效率、实现业务逻辑,那么需要传入一个参数,从而确认是否是第一次进行内存。搜索分析类型有两种:如果参数值为0,那么就代表增量搜索。如果参数值为1,那么就代表减量搜索。其应用实例如下: if RadioButton1.Checked then v:=0 else v:=1;  maxV:=1024*1024*1024;  maxV:=2*MaxV;  minV:=4*1024*1024;  with MpMemCtl1 do  begin   MaxAddress:=maxV;   MinAddress:=minV;   ShowGauge:=Gauge1;   compare(first,v); end; if first then first:=false;   五、得到内存地址值   在控件中,提供获得分析后内存地址列表的方法,只需要调用getAddressList()方法,便可以获得分析过程中或分析结果地址列表。但如果使用的是按内存变化增减量进行搜索分析的方法,那么第一次可能会搜索出来很多的地址,致使返回速度过长,那么建议使用getAddressCount()方法确定返回列表为一定长度后才给予返回。 getAddressList():TStrings //返回地址字符串列表 getAddressCount():Integer //返回地址字符串列表长度   其应用实例如下: if MpMemCtl1.getAddressCount() 100 then  listbox1.Items:=MpMemCtl1.getAddressList();   通过以上五个步骤,便可以整合成一个功能比较完备的,基于内存控制方法的游戏外挂。有了“FPE”的关键部份功能。利用此工具,通过一些方法,不仅仅可以分析出来游戏属性单内存地址,而且可以分析出一部份多内存游戏属性存放地址。 - 作者: kalrt 2006年01月11日, 星期三 19:24  回复(0) |  引用(0) 加入博采 HOOK(钩子)函数的详细说明钩子HOOK函数是Windows消息处理机制的一部分,通过设置“钩子”,应用程序可以在系统级对所有消息、事件进行过滤,访问在正常情况下无法访问的消息。当然,这么做也是需要付出一定的代价的。由于多了这么一道处理过程,系统性能会受到一定的影响,所以大家在必要的时候才使用“钩子”,并在使用完毕及时将其删除。 ============================================================   首先让我们看看HOOK函数是怎么安装、调用和删除的。应用程序通常是调用SetWindowsHookEx()函数来进行安装的,其函数的原型如下: SetWindowsHookEx( Int idHook; HOOKPROC lpfn; HINSTANCE hMod; DWORD dwThreadId; ); 参数说明: idHook 是”钩子”的类型,”钩子”的类型一共有13种,具体如下表: “钩子”类型 解释 WH_CALLWNDPROC 系统将消息发送到指定窗口之前的“钩子” WH_CALLWNDPROCRET 消息已经在窗口中处理的“钩子” WH_CBT 基于计算机培训的“钩子” WH_DEBUG 差错“钩子” WH_FOREGROUNDIDLE 前台空闲窗口“钩子” WH_GETMESSAGE 接收消息投递的“钩子” WH_JOURNALPLAYBACK 回放以前通过WH_JOURNALRECORD“钩子”记录的输入消息 WH_JOURNALRECORD 输入消息记录“钩子” WH_KEYBOARD 键盘消息“钩子” WH_MOUSE 鼠标消息“钩子” WH_MSGFILTER 对话框、消息框、菜单或滚动条输入消息“钩子” WH_SHELL 外壳“钩子” WH_SYSMSGFILTER 系统消息“钩子” lpfn 指向“钩子”过程的指针。 hMod “钩子”过程所在模块的句柄。 dwThreadId “钩子”相关线程的标识。  通常我们都是把”钩子”做成动态链接库,这样的好处是可以是系统内的每个进程访问。但是也可以在系统中直接调用,我的建议还是用动态库。如果用动态库的话,那么SetWindowsHookEx()中的第三个参数就是该动态链接库模块的句柄;对于一个只供单个进程访问的”钩子”,可以将其”钩子”过程放在安装”钩子”的同一个线程内,此时SetWindowsHookEx()中的第三个参数为该线程的hInstance。安装”钩子”有两种方法:1.你可以把他做成动态连接库文件,和程序一起编译。2.你可以在程序的任何地方直接调用。第2种的方法太麻烦,我不建议用,在这里我就不详细介绍啦。相比之下第1种比较简单。其”钩子”的过程都在动态链接库内完成。SetWindowsHookEx()函数是一个安装函数,如故一个由某种类型的”钩子”监视的事件发生,系统就会调用相应类型的”钩子”链开始处的”钩子”过程,”钩子”链的每个”钩子”过程都要考虑是否把事件传递给下一个”钩子”过程。如果要传递的话,就要调用CallNestHookEx()函数。这个函数成功时返回”钩子”链中下一个”钩子”过程的返回值,返回值的类型依赖于”钩子”的类型。这个函数的原型如下: LRESULT CallNextHookEx( HHOOK hhk; int nCode; WPARAM wParam; LPARAM lParam; );   其中hhk为当前”钩子”的句柄,由SetWindowsHookEx()函数返回。NCode为传给”钩子”过程的事件代码。wParam和lParam 分别是传给”钩子”过程的wParam值,其具体含义与”钩子”类型有关。释放”钩子”   释放”钩子”比较简单,他只有一个参数。当不在需要”钩子”时,应及时将其释放。他是调用UnhookWindowsHookEx()函数来实现的,函数原型如下: UnhookWindowsHookEx( HHOOK hhk; ); 函数成功返回TRUE,否则返回FALSE。如果我这样讲您还是不明白的话,请看下面给出的一些典型“钩子”代码和说明。 LRESULT WINAPI CallWndProc(int nCode,WPARAM wParam,LPARAM lParam) { if(nCode<0) return CallNextHookEx(NULL,nCode,wParam,lParam); switch(nCode) { case HC_ACTION: //”钩子”程序要处理什么的代码 break; default: break; } return CallNextHookEx(NULL,nCode,wParam,lParam); }   这是WH_CALLWNDPROC”钩子”的代码,此”钩子”允许程序监视由函数SendMessage发送给窗口过程的消息。系统将消息发送到目的窗口之前调用WH_CALLWNDPROC “钩子”过程。 LRESULT WINAPI CallwndProc(int nCode,WPARAM,wParam,LPARAM lParam) { if(nCode<0) return callNextHookEx(NULL,nCode,wParam,lParam); switch(nCode) { case HC_ACTION: switch(wParam) { Case PM_REMOVE: //某个应用程序调用了GetMessage函数或者是带PM_REMOVE参数的//PeekMessage函数,从消息队列中移去一个消息。 Break; Case PM_NOREMOVE: //某个应用程序以PM_NOREMOVE为参数调用PeekMessage函数 break; default: break; } break; default: break; } return CallNextHookEx(NULL,nCode,wParam,lParam); }   这是调用WH_GETMESSAGE的函数,此函数允许应用程序监视函数GetMessage和 PeekMessage返回的消息。应用程序可以用钩子WH_GETMESSAGE来监视鼠标和键盘的输入以及其他系统发送到消息队列中的消息。 LRESULT CALLBACK CBTProc(int nCode,WPARAM wParam,LPARAM lParam) { If(nCode<0) Return callNextHookEx(NULL,nCode,wParam,lParam); Switch(nCode) { case HCBT_ACTIVATE: //系统将激活一个窗口 break; case HCBT_CLICKSKIPPED: //系统从系统消息队列中移去一个鼠标消息 break; case HCBT_CREATEWND: //系统将创建一个窗口 break; case HCBT_DESTROYWND: //系统将关闭一个窗口 break; case HCBT_KEYSKIPPED: //系统从系统消息队列中移去一个键盘消息 break; case HCBT_MINMAX: //系统将最大化或最小化一个窗口 break; case HCBT_MOVESIZE: //系统将移动一个窗口或改变一个窗口的大小 break; case HCBT_QS: //系统在系统消息队列中检索到WM_QUEUESYNC消息 break; case HCBT_SETFOCUS: //系统设置键盘输入窗口 break; case HCBT_SYSCOMMAND: //将要执行一个系统命令 break; default: //可以添加其他代码 break; } return CallNextHookEx(NULL,nCode,wParam,lParam); }   每种”钩子”类型都有其对应的函数,这些函数的参数都是一样的,有兴趣的朋友可以在MSDN中找的他们的详细说明。下面我给出一个完整的”钩子”安装和删除的过程的代码。 #include "stdafx.h" #include "hook.h" HINSTANCE hInstance; HHOOK hhkKeyboard; BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } hInstance=(HINSTANCE)hModule; return TRUE; } LRESULT KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam) { MessageBeep(-1); return CallNextHookEx(hhkKeyboard,nCode,wParam,lParam); } HOOK_API BOOL EnableKeyboardCapture() { if(!(hhkKeyboard=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyboardProc,hInstance,0))) return FALSE; return TRUE; } HOOK_API BOOL DisableKeyboardCapture() { return UnhookWindowsHookEx(hhkKeyboard); } 按是否需要游戏客户端分为 内挂 和 脱机(这是我的称呼,你可以用别的名词称呼)内挂又可以按照修改游戏的方法分成 模拟*作型 、 修改内存型 、 封包型 。 =================================================== 脱机的不需要特别说明,利用 Mircosoft Winsock Control 6.0 可以实现功能相当强大的脱机客户端,在这方面VB相对于其他编程语言没有明显的劣势,甚至是优势。对于内挂型的~ 一、 模拟按键型因为在VB中嵌入API已经不是什么难事,所以VB6应用程序也可以方便的调用 keybd_event mouse_event 来实现键盘和鼠标的模拟。在WindowsNT上,这两个函数是不被推荐使用的可以使用SendInput代替~但是SendInput在VB的API浏览器没有包含~ 二、 修改内存修改目标游戏内存,类似于FPE,GM之类的软件,但是修改的目标确是不同,一般上说FPE等单机游戏内存修改器主要是修改数据区内容,而网络游戏修改内存外挂主要是修改程序指令~ 具体要使用到的API是 WriteProcessMemory ReadProcessMemoryF OpenProcess CloseHandle 第一个被调用的API是OpenProcess,第一个参数dwDesiredAccess 使用的常量在VB中是没有声明能够的。在winnt.h(4318)的定义如下 #define PROCESS_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF) 用这个值做参数调用OpenProcess得到的*作权将是最大的,同时破坏性也最大,使用不当容易使游戏进程DOWN掉。所以我个人推荐使用: #define PROCESS_VM_READ (0x0010) #define PROCESS_VM_WRITE (0x0020) 分别对应ReadProcessMemory/WriteProcessMemory 第二个参数一般做0 意义是 是否让自进程继承这个句柄第三个参数就是目标进程ID,进程ID可以通过FindWindow得到句柄后调GetWindowThreadProcessId得到打开进程,获得进程句柄后,就可以利用ReadProcessMemory/WriteProcessMemory来读写游戏进程数据了~ 记住使用完成后,及时调用CloseHandle来释放句柄,不然你的程序运行一段时间后就会出错~三、 封包型封包型也可以分为两种: 1)代理型 2)hook winsock api型第一种主要就是在本机虚拟一个游戏服务器,让游戏的数据包先发给你的程序,有你处理后再发往真实的游戏服务器,这种方法简单灵活,但是,目前绝大多数的网络游戏都对这型外挂有ANTI机制。(一下不知道该用什么词 :) ) HOOKAPI型,一直是VB外挂程序的禁区,因为目前所使用的大多数方法,都需要建立一个DLL EJECT到目标进程中,而VB建立的DLL是不能满足要求的~ 下面来说重点吧~(至少我这么认为) WIN32API中有这么一套API,叫做调试API. 先是DebugActiveProcess, BOOL DebugActiveProcess( DWORD dwProcessId // process to be debugged ); 参数就一个就是目标进程的ID,可以用我前面说的方法来得到。当开始调试一个进程后,就可以使用WaitForDebugEvent, BOOL WaitForDebugEvent( LPDEBUG_EVENT lpDebugEvent, // pointer to debug event structure DWORD dwMilliseconds // milliseconds to wait for event ); 来等待DEBUG事件了~ DEBUG_EVENT的结构定义是这样的: ************************************************************* typedef struct _DEBUG_EVENT { // de DWORD dwDebugEventCode; DWORD dwProcessId; DWORD dwThreadId; union { EXCEPTION_DEBUG_INFO Exception; CREATE_THREAD_DEBUG_INFO CreateThread; CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; EXIT_THREAD_DEBUG_INFO ExitThread; EXIT_PROCESS_DEBUG_INFO ExitProcess; LOAD_DLL_DEBUG_INFO LoadDll; UNLOAD_DLL_DEBUG_INFO UnloadDll; OUTPUT_DEBUG_STRING_INFO DebugString; RIP_INFO RipInfo; } u; } DEBUG_EVENT; typedef struct _EXCEPTION_RECORD { // exr DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD; typedef struct _EXCEPTION_DEBUG_INFO { // exdi EXCEPTION_RECORD ExceptionRecord; DWORD dwFirstChance; } EXCEPTION_DEBUG_INFO; ************************************************************* 在众多事件中,我们关心的只有EXCEPTION_DEBUG_EVENT,当dwDebugEventCode==EXCEPTION_DEBUG_EVENT时,U的结构展开是这样的! ExceptionCode表明了DEBUG事件产生的原因;可能的值中,需要我们关心的只有EXCEPTION_BREAKPOINT。当目标执行到指令INT3时就会触发上面的EXCEPTION_BREAKPOINT时间,所以,我们大可以在SEND/RECV等API入口处放一个INT3指令,当游戏调用该API时就触发了一个调试事件,进程被挂起,然后通知调试进程,这个时候,我们就可以从游戏程序的堆栈中读取参数,甚至修改~~ 最后,做了一些必要的出来后记得调用ContinueDebugEvent来继续游戏程序的执行~
 
 
   
 
网上赚钱申请指南
 

友情链接
个人主页
 
 
 
 

wel come to . 欢迎光临
宇宙浪仔