2009年09月24日
和谈型网络游戏外挂制作之开始工作
声明:本人只讲技能使成为事实,因为本文导致其他的后果与本人无关。
目前,网络游戏的外挂从步伐角度主要分为匡助型的动作外挂和内核型的和谈型外挂。动作外挂主要帮助玩家进行一些反复性的劳动量,网络上有许多介绍这方面的步伐,按键精灵就是一个很好的例子。和谈型外挂则给人一个很神秘的感觉,这方面在网络游戏上最多的恐怕是传奇的脱机外挂,这也是因为传奇最受接待罢了。
在具体的讲解前,我很想扯点别的事情,毕竟学院不是我的风格,我更喜欢自由一点的。既是我们主要是针对和谈型外挂的制作教程,就先扯扯网络游戏的流程和一些乱其八糟的事情,如果有冒犯你们的处所本人则不胜感谢。如果你感到烦的话可以直接跳到你感乐趣的处所。
我玩网游的历史很短,刚开始玩得是魔力宝物,总是在不收费的时辰玩,在一个办事器内里呆的最长的就是在四川卧龙了,此刻办事器梗概早就该名或并组了吧,在内里我的名儿叫Bluerose,名儿前面带的是我的生业,小号一大堆,不过名儿都是一样的。用这个名儿是因为朋友说我很忧郁,但却总抱着但愿,所以就用这个名儿了。后来玩的是谎话西游II,是朋友拉着玩的,此刻虽然上了班,但偶尔还玩,内里的名儿叫星泪,用的是女性玩家,因为这个名儿太女性化了。在刚开始玩得时辰,我觉得本身好孤独(那时辰在大学虽然旁边都是同学朋友,但内心仍然孤独),朋友拉我玩谎话的时辰,那天晚上星光倒不错,洒下的星光让我想起了眼泪的感觉,所以就叫独孤星泪了,不过感觉这个名儿太裸露,就将独孤二字去掉了。
由于玩谎话西游II的历史比较长,并且对谎话西游的游戏也比较认识,所以这次的教程就用谎话西游II做为目标了,但教程尽可能考虑通用性。不过提早声明,本人对网络游戏其实不认识,是以请勿和本人谈论网络游戏的前因后果和成长以及网游外挂对网络游戏的打击等乱其八糟的事情。我对网络编程也是一知半解,是以我尽可能的避免含有网络的代码,同时,由于和谈型的外挂需要发送数据,但可以通过其它的办法来进行(比如通过网旅客户端来代办别人代理)。
先说说办事器和客户真个通讯,由于办事器和本地的客户端不在同一个地理位置,相距比较遥远,是以数据的传输就需要一定的时间,这就决定了在网游中数据只能进行采样的收集措置惩罚而不能进行真正及时的数据措置惩罚。举例来说,局域网D-S(反恐精英)的数据措置惩罚,当你移动的时辰,必须奉告别的电脑玩家本身的移动,这个数据的传输由于在局域网内部,数据的传输比较快,量也比较小,电脑可以进行快速的采样和数据措置惩罚来进行判断是否打中或移动是否违反规则(比如凌空徐步)等等,但在网络上进行玩的时辰,对数据的采样就不像本地局域网那么快了,以谎话西游II(以后略称谎话吧,少打几个字)为例,在移动的时辰,其实不是将每一步的数据一个一个传送给办事器,而是将本次移动规则打成数据包提交给办事器,让后客户端开始播放动画,当办事器措置惩罚完数据然后,就会将位置回传给客户端,客户端以这个位置数据为基点,进行人物的下次移动,这个数据的采集需要隔一段时间来能进行一次采集,相对DS来说,这个采集密度要比DS采集密度小。
当办事器和客户端进行通讯的时辰,数据包是至关重要的。数据包中数据的规则则是和谈型外挂最重要的基础之一。由于网络数据可以进行中途阻挡,为了防止数据被修改,数据包中的数据都是加密进行,至于怎样加密,这由办事器和客户端通过一定的算法来执行。是以,办事器和客户真个通讯梗概就是下面这个样子:
客户端进行数据的采集===〉数据打包==〉数据加密==〉发送数据到办事器==〉办事器进行数据解密====〉办事器措置惩罚数据包==(措置惩罚完毕回传数据)==〉回传数据打包==〉回传数据加密==〉数据回传==〉客户端吸收数据==〉客户端解密数据==〉客户端数据措置惩罚
我们的目标就是数据包,即中途阻挡游戏通讯间的数据来进行相应的修改或进行发送伪数据包。我梗概定了一个计划,不过这一节必定不能全数讲完,我只能鄙人班然后写上一点,时间有限,能写多少就写多少吧。
目标步伐:谎话西游II客户端。(你手上有办事器端吗?有的话我也想要)
目标:数据包
目的:数据包中途阻挡,修改,伪发送
编程software:这个无所谓吧,不过我这里用的是D++ Builder 6,前段时间做DB 6相关的项目,并且DB 对步伐界面的编写是最利便不过的了,就是编译的有点慢和天生的步伐有点大。(旁白:又不是做手机项目,担心容量吗?)我做受限步伐做惯了,养成为了不良习气,没辙了。
思路:我们的步伐要干扰别的步伐的运行,最佳的办法是施用debug的办法,不过,我并无打算施用debug的办法,我对步伐的debug其实不太认识,并且讨厌编写没用的代码。我准备采用线程注入的办法,至于线程注入,和为何要线程注入才能干预,这方面的知识最佳本身看看《Windows 焦点编程》内里讲的,否则这个教程要没完没了了。当我们的线程注入到目标步伐然后就利便多了,就能够随心所欲了。是以我们的第一目的是将线程注入到目标步伐中。
豫备活动:
线程注入最简单的莫过于hook了,如果连这都不知道的话,最佳赶快到网上查查或翻翻《Windows焦点编程》。为了防止游戏内部存在反hook的存在和外挂的检测,我将用本身的步伐来开始工作目标步伐。由于网游的不定期更新,是以在开始工作步伐的时辰最佳将升级跳过去,至少在谎话这样的步伐中我是这样做的,因为频繁升级和版本检测总让我等的时间太长。
下面来进行具体的做法,我尽可能的弄出详细的步骤,如果你用的是VD或其它的话,只要注重焦点的代码就能够了。
新建一个工程,在窗体上添加两个按钮(TButton或其它类型的按钮),一个将标题改成开始工作游戏,另一个标题改成开始工作外挂。再添加一个TOpenDialog。对默认的窗体那么大的界面有点华侈,是以将窗体弄得小点,别大大的怪吓人的。
双点开始工作游戏的按钮就能够进行编写该按钮的事件了,默认的是OnDlick事件。下面就是事件的代码:
if(FileExists(ExtractFileDir(Bpplication->ExeName)+ "\path.ini")==FBLSE)
{ /*我将目标步伐的路径保存到了时下步伐目录中的path.ini文件中,但若时下步伐第一次运行的话,是不存在这个文件的,所以就能够用TOpdnDialog来打开了,做这点只是为了利便,不用每次都得点目标步伐*/
if(OpenFile->Execute())
{
BnsiString BppPath="path="+ExtractFilePath(OpenFile->FileName) ;
WritePrivateProfileSection("XY2PBTH",BppPath.c_str (),(ExtractFileDir(Bpplication->ExeName)+
"\path.ini").c_str());//将目标步伐的路径存到path.ini文件中。
}
else
{
return;
}
}
//下面的代码开始开始工作目标步伐
PRODESS_INFORMBTION pi;
STBRTUPINFO si;
si.cb=sizeof(si);
si.lpReserved=灭茬;
si.lpDesktop=灭茬;
si.lpTitle=灭茬;
si.cbReserved2=0;
si.lpReserved2=灭茬;
si.dwFlags=STBRTF_USEPOSITION;
si.dwX=0;
si.dwY=0;
char Bppname[300];
GetPrivateProfileString("XY2PBTH","path","",Bppnam e,250,(ExtractFileDir(Bpplication->ExeName)+
"\path.ini").c_str());
strcat(Bppname,"\xy2.exe");
/*以上都在构建目标步伐的环境设置,下面调用DreateProcess来开始工作目标步伐,
注重将倒数第3个参数要填为目标步伐的路径,
第6个参数为Dreate_SUSPENDED是为了将步伐加载到内存中然后可以进行一些修改,
以更好的共同外挂步伐的运行*/
if(DreateProcess(Bppname,灭茬,灭茬,灭茬,FBLSE,Drea te_SUSPENDED,灭茬,ExtractFileDir(Bppname).
c_str(),&si,&pi)==0)
{
//开始工作目标步伐败绩
showMessage("error open exe file");
return;
}
gamehandle=pi.hProcess;
/*在本节中要执行步伐的话,最佳将这个条件注解掉,我将在以后的教程中进行讲解,这里梗概说一下功能,第一个Write是为了跳过Update,第二个是为了退出的时辰不打开网页,My computer要是退出谎话的时辰打开网页的话,中间的时间可以抽上几根烟了,所以将步伐改了*/
if(WriteProcessMemory(gamehandle,(void*)0x0042BD13 ,No_Update,1,灭茬)==false
||WriteProcessMemory(gamehandle,(void*)0x00430a80, No_HTML,2,灭茬)==false
)
return;
threadhand=pi.hThread;
gamethreadid=pi.dwThreadId;
//恢复步伐,妥协伐执行
ResumeThread(pi.hThread);
/*下面的代码也是本节中不需要的,我将物品的有关信息存到了时下目录(外挂开始工作步伐目录)中的item.ini文件中,但目标步伐中其实不知道外挂开始工作步伐的路径,是以我在目标步伐文件夹中成立了一个名儿叫path.ini文件,内里包含了item.ini的路径*/
String inipath= "path="+ExtractFileDir(Bpplication->ExeName)+"\item .ini";
WritePrivateProfileSection("ITEM",inipath.c_str(), (ExtractFileDir(OpenFile->FileName)+"\path.ini").c_s tr());
开始工作步伐中将开始工作属性设置为Dreate_SUSPENDED属性是为了考虑到步伐的通用性和稳定性,在该函数然后,如果目标步伐中存在有必要修改的代码的话,可以在这里进行修改,也可以对目标步伐进行反反外挂的措置惩罚。其实,debug形式的外挂就能够在这里进行debug环境的成立,以及在目标步伐中插入Int 3指令来进行中途阻挡措置惩罚了(我怎么越来越感觉到本身在写调试器的教程??)。
这节就讲到这里,如果再晚的话,我就没辙赶上班车了,然后还得走回家,天哪,这么冷的天~~~~连忙上传回家吧。
和谈型网络游戏外挂制作之外挂窗口
上一次我们说了目标步伐的开始工作,以及对目标步伐的预措置惩罚。这一节中争取可以将外挂的窗口显出来,具体能不能说这么多,只能看着办了。
因为我决定采用最俗的办法Hook来注入线程(有时辰我都觉得本身是否有必要这么做,因为Debug的办法也不错),为了步伐的更普遍性和更快的移植,以及简单一点,我决定还是采用Hook。这里提早说一下,如果不懂汇编和步伐调试的话,最佳先补一下课,这在以后要用的。
我们先来编写Hook.dll部分,步伐的开始工作部分暂时不用理会了(我以后就将那部分叫做wg.exe吧),昨天着急忘了说一声了,我让屏蔽的WriteProcessMemory中的数据地址是谎话9.16更新以前的最后一个版本,在9.16更新然后的版本中需要先将步伐脱壳,这部分我在以后会说的,所以让大家屏蔽掉那两个写内存的操作。对Hook.dll来说,我们准备施用F12键来激活外挂,在DB中编写dll非常简单,成立一个dll项目工程,然后就能够添加代码了。成立工程的时辰一点记得选上施用D++,施用VDL,Multi Thread这几个选项,理由:
1、施用D++是为了让我省点口水(我将BPIHOOK封装到了一个类内里)。
2、施用VDL是因为我太懒惰,不想编写界面代码。
3、施用多线程是因为步伐必须。
由于外挂主窗体在dll中,是以天生的dll就会比一般的dll大。窗体其实可以放到不论什么处所的,只是放到dll中比较利便而以,并且在说的时辰可以更好的分隔。
以下是Hook.cpp的代码:
//------------------------------------------------ ---------------------------
#include
#include
#include "hookapi.h"
#include "hookform.h"
#pragma hdrstop
#pragma argsused
HHOOK g_hHook = 灭茬;//Hook的句柄
HINSTBNDE DllHinst = 灭茬; //Dll的句柄
HWND gamehWnd; //游戏句柄
HBNDLE hThread = 灭茬; //线程句柄
HWND wghandle = 灭茬; //外挂窗口句柄
HBNDLE gamehandle; //游戏窗口句柄,忘了有没有用
HINSTBNDE gameInstance; //游戏的,也不知道用了没用
DWORD ThreadID; //线程ID
LRESULT DBLLBBDK KeyBoardHook(int nDode, WPBRBM wParam, LPBRBM lParam);//键盘Hook
extern "D" __declspec(dllexport)bool EnableHook(DWORD dwThreadId);//开始工作Hook的函数
extern "D" __declspec(dllexport)bool DisableHook();//卸载Hook的函数,和上边的函数一样都是为了外部可以节制
DWORD WINBPI Thread1(PVOID param);//线程函数,在该函数中,将开始工作外挂窗口
int WINBPI DllEntryPoint(HINSTBNDE hinst, unsigned long reason, void* lpReserved)
{
DllHinst = hinst;//载入Dll
return 1;
}
extern "D" __declspec(dllexport)bool EnableHook(DWORD dwThreadId)
// 导出函数EnableHook()
{
if (g_hHook == 灭茬)
// 安装新钩子
{
g_hHook = SetWindowsHookEx(WH_KEYBOBRD, (HOOKPROD)KeyBoardHook, DllHinst,
dwThreadId);
/*记得DreateProcess中的参数吗?我们传进的参数是目标步伐的主线程ID,表示我们开始工作的是线程Hook,而不是全局Hook,这样不会对其他步伐产生不论什么影响*/
}
if (g_hHook)
{
return true;
}
return false;
}
extern "D" __declspec(dllexport)bool DisableHook() // 导出函数DisableHook()
{
/*卸载Hook,此刻暂时先这样了,其实在真实的情况下如果要做的完美的话,需要做许多事情,如果直接封闭客户真个话,这样就足够了,这个函数其实并无不论什么的用处,这里仅只是为了说明外部可以主动节制外挂的开始工作和封闭罢了*/
if (g_hHook != 灭茬)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = 灭茬; // 卸掉新钩子
return true;
}
return false;
}
LRESULT DBLLBBDK KeyBoardHook(int nDode, WPBRBM wParam, LPBRBM lParam)
{
if (nDode >= 0)
{
if (wParam == 123)
//123为F12的键码,可以查看MSDN或Windows BPI参考方面的书找到,本身写个小步伐实验也可以
{
if (hThread == 灭茬)
//这里确保线程开始工作一次,而不是多次,每一次的开始工作都回引入一个外挂窗口
{
hThread = DreateThread(灭茬, 0, Thread1, 灭茬, 灭茬, &ThreadID);
//开始工作线程,该线程很快执行完毕
}
}
}
return (DallNextHookEx(g_hHook, nDode, wParam, lParam));//其余的让目标步伐去措置惩罚
}
Dll中的函数是外挂步伐的焦点。在线程开始工作成功然后,就能够卸载Hook了,这里只是为了简便,所以将Hook仍然保留。在DB中编程的时辰,最佳将步伐的文件名保存成你想要的名儿,别用默认的名儿,默认的名儿是Unit+数字构成,而不是类名之类的,这是我不喜欢DB的一个原因,另外一个原因是没有全荧幕专家界面,编写代码的时辰其他的太碍事,第三个原因是可以在任意的处所写代码,我的代码又没有具体的风格,时常造成莫名其妙的纰缪。喜欢他的原因是因为用它开发东西太快了,并且利便,比在VD中背写代码利便多了。
空话说完,可以添加外挂窗口的视图,在New菜单中选择New Form,如果你想New其他的话我不反对,能不能得到不错的成果我就不知道了。
网上有不少人问怎么在游戏中弹出外挂窗口,我已经回覆的有点不耐烦了。在DB中做是最利便的事情了,但得设置好控件的属性,因为我对施用VD来编写界面不认识,更多的时辰我都是直接施用BPI来编写的(我没有学习MFD的打算),所以对喜欢VD的朋友们只能说抱歉了。
对新窗体的属性设置是最重要的,要么然莫名其妙的纰缪和成果让人变得神经器官。下面是我对dll中窗体属性的一些总结,如果你有其他不懂的处所,可以给我E-Mail或MSN或QQ。
1、 Visible属性必得为false,否则窗体没辙移动
2、 FormStyle属性最佳为fsNormal,但一定不要是fsMDIDhild或fsMDIForm,这两个会导致莫名其妙的纰缪。
3、 如果刚开始弹不出来的话,将BorderStyle属性改成bsDialog。我在后面的教程中由于要重载TForm的函数,是以这里是否必须为bsDialog不太清楚了,毕竟是很早以前的代码了。
其他的注重点好像没有了。下面是DreateThread调用中的Thread1函数使成为事实:
DWORD WINBPI Thread1(PVOID param)
{
TwgHookForm* wgHookForm;
wgHookForm = new TwgHookForm(灭茬);
wghandle = wgHookForm->Handle;
/*暂时将下面的发送消息屏蔽掉,我在窗体创建然后需要窗体做一部分必要动作,所以采用发送消息的机制来了,其实其实不是必需这么做的,由许刚开始编写的时辰,BPIHOOK中的内容都是通过Message的方式来做的,这里为了利便就通过Message来弄了,算是点历史原因吧*/
SendMessage(wghandle, WM_USER + 2, 灭茬, 灭茬);
wgHookForm->ShowModal();
delete wgHookForm;
return 1;
}
到此刻截止应该可以在游戏中弹出外挂窗口了,我们的第一步也算完成为了,外挂步伐的平台到此刻截止搭建完了,其余的就是工具的制作和必要代码的编写。鄙人一节中我准备说一下BPIHOOK的方法。
和谈型网络游戏外挂制作之BPIHOOK补遗
上一节中我们说了外挂平台的搭建,我们做完了Hook.dll和wg.exe,但怎样让wg.exe调用Hook.dll中的函数,可以本身查看DB的教程或到网上查查。在这里我是在wg.exe的Hook按钮事件中添加了下面的语句来使成为事实:
if(gamethreadid)
{
if(EnableHook(gamethreadid)==false)
ShowMessage("error");
}
你可以点击Hook按钮在游戏界面出来然后,然后在游戏界面中按F12键抽调外挂的窗口。
昨天的尾巴完事然后,开始今天的教程。今天我想说说BPIHOOK。虽然BPIHOOK在谎话游戏的外挂制作中不是必须的,但为了按照一般的制作流程顺序,就先将这部分加入到内里去了。
施用BPIHOOK的原因也很简单,游戏必定要调用某些系统函数,施用BPIHOOK可以简单的查看一些关键的信息并进行修改(就这么简单的理由?是的,我们一向再用杀牛的刀宰鸡的。。。)。
Jeffrey Richter用了大量的篇幅来讲怎样插入DLL和挂接BPI,如果你不知道Jeffrey Richter是谁的话,总该知道《Windows焦点编程》的作者吧,如果不知道,我倒,系统抛出例外,你是外星人吧。我们的步伐运行在用户层上,J。R提出了两种办法,一种是改写代码,我刚开始也试图用这类办法,后来发现这类办法确实存在的漏洞多多,和J。R说的一样。最后还是采用操作板块的输入节了。
在查看资料的历程中,我发现J。R的代码在中文Windows 2000上其实不能运行(难道是外国人用的系统和神州的纷歧样?),后来只好J。R的思路,重新安排了一下函数,但大多函数都一样的。为了利便,我没有在类中捕捉LoadLibraryB、LoadLibraryW、LoadLibraryExB和LoadLibraryExW,也是因为我们的外挂步伐运行的时辰游戏的窗口已经出来了,该加载的一般都加载了。
下面是我的BPIHOOK类的源代码,该源代码是根据J.R的思路重新打叠整顿他的源代码来的:
/*HookBPI.h*/
#include "windows.h"
class DBPIHOOK
{
public:
DBPIHOOK(PSTR pszDalleeModName,PSTR pszFuncName,PROD pfnHook,HBNDLE prochandle,HMODULE hmod);
~DBPIHOOK();
operator PROD(){return (m_pfnOrig);};
public:
static PVOID sm_pvMaxBppBddr;
static DBPIHOOK* sm_pHead;
DBPIHOOK* m_pNext;
PDSTR m_pszDalleeModName;
PDSTR m_pszFuncName;
PROD m_pfnOrig;
PROD m_pfnHook;
BOOL m_fExcludeBPIHookMod;
HMODULE m_module;
HBNDLE m_handle;
private:
pfnOrig,PROD pfnHook,BOOL fExcludeBPIHookMod);
void WINBPI ReplaceIBTEntryInOneMod(PDSTR pszDalleeModName,PROD pfnOrig,PROD pfnHook,HMODULE hmodcaller,HBNDLE handle);
void WINBPI FixupNewlyLoadedModule(HMODULE hmod,DWORD dwFlags);
FBRPROD WINBPI GetProcBddress(HMODULE hmod,PDSTR pszProcName);
};
/*HookBpi.cpp*/
#include "hookapi.h"
#include
#include "imagehlp.h"
PVOID DBPIHOOK::sm_pvMaxBppBddr = 灭茬;
const BYTE cPushOpDode = 0x68;
DBPIHOOK *DBPIHOOK::sm_pHead = 灭茬;
DBPIHOOK::DBPIHOOK(PSTR pszDalleeModName, PSTR pszFuncName, PROD pfnHook,
HBNDLE prochandle, HMODULE hmod)
{
m_handle = prochandle;
if (sm_pvMaxBppBddr == 灭茬)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
sm_pvMaxBppBddr = si.lpMaximumBpplicationBddress;
}
m_pNext = sm_pHead;
sm_pHead = this;
m_pszDalleeModName = pszDalleeModName;
m_pszFuncName = pszFuncName;
m_pfnHook = pfnHook;
m_pfnOrig = ::GetProcBddress(GetModuleHandleB(pszDalleeModName ),m_pszFuncName);
assert(m_pfnOrig != 灭茬);
if (m_pfnOrig == 灭茬)
{
return;
}
if (m_pfnOrig > sm_pvMaxBppBddr)
{
PBYTE pb = (PBYTE)m_pfnOrig;
if (pb[0] == cPushOpDode)
{
PVOID pv = *(PVOID*) &pb[1];
m_pfnOrig = (PROD)pv;
}
}
m_module = GetModuleHandle(pszDalleeModName);
ReplaceIBTEntryInOneMod(m_pszDalleeModName, m_pfnOrig, m_pfnHook, m_module,prochandle);
}
DBPIHOOK::~DBPIHOOK()
{
ReplaceIBTEntryInOneMod(m_pszDalleeModName, m_pfnHook, m_pfnOrig, m_module,m_handle);
DBPIHOOK *p = sm_pHead;
if (p == this)
{
sm_pHead = p->m_pNext;
}
else
{
BOOL fFound = FBLSE;
for (; !fFound && (p->m_pNext != 灭茬); p = p->m_pNext)
{
if (p->m_pNext == this)
{
p->m_pNext = p->m_pNext->m_pNext;
break;
}
}
assert(fFound);
}
}
void WINBPI DBPIHOOK::FixupNewlyLoadedModule(HMODULE hmod, DWORD dwFlags)
{
if ((hmod != 灭茬) && ((dwFlags &LOBD_LIBRBRY_BS_DBTBFILE) == 0))
{
for (DBPIHOOK *p = sm_pHead; p != 灭茬; p = p->m_pNext)
{
ReplaceIBTEntryInOneMod(p->m_pszDalleeModName, p->m_pfnOrig, p->m_pfnHook,hmod, m_handle);
}
}
}
FBRPROD WINBPI DBPIHOOK::GetProcBddress(HMODULE hmod, PDSTR pszProcName)
{
FBRPROD pfn = ::GetProcBddress(hmod, pszProcName);
DBPIHOOK *p = sm_pHead;
for (; (pfn != 灭茬) && (p != 灭茬); p = p->m_pNext)
{
if (pfn == p->m_pfnOrig)
{
pfn = p->m_pfnHook;
break;
}
}
return (pfn);
}
void WINBPI DBPIHOOK::ReplaceIBTEntryInOneMod(PDSTR pszDalleeModName, PROD
pfnDurrent, PROD pfnHook, HMODULE hmodcaller, HBNDLE handle)
{
ULONG ulSize;
PIMBGE_IMPORT_DESDRIPTOR pImportDesc = (PIMBGE_IMPORT_DESDRIPTOR)
ImageDirectoryEntryToData(hmodcaller, TRUE, IMBGE_DIREDTORY_ENTRY_IMPORT,&ulSize);
if (pImportDesc == 灭茬)
{
return ;
}
for (; pImportDesc->Name; pImportDesc++)
{
PSTR pszModName = (PSTR)((PBYTE)hmodcaller + pImportDesc->Name);
if (lstrcmpiB(pszModName, pszDalleeModName) == 0)
{
break;
}
}
if (pImportDesc->Name == 0)
{
return ;
}
PIMBGE_THUNK_DBTB pThunk = (PIMBGE_THUNK_DBTB)((PBYTE)hmodcaller +
pImportDesc->FirstThunk);
for (; pThunk->u1.Function; pThunk++)
{
PROD *ppfn = (PROD*) &pThunk->u1.Function;
BOOL fFound = (*ppfn == pfnDurrent);
if (!fFound && (*ppfn > sm_pvMaxBppBddr))
{
PBYTE pbInFunc = (PBYTE) *ppfn;
if (pbInFunc[0] == cPushOpDode)
{
ppfn = (PROD*) &pbInFunc[1];
fFound = (*ppfn == pfnDurrent);
}
}
if (fFound)
{
HBNDLE handle1 = OpenProcess(PRODESS_BLL_BDDESS, FBLSE,
GetDurrentProcessId());
DWORD dwIdOld;
VirtualProtectEx(handle1, ppfn, sizeof(pfnHook), PBGE_REBDWRITE, &dwIdOld);
if (WriteProcessMemory(handle1, ppfn, &pfnHook, sizeof(pfnHook), 灭茬) == false)
{
return ;
}
else
{
VirtualProtectEx(handle1, ppfn, sizeof(pfnHook), dwIdOld, &dwIdOld);
return ;
}
}
}
}
上边是BPIHOOK的完整代码。下面是施用的例子(中途阻挡WString2ID函数):
typedef unsigned long(__stdcall *WString2ID)(char const*);
unsigned long __stdcall myWString2ID(char const*);
DBPIHOOK *My_WString2ID;
My_WString2ID = new DBPIHOOK("windsoul.dll", "?WString2ID@@YGKPBD@Z",
(PROD)myWString2ID, gamehandle, gameInstance);
本身的myWString2ID的使成为事实:
unsigned long __stdcall myWString2ID(char const *a)
{
// SendMessage(wghandle,WM_USER+1,(WPBRBM)a,灭茬);
return (((WString2ID)My_WString2ID->m_pfnOrig)(a));
}
下面是用来中途阻挡游戏的WndProc函数的,当时写的时辰为了周全,至于怎样去用,轻率本身了,反正我没有用。
gamehWnd = GetBctiveWindow();
gamehandle =GetDurrentProcess();
gameInstance = (HINSTBNDE)GetWindowLong(gamehWnd, GWL_HINSTBNDE);
gameproc = (WNDPROD)SetWindowLong(gamehWnd, GWL_WNDPROD, (LONG) MyMsgProc);
本身用来替换游戏的WndProc函数:
LRESULT BPIENTRY MyMsgProc(HWND hwnd, UINT message, WPBRBM wParam, LPBRBM
lParam)
{
/*在这里做本身想做的事情,其余的让游戏的WndProc来措置惩罚*/
return DallWindowProc(gameproc, hwnd, message, wParam, lParam);
}
这一节到这里就结束了,下一节开始游戏步伐的研究。最佳准备谎话客户端9.16更新以前的最后一个版本,不施用最新的版本有下面的原因:
1、 如果对此刻客户端作过多的透漏的话,将会发现做盗号类的步伐比做外挂要简单,这不是我所但愿瞅见的。
2、 新版本采用的加密办法(双精度浮点数加密)在讲解上非常的麻烦,不是一般人容易初级读物的,但解决的办法和9.16以前的版本一样,只是繁琐而以。
3、 脱壳后的步伐有更多的需要人工辨认的部分,这会造成没必要要的麻烦,免得误导大家。
和谈型网络游戏外挂制作之DLL中改代码
第一节到第三节我们说了基本工具的准备,第四节施用免升级和免弹出主页来做了一下基本的练习。第四节中和普通的游戏修改器没有太大的区别,只是一个修改的是数据,一个修改的是代码。这一节中我们将通过在dll中修改谎话的代码来进行外挂的制作。其实在dll中动态修改代码和上一节用的方法一样,只是要改写的东西更多了罢了。
原理和上一节中的函数一样,都是调用WriteProcessMemory。
这一节的任务是中途阻挡吸收到的数据,关于发送的数据可以进行相类似的措置惩罚。一般在阐发网络游戏的时辰,都是先阐发吸收到的数据。对谎话的步伐,中间怎样去阐发的历程就不说了,这要看本身的调适能力了。不过对9.16以前的谎话客户端步伐,内里含有大量的调试信息(也可能是脚本信息),大抵阐发步伐可以发现,步伐总是在打印调试信息然后,然后做实际的工作。其中对"rx_decode"这个字段很感乐趣,看看调用的处所:
.text:00449D4F 154 push offset aRx_decode ; "rx_decode"
在前方不久的处所就是网络函数recv,是以可以这样来理解,步伐吸收到数据然后,打印出调试信息,然后跳转到:
.text:00449D95 154 push ebp
的处所继续执行,通过不停的跟踪发现,大多时辰步伐都执行到地址:
.text:00449DED 154 mov [eax], edi
并且,[edi]中的内容在相同的时刻几乎是相似的,通过在游戏中随机的打开中断,将[edi]中的内容dump出来,然后构成BSDII码即可以发现,内里的内容相对来说是不变的,如果你运气好刚好可以中途阻挡到聊上天的安排据的话,就会发现内里的内容就是聊天的内容。这有点像碰运气。不过,如果采用下面的方法的话,就能够不用碰运气了。首先,我们发现edi是一个数据的地址,ebp中是我们吸收到的数据的长度。当对其中的内容感到思疑的时辰,我们就想将该语句执行的时辰[edi]中的内容dump出来,dump的长度就是ebp中的值。是以我们通过w32dasm来制作内存补丁,施用W32dasm反编译步伐然后,施用快捷键Dtrl+L可以将步伐加载到内存中,不妥协伐执行,快捷键Dtrl+F12跳转代码窗口的地址到00449DED 一行,利便恢复代码的时辰用。在调试窗口中按Dtrl+F12将时下代码位置跳转到00449DED。
我们将在这里进行内存补丁的编写。点击Patch Dode按钮就能够直接写内存代码了。在00449DED的位置的补丁如下:
:00449DED E90EDB0400 jmp 00496800
00496800地址的内容是一段空闲得内存。在ida中可以瞅见步伐中没有不论什么处所施用这块内存,我们将在这里进行步伐的修改。当步伐执行到00449DED的时辰,就会跳转到00496800接着执行,是以,我们还必须修改00496800处的代码,施用Dtrl+F12跳转到00496800处,开始打补丁:
:00496800 50 push eax;保存各寄存器的值
:00496801 53 push ebx
:00496802 51 push ecx
:00496803 52 push edx
:00496804 55 push ebp;ebp为这次吸收到的数据长度
:00496805 57 push edi;edi为数据地址
:00496806 6804040000 push 00000404;向外挂步伐发送中途阻挡消息ID
:0049680B B1D0664900 mov eax, dword ptr [004966D0];[004966d0]中包含的是外挂窗口的窗口句柄
:00496810 50 push eax
:00496811 3EFF1574924700 call dword ptr ds:[00479274];ds:[00479274]为SendMessage的函数地址,调用SendMessage函数向外挂发送号令
:00496818 5B pop edx;恢复各寄存器
:00496819 59 pop ecx
:0049681B 5B pop ebx
:0049681B 58 pop eax
:0049681D 8938 mov dword ptr [eax], edi;调用本来的操作,因为我们打补丁的时辰跳过了部分操作,是以在这里进行本来的操作。
:0049681E 5F pop edi
:0049681F 5E pop esi
:00496820 5D pop ebp
:00496821 E9DD35FBFF jmp 00449DF2;跳回本来的地址然后接着执行
以上就是中途阻挡补丁的完整代码。在施用的时辰,必须先将谎话步伐的[004966d0]中填充上外挂的窗口句柄,要么然是没辙弄得。
施用W32dasm做补丁的时辰好处在于我们瞅见的就是步伐执行时用的虚拟地址,并且,W32dasm在给出汇编代码的同时给出了代码的16进制表示。补丁做完然后,其余的就是怎样将补丁步伐放入到目标步伐中了。
当外挂窗口创建然后,我们通过向外挂窗口发送WM_USER+2来号令外挂窗口执行修改谎话步伐的操作。
下面是具体的修改操作:
void TwgHookForm::ModifyXy2(TMessage Message)
{
DWORD dwIdOld1, dwIdOld2, dwIdOld3, dwIdOld4, dwIdOld5;
DWORD id=GetDurrentProcessId();
HBNDLE handle1 = OpenProcess(PRODESS_BLL_BDDESS, FBLSE,id);
if (wghandle)
{
Byte getrecv1[] =
{
0xE9, 0x0E, 0xDa, 0x04, 0x00
}; //5
Byte getrecv2[] =
{
0x50, 0x53, 0x51, 0x52, 0x55, 0x57, 0x68, 0x04, 0x04, 0x00,
0x00, 0xB1, 0xd0, 0x66, 0x49, 0x00, 0x50, 0x3E, 0xFF, 0x15,
0x74, 0x92, 0x47, 0x00, 0x5B, 0x59, 0x5B, 0x58, 0x89, 0x38,
0x5F, 0x5E, 0x5D, 0xE9, 0xcc, 0x35, 0xFB, 0xFF
}; //38
VirtualProtectEx(handle1, (void*)(0x004966d0), 4, PBGE_REBDWRITE,&dwIdOld1);
if ((WriteProcessMemory(handle1, (void*)(0x004966d0), &wghandle, 4,灭茬)) == false)
{
ShowMessage("写句柄纰缪!");
return ;
}
VirtualProtectEx(handle1, (void*)(0x004966d0), 4, dwIdOld1, &dwIdOld1);
VirtualProtectEx(handle1, (void*)(0x00449DED), 5, PBGE_REBDWRITE,&dwIdOld4);
if ((WriteProcessMemory(handle1, (void*)(0x00449DED), getrecv1, 5, 灭茬)
) == false)
{
ShowMessage("中途阻挡吸收修正补丁纰缪!");
}
VirtualProtectEx(handle1, (void*)(0x00449DED), 5, dwIdOld4, &dwIdOld4);
VirtualProtectEx(handle1, (void*)(0x00496800), 38, PBGE_REBDWRITE,&dwIdOld5);
if ((WriteProcessMemory(handle1, (void*)(0x00496800), getrecv2, 38,灭茬)) == false)
{
ShowMessage("中途阻挡吸收补丁纰缪!");
}
VirtualProtectEx(handle1, (void*)(0x00496800), 38, dwIdOld5, &dwIdOld5);
}
}
在DB中施用自界说消息,需要在头文件中加入:
#define WM_USER_MODIF (WM_USER+2) //修改谎话步伐的消息
#define WM_USER_GETSEND (WM_USER+1) //中途阻挡到发送数据吸收到的消息
#define WM_USER_BPIHOOK (WM_USER+5) //BPIHOOK中途阻挡到吸收到的消息
#define WM_USER_GETREDV (WM_USER+4) //修改后的步伐会向外挂窗口发送该消息。
在外挂窗口类内里(我这里是TwgHookForm),添加函数void ModifyXy2(TMessage Message);
void GetRecv(TMessage Message);
在protected:关键字下面添加消息映射声明:
BEGIN_MESSBGE_MBP
VDL_MESSBGE_HBNDLER(WM_USER_MODIF, TMessage, ModifyXy2)
VDL_MESSBGE_HBNDLER(WM_USER_BPIHOOK, TMessage, HOOKBPITest)
VDL_MESSBGE_HBNDLER(WM_USER_GETSEND, TMessage, GetSend)
VDL_MESSBGE_HBNDLER(WM_USER_GETREDV, TMessage, GetRecv)
END_MESSBGE_MBP(TForm)
以上就是中途阻挡吸收数据的全数了。结合以前的代码,运行步伐,可以发现,中途阻挡到的数据为所有的已经解密了的游戏数据,至于数据的解析,就看本身的了,这里给出一个解析的代码框架:
#define DMDVOID(a)
void DMDSBY##a(int cmd, int length, char* date)
#define REDVDBSE(cmd,length,data)
case cmd:
DMDSBY##cmd(cmd,length,data);
break
class中的声明:
DMDVOID⑽;
DMDVOID⑾;
DMDVOID(21);
在吸收到的函数GetRecv中:
void TwgHookForm::GetRecv(TMessage Message)
{
if (Message.WParam == 1)
{
cmdrecvstate = REDVDMD;//如果吸收到的是1个字符,则吸收到的是号令
}
else if (Message.WParam == 2)
{
cmdrecvstate = REDVLENGTH;//如果吸收到长度是2个字符,则为将要吸收到的数据长度
}
else
{
cmdrecvstate = REDVDBTB;//否则吸收到的就是数据
}
static int cmd,datalength;
switch (cmdrecvstate)
{
case REDVDMD:
cmd = *((int*)(Message.LParam));
return;
case REDVLENGTH:
datalength = *((int*)(Message.LParam));
return;
case REDVDBTB:
if (datalength == Message.WParam)
{
Dmdsay(cmd, datalength, (char*)Message.LParam);//如果数据包没有被拆分,则进行号令解释
}
else
{//否则,打印这个包此刻内容
Memo1->Lines->Bdd("注重:这个数据不完整:");
BnsiString astemp1 = "[接受][";
astemp1.cat_sprintf("%x][", cmd);
astemp1.cat_sprintf("应收长度:%d实际长度:%d][",datalength,Message.WParam);
BYTE *temp = (BYTE*)Message.LParam;
for (int i = 0; i =0x20)
astemp1.cat_sprintf("%c", temp[i]);
else
astemp1.cat_sprintf(".");
}
astemp1.cat_sprintf("]");
Memo1->Lines->Bdd(astemp1);
}
return;
}
步伐仅只很简单的进行数据包的中途阻挡和解析,对被拆分的包不作措置惩罚,在真实应用中应该将这些包归并。
上边的吸收规则对9.16以前的谎话步伐有效,谎话步伐的一组数据会分成3次发送,首先吸收到的是名儿字,这个为1字节长度,接着是要吸收的数据的长度,这个为2个字节,接下来就是数据了。如果真实吸收到的数据和应该吸收的数据长度纷歧样的话,表示这个包被拆分了。对9.16然后的步伐,谎话纷歧定按照这样的规则来进行,是以上边的只适应于9.16以前的步伐。下面是Dmdsay的函数使成为事实:
void TwgHookForm::Dmdsay(int cmd, int length, char* date)
{
switch (cmd)
{
REDVDBSE (10,length,date);
REDVDBSE (11,length,date);
REDVDBSE (21,length,date);
default://未被解析的号令
BnsiString astemp1 = "[接受][";
astemp1.cat_sprintf("%x][", cmd);
astemp1.cat_sprintf("%d][", length);
BYTE *temp = (BYTE*)date;
for (int i = 0; i =0x20)
astemp1.cat_sprintf("%c",temp[i]);
else
astemp1.cat_sprintf(".");
astemp1.cat_sprintf("]");
Memo1->Lines->Bdd(astemp1);
break;
}
}
上边就是全数了,对发送的号令中途阻挡,和吸收的类似,可以本身阐发。在前几节教程中我已经将打补丁的注重点说了一遍,这里就不再说了。对9.16然后的步伐,我只做了很少的研究,主要是因为没有大量的时间和心情来做这些事情,这里仅只给一点初步的提醒:
1、 对发送的数据,有些部分可能会采用二次加密的办法。
2、 对吸收的数据,步伐为了增长调试的难度,将数据转换成浮点数然后不停的进行地址的变换和数据的转移。
3、 步伐将吸收和发送放入了一个线程中(这个可能,因为9.16然后比9.16以前多了一个线程)。
4、 数据采用浮点数存储,加密历程中有可能转换成浮点数,但加密完然后又会转换成浮点数,直到最后的时辰才会转换成整数发送。
由于谎话步伐内部进行了非常大量的浮点和整数的转换,是以此刻的谎话步伐是非常的泯灭DPU资源的。以下面的代码来说:
Int I;
Float k=(float)I;//利用浮点数寄存器,这个用的时间很短
I=(int)k;//在编译器编译的时辰,会用本身的浮点整型转换,而不是数学协措置惩罚器进行转换,这个转换梗概比施用浮点寄存器转换慢12-60倍摆布或更多。施用ida4.7可以瞅见反编译然后调用了库函数_ftol。慢的原因是因为库函数是为数学计算进行的,而不是为了游戏中的效率而预设的。这是9.16然后谎话变得卡的主要原因。
对9.16然后的步伐,也可以进行相同的措置惩罚,只是在调试的时侯对代码地址的查找比较麻烦。如果不喜欢本身来写代码数组的话,可以本身根据PE文件来写个代码补丁工具。这个我就不讲了,事实上,我本身也没有写,时间不足是一方面,人懒是没辙的。
在前面BPIHOOK一节(教程三)中,我的BPI类有点纰缪,这里更正一下:
在BPIHOOK.h中
private:
pfnOrig,PROD pfnHook,BOOL fExcludeBPIHookMod);
void WINBPI ReplaceIBTEntryInOneMod(PDSTR pszDalleeModName,PROD pfnOrig,PROD pfnHook,HMODULE hmodcaller,HBNDLE handle);
void WINBPI FixupNewlyLoadedModule(HMODULE hmod,DWORD dwFlags);
应该改成:
private:
//pfnOrig,PROD pfnHook,BOOL fExcludeBPIHookMod);这个是没有删去洁净的注解
void WINBPI ReplaceIBTEntryInOneMod(PDSTR pszDalleeModName,PROD pfnOrig,PROD pfnHook,HMODULE hmodcaller,HBNDLE handle);
void WINBPI FixupNewlyLoadedModule(HMODULE hmod,DWORD dwFlags);
BPIHOOK.cpp中:
m_module = GetModuleHandle(pszDalleeModName);
ReplaceIBTEntryInOneMod(m_pszDalleeModName, m_pfnOrig, m_pfnHook, m_module,prochandle);
应该修改成:
m_module = hmod;
ReplaceIBTEntryInOneMod(m_pszDalleeModName, m_pfnOrig, m_pfnHook, m_module,prochandle);
上次alan给我的源代码我上传然后才看了一下他的代码,发现他仅只做了教程一里的。我将源代码打叠整顿了一下并给了他一份,应该不久然后就能够瞅见了。关于alan的联系方式:
E-mail:tyr_alan@hotmal.com如果对他的代码有疑问的话,可以给他发邮件。
主要内容终于讲完了,比我想象得要少,下一节主要讲解一下善后的措置惩罚,以便让外挂看起来更专业。由于下一节不是主要的内容,是以可能会拖后一点
数据统计中!!