Chinaunix首页 | 论坛 | 博客
  • 博客访问: 16498232
  • 博文数量: 5645
  • 博客积分: 9880
  • 博客等级: 中将
  • 技术积分: 68081
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-28 13:35
文章分类

全部博文(5645)

文章存档

2008年(5645)

我的朋友

分类:

2008-04-28 21:32:24

下载本文示例代码
  第一节到第三节我们说了基本工具的准备,第四节使用免升级和免弹出主页来做了一下基本的练习。第四节中和普通的游戏修改器没有太大的区别,只是一个修改的是数据,一个修改的是代码。这一节中我们将通过在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出来,然后组成ASCII码便可以发现,里面的内容相对来说是不变的,如果你运气好刚好可以拦截到聊天数据的话,就会发现里面的内容就是聊天的内容。这有点像碰运气。不过,如果采用下面的方法的话,就可以不用碰运气了。首先,我们发现edi是一个数据的地址,ebp中是我们接收到的数据的长度。当对其中的内容感到怀疑的时候,我们就想将该语句执行的时候[edi]中的内容dump出来,dump的长度就是ebp中的值。因此我们通过w32dasm来制作内存补丁,使用W32dasm反编译程序之后,使用快捷键Ctrl L可以将程序加载到内存中,不让程序执行,快捷键Ctrl F12跳转代码窗口的地址到00449DED 一行,方便恢复代码的时候用。在调试窗口中按Ctrl F12将当前代码位置跳转到00449DED。  我们将在这里进行内存补丁的编写。点击Patch Code按钮就可以直接写内存代码了。在00449DED的位置的补丁如下::00449DED E90ECA0400 jmp 00496800  00496800地址的内容是一段空闲得内存。在ida中可以看到程序中没有任何地方使用这块内存,我们将在这里进行程序的修改。当程序执行到00449DED的时候,就会跳转到00496800接着执行,因此,我们还必须修改00496800处的代码,使用Ctrl 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 A1D0664900 mov eax, dword ptr [004966D0];[004966d0]中包含的是外挂窗口的窗口句柄 :00496810 50 push eax :00496811 3EFF1574924700 call dword ptr ds:[00479274];ds:[00479274]为SendMessage的函数地址,调用SendMessage函数向外挂发送命令 :00496818 5A pop edx;恢复各寄存器 :00496819 59 pop ecx :0049681A 5B pop ebx :0049681B 58 pop eax :0049681C 8938 mov dword ptr [eax], edi;调用原来的操作,因为我们打补丁的时候跳过了部分操作,因此在这里进行原来的操作。 :0049681E 5F pop edi :0049681F 5E pop esi :00496820 5D pop ebp :00496821 E9CC35FBFF jmp 00449DF2;跳回原来的地址之后接着执行  以上就是拦截补丁的完整代码。在使用的时候,必须先将大话程序的[004966d0]中填充上外挂的窗口句柄,要不然是没办法弄得。  使用W32dasm做补丁的时候好处在于我们看到的就是程序执行时用的虚拟地址,并且,W32dasm在给出汇编代码的同时给出了代码的16进制表示。补丁做完之后,剩下的就是如何将补丁程序放入到目标程序中了。  当外挂窗口创建之后,我们通过向外挂窗口发送WM_USER 2来命令外挂窗口执行修改大话程序的操作。  下面是具体的修改操作: void TwgHookForm::ModifyXy2(TMessage Message){ DWORD dwIdOld1, dwIdOld2, dwIdOld3, dwIdOld4, dwIdOld5; DWORD id=GetCurrentProcessId(); HANDLE handle1 = OpenProcess(PROCESS_ALL_ACCESS, FALSE,id); if (wghandle) {  Byte getrecv1[] =   {    0xE9, 0x0E, 0xCa, 0x04, 0x00   }; //5  Byte getrecv2[] =   {    0x50, 0x53, 0x51, 0x52, 0x55, 0x57, 0x68, 0x04, 0x04, 0x00,    0x00, 0xA1, 0xd0, 0x66, 0x49, 0x00, 0x50, 0x3E, 0xFF, 0x15,    0x74, 0x92, 0x47, 0x00, 0x5A, 0x59, 0x5B, 0x58, 0x89, 0x38,    0x5F, 0x5E, 0x5D, 0xE9, 0xcc, 0x35, 0xFB, 0xFF   }; //38  VirtualProtectEx(handle1, (void*)(0x004966d0), 4, PAGE_READWRITE,&dwIdOld1);  if ((WriteProcessMemory(handle1, (void*)(0x004966d0), &wghandle, 4,NULL)) == false)  {   ShowMessage("写句柄错误!");   return ;  }  VirtualProtectEx(handle1, (void*)(0x004966d0), 4, dwIdOld1, &dwIdOld1);  VirtualProtectEx(handle1, (void*)(0x00449DED), 5, PAGE_READWRITE,&dwIdOld4);  if ((WriteProcessMemory(handle1, (void*)(0x00449DED), getrecv1, 5, NULL)) == false) {  ShowMessage("拦截接收修正补丁错误!"); } VirtualProtectEx(handle1, (void*)(0x00449DED), 5, dwIdOld4, &dwIdOld4); VirtualProtectEx(handle1, (void*)(0x00496800), 38, PAGE_READWRITE,&dwIdOld5); if ((WriteProcessMemory(handle1, (void*)(0x00496800), getrecv2, 38,NULL)) == false) {  ShowMessage("拦截接收补丁错误!"); } VirtualProtectEx(handle1, (void*)(0x00496800), 38, dwIdOld5, &dwIdOld5);}}  在CB中使用自定义消息,需要在头文件中加入: #define WM_USER_MODIF (WM_USER 2) //修改大话程序的消息#define WM_USER_GETSEND (WM_USER 1) //拦截到发送数据接收到的消息#define WM_USER_APIHOOK (WM_USER 5) //APIHOOK拦截到接收到的消息#define WM_USER_GETRECV (WM_USER 4) //修改后的程序会向外挂窗口发送该消息。  在外挂窗口类里面(我这里是TwgHookForm),添加函数void ModifyXy2(TMessage Message); void GetRecv(TMessage Message);  在protected:关键字下面添加消息映射声明: BEGIN_MESSAGE_MAPVCL_MESSAGE_HANDLER(WM_USER_MODIF, TMessage, ModifyXy2)VCL_MESSAGE_HANDLER(WM_USER_APIHOOK, TMessage, HOOKAPITest)VCL_MESSAGE_HANDLER(WM_USER_GETSEND, TMessage, GetSend)VCL_MESSAGE_HANDLER(WM_USER_GETRECV, TMessage, GetRecv)END_MESSAGE_MAP(TForm)  以上就是拦截接收数据的全部了。结合以前的代码,运行程序,可以发现,拦截到的数据为所有的已经解密了的游戏数据,至于数据的解析,就看自己的了,这里给出一个解析的代码框架: #define CMDVOID(a) \void CMDSAY##a(int cmd, int length, char* date)#define RECVCASE(cmd,length,data) \case cmd:\CMDSAY##cmd(cmd,length,data);\break  class中的声明:CMDVOID(10);CMDVOID(11);CMDVOID(21);  在接收到的函数GetRecv中: void TwgHookForm::GetRecv(TMessage Message){ if (Message.WParam == 1) {  cmdrecvstate = RECVCMD;//如果接收到的是1个字符,则接收到的是命令 } else if (Message.WParam == 2) {  cmdrecvstate = RECVLENGTH;//如果接收到长度是2个字符,则为将要接收到的数据长度 } else {  cmdrecvstate = RECVDATA;//否则接收到的就是数据 } static int cmd,datalength; switch (cmdrecvstate) {  case RECVCMD:   cmd = *((int*)(Message.LParam));   return;  case RECVLENGTH:   datalength = *((int*)(Message.LParam));   return;  case RECVDATA:   if (datalength == Message.WParam)   {    Cmdsay(cmd, datalength, (char*)Message.LParam);//如果数据包没有被拆分,则进行命令解释   }   else   {//否则,打印这个包现在内容    Memo1->Lines->Add("注意:这个数据不完整:");    AnsiString astemp1 = "[接受][";    astemp1.cat_sprintf("%x][", cmd);    astemp1.cat_sprintf("应收长度:%d实际长度:%d][",datalength,Message.WParam);    BYTE *temp = (BYTE*)Message.LParam;    for (int i = 0; i < Message.WParam; i )    {     astemp1.cat_sprintf("%0.2x ", temp[i]);    }    astemp1.cat_sprintf("][");    for (int i = 0; i < Message.WParam; i )    {     if(temp[i]>=0x20)      astemp1.cat_sprintf("%c", temp[i]);     else      astemp1.cat_sprintf(".");    }    astemp1.cat_sprintf("]");    Memo1->Lines->Add(astemp1);   }   return;  }  程序仅仅很简单的进行数据包的拦截和解析,对于被拆分的包不作处理,在真实应用中应该将这些包合并。  上面的接收规则对于9.16之前的大话程序有效,大话程序的一组数据会分成3次发送,首先接收到的是名字字,这个为1字节长度,接着是要接收的数据的长度,这个为2个字节,接下来就是数据了。如果真实接收到的数据和应该接收的数据长度不一样的话,表示这个包被拆分了。共2页。 1 2 :   第一节到第三节我们说了基本工具的准备,第四节使用免升级和免弹出主页来做了一下基本的练习。第四节中和普通的游戏修改器没有太大的区别,只是一个修改的是数据,一个修改的是代码。这一节中我们将通过在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出来,然后组成ASCII码便可以发现,里面的内容相对来说是不变的,如果你运气好刚好可以拦截到聊天数据的话,就会发现里面的内容就是聊天的内容。这有点像碰运气。不过,如果采用下面的方法的话,就可以不用碰运气了。首先,我们发现edi是一个数据的地址,ebp中是我们接收到的数据的长度。当对其中的内容感到怀疑的时候,我们就想将该语句执行的时候[edi]中的内容dump出来,dump的长度就是ebp中的值。因此我们通过w32dasm来制作内存补丁,使用W32dasm反编译程序之后,使用快捷键Ctrl L可以将程序加载到内存中,不让程序执行,快捷键Ctrl F12跳转代码窗口的地址到00449DED 一行,方便恢复代码的时候用。在调试窗口中按Ctrl F12将当前代码位置跳转到00449DED。  我们将在这里进行内存补丁的编写。点击Patch Code按钮就可以直接写内存代码了。在00449DED的位置的补丁如下::00449DED E90ECA0400 jmp 00496800  00496800地址的内容是一段空闲得内存。在ida中可以看到程序中没有任何地方使用这块内存,我们将在这里进行程序的修改。当程序执行到00449DED的时候,就会跳转到00496800接着执行,因此,我们还必须修改00496800处的代码,使用Ctrl 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 A1D0664900 mov eax, dword ptr [004966D0];[004966d0]中包含的是外挂窗口的窗口句柄 :00496810 50 push eax :00496811 3EFF1574924700 call dword ptr ds:[00479274];ds:[00479274]为SendMessage的函数地址,调用SendMessage函数向外挂发送命令 :00496818 5A pop edx;恢复各寄存器 :00496819 59 pop ecx :0049681A 5B pop ebx :0049681B 58 pop eax :0049681C 8938 mov dword ptr [eax], edi;调用原来的操作,因为我们打补丁的时候跳过了部分操作,因此在这里进行原来的操作。 :0049681E 5F pop edi :0049681F 5E pop esi :00496820 5D pop ebp :00496821 E9CC35FBFF jmp 00449DF2;跳回原来的地址之后接着执行  以上就是拦截补丁的完整代码。在使用的时候,必须先将大话程序的[004966d0]中填充上外挂的窗口句柄,要不然是没办法弄得。  使用W32dasm做补丁的时候好处在于我们看到的就是程序执行时用的虚拟地址,并且,W32dasm在给出汇编代码的同时给出了代码的16进制表示。补丁做完之后,剩下的就是如何将补丁程序放入到目标程序中了。  当外挂窗口创建之后,我们通过向外挂窗口发送WM_USER 2来命令外挂窗口执行修改大话程序的操作。  下面是具体的修改操作: void TwgHookForm::ModifyXy2(TMessage Message){ DWORD dwIdOld1, dwIdOld2, dwIdOld3, dwIdOld4, dwIdOld5; DWORD id=GetCurrentProcessId(); HANDLE handle1 = OpenProcess(PROCESS_ALL_ACCESS, FALSE,id); if (wghandle) {  Byte getrecv1[] =   {    0xE9, 0x0E, 0xCa, 0x04, 0x00   }; //5  Byte getrecv2[] =   {    0x50, 0x53, 0x51, 0x52, 0x55, 0x57, 0x68, 0x04, 0x04, 0x00,    0x00, 0xA1, 0xd0, 0x66, 0x49, 0x00, 0x50, 0x3E, 0xFF, 0x15,    0x74, 0x92, 0x47, 0x00, 0x5A, 0x59, 0x5B, 0x58, 0x89, 0x38,    0x5F, 0x5E, 0x5D, 0xE9, 0xcc, 0x35, 0xFB, 0xFF   }; //38  VirtualProtectEx(handle1, (void*)(0x004966d0), 4, PAGE_READWRITE,&dwIdOld1);  if ((WriteProcessMemory(handle1, (void*)(0x004966d0), &wghandle, 4,NULL)) == false)  {   ShowMessage("写句柄错误!");   return ;  }  VirtualProtectEx(handle1, (void*)(0x004966d0), 4, dwIdOld1, &dwIdOld1);  VirtualProtectEx(handle1, (void*)(0x00449DED), 5, PAGE_READWRITE,&dwIdOld4);  if ((WriteProcessMemory(handle1, (void*)(0x00449DED), getrecv1, 5, NULL)) == false) {  ShowMessage("拦截接收修正补丁错误!"); } VirtualProtectEx(handle1, (void*)(0x00449DED), 5, dwIdOld4, &dwIdOld4); VirtualProtectEx(handle1, (void*)(0x00496800), 38, PAGE_READWRITE,&dwIdOld5); if ((WriteProcessMemory(handle1, (void*)(0x00496800), getrecv2, 38,NULL)) == false) {  ShowMessage("拦截接收补丁错误!"); } VirtualProtectEx(handle1, (void*)(0x00496800), 38, dwIdOld5, &dwIdOld5);}}  在CB中使用自定义消息,需要在头文件中加入: #define WM_USER_MODIF (WM_USER 2) //修改大话程序的消息#define WM_USER_GETSEND (WM_USER 1) //拦截到发送数据接收到的消息#define WM_USER_APIHOOK (WM_USER 5) //APIHOOK拦截到接收到的消息#define WM_USER_GETRECV (WM_USER 4) //修改后的程序会向外挂窗口发送该消息。  在外挂窗口类里面(我这里是TwgHookForm),添加函数void ModifyXy2(TMessage Message); void GetRecv(TMessage Message);  在protected:关键字下面添加消息映射声明: BEGIN_MESSAGE_MAPVCL_MESSAGE_HANDLER(WM_USER_MODIF, TMessage, ModifyXy2)VCL_MESSAGE_HANDLER(WM_USER_APIHOOK, TMessage, HOOKAPITest)VCL_MESSAGE_HANDLER(WM_USER_GETSEND, TMessage, GetSend)VCL_MESSAGE_HANDLER(WM_USER_GETRECV, TMessage, GetRecv)END_MESSAGE_MAP(TForm)  以上就是拦截接收数据的全部了。结合以前的代码,运行程序,可以发现,拦截到的数据为所有的已经解密了的游戏数据,至于数据的解析,就看自己的了,这里给出一个解析的代码框架: #define CMDVOID(a) \void CMDSAY##a(int cmd, int length, char* date)#define RECVCASE(cmd,length,data) \case cmd:\CMDSAY##cmd(cmd,length,data);\break  class中的声明:CMDVOID(10);CMDVOID(11);CMDVOID(21);  在接收到的函数GetRecv中: void TwgHookForm::GetRecv(TMessage Message){ if (Message.WParam == 1) {  cmdrecvstate = RECVCMD;//如果接收到的是1个字符,则接收到的是命令 } else if (Message.WParam == 2) {  cmdrecvstate = RECVLENGTH;//如果接收到长度是2个字符,则为将要接收到的数据长度 } else {  cmdrecvstate = RECVDATA;//否则接收到的就是数据 } static int cmd,datalength; switch (cmdrecvstate) {  case RECVCMD:   cmd = *((int*)(Message.LParam));   return;  case RECVLENGTH:   datalength = *((int*)(Message.LParam));   return;  case RECVDATA:   if (datalength == Message.WParam)   {    Cmdsay(cmd, datalength, (char*)Message.LParam);//如果数据包没有被拆分,则进行命令解释   }   else   {//否则,打印这个包现在内容    Memo1->Lines->Add("注意:这个数据不完整:");    AnsiString astemp1 = "[接受][";    astemp1.cat_sprintf("%x][", cmd);    astemp1.cat_sprintf("应收长度:%d实际长度:%d][",datalength,Message.WParam);    BYTE *temp = (BYTE*)Message.LParam;    for (int i = 0; i < Message.WParam; i )    {     astemp1.cat_sprintf("%0.2x ", temp[i]);    }    astemp1.cat_sprintf("][");    for (int i = 0; i < Message.WParam; i )    {     if(temp[i]>=0x20)      astemp1.cat_sprintf("%c", temp[i]);     else      astemp1.cat_sprintf(".");    }    astemp1.cat_sprintf("]");    Memo1->Lines->Add(astemp1);   }   return;  }  程序仅仅很简单的进行数据包的拦截和解析,对于被拆分的包不作处理,在真实应用中应该将这些包合并。  上面的接收规则对于9.16之前的大话程序有效,大话程序的一组数据会分成3次发送,首先接收到的是名字字,这个为1字节长度,接着是要接收的数据的长度,这个为2个字节,接下来就是数据了。如果真实接收到的数据和应该接收的数据长度不一样的话,表示这个包被拆分了。共2页。 1 2 : 下载本文示例代码


协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码协议型网络游戏外挂制作之DLL中改代码
阅读(35) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~