首页 » 技术分享 » 国服游戏封包解密-外挂制作全过程

国服游戏封包解密-外挂制作全过程

 

[软件]国服游戏-路尼亚战记


[工具]OD,Wep,以及其它的一些文本工具


[目的]研究游戏保护技术,深论协议级分析。




意在抛砖引玉,抵制游戏外挂。我会在每个分析点做出一些保护上的思考。




开始正文。




一个多月前,有看过一些游戏,DNF,路尼亚战记。他们大概是属于那种靠操作,连招一张地图一张地图那种游戏。DNF是由腾迅公司代理的,自己做了点不强的小保护,但是还是被别人开发出了外挂。居然还有全屏秒杀怪功能。


从朋友那里大概了解了点游戏开发的一些设计思路,就服务器和客户端而言,有强服务器弱客户端,和强客户端,弱服务器。之类的分别。




大概dnf这样的游戏是属于强客户端这样的游戏,所以才有可能开发出全屏秒杀。二者主要的区别在于,把主要运算是放在服务器端,还是放在客户端。




经过我的分析,像路尼亚战记这样的游戏,是属于强服务器的。




前边所有的东西------我就不说了,开始分析。






首先,注册个帐号,创建角色,然后登陆游戏。OD--附加游戏进程。




既然是分析协议,我们就在send处下段。






在开始讲解之前,首先要明确以下一些事。第一,我们明确的知道,我们的发送包是经过加密处理了的。第二,我们要明确,我们要分析的send动作大概是什么,比如说走一步,又比如说要打开一个仓库。




对于第二点,我们采用在send处,下段,然后在很快的反映时间里,给游戏一个动作。然后观察send数据。




这里我选择的是打开武器铺,我们发现其打开武器铺的数据长度是0x0e.






71A24288      90            nop


71A24289      90            nop


71A2428A >    8BFF          mov     edi, edi                         ;  dword ptr [esp+0x0c] == 0x0e


71A2428C   .  55            push    ebp


71A2428D   .  8BEC          mov     ebp, esp


71A2428F   .  83EC 10       sub     esp, 10


71A24292   .  56            push    esi


71A24293   .  57            push    edi


71A24294   .  33FF          xor     edi, edi


71A24296   .  813D 2840A371>cmp     dword ptr [71A34028], 71A29448   ;  入口地址


71A242A0   .  0F84 AD730000 je      71A2B653


71A242A6   >  8D45 F8       lea     eax, dword ptr [ebp-8]


71A242A9   .  50            push    eax






下条件断点。




然后,当我们打开武器铺的时候,程序就会中断在那里。




然后,这时候,我们想知道的是,什么时候,其向send数据包里,那段内存写入了数据,我们才能回烁跟踪。




方法很多,我就不一一说了,就针对这个游戏。谈谈...




我们多次打开武器库,观察发现,其每次发送的数据的内存地址都是一个。我们根据这个地方下硬件访问断点就好了。




-------这里,要谈谈游戏保护技术了。我觉得好点的保护,特别是在send点这里,send的数据内存地址,应该尽力保持活跃,跳动。不能一直固定。好象(分析有段时间了,记忆就忘记了),朱仙这点就做的比较好,在send数据的时候。内存点会变。




但是使用alloc和reallloc等函数,又难免会被别人在这些关键点的地方下断点。作为一个破解分析者,首先会考虑的是以最高效的方法做出分析。不会把所有的游戏代码,反汇编读完。所以一些关键点,应该考虑离散性高,偶合性高。高的偶合会让分析者迷茫,找不到关键点。高的离散,会让分析者解读不出确实的意义。




接下来继续。




007332C0    51              push    ecx


007332C1    8B4424 0C       mov     eax, dword ptr [esp+C]


007332C5    85C0            test    eax, eax


007332C7    55              push    ebp


007332C8    8B6C24 0C       mov     ebp, dword ptr [esp+C]


007332CC    57              push    edi


007332CD    8BF9            mov     edi, ecx


007332CF    74 63           je      short 00733334


007332D1    53              push    ebx


007332D2    894424 18       mov     dword ptr [esp+18], eax


007332D6    56              push    esi


007332D7    EB 07           jmp     short 007332E0


007332D9    8DA424 00000000 lea     esp, dword ptr [esp]


007332E0    8A45 00         mov     al, byte ptr [ebp]


007332E3    884424 18       mov     byte ptr [esp+18], al


007332E7    8B47 04         mov     eax, dword ptr [edi+4]


007332EA    8D48 01         lea     ecx, dword ptr [eax+1]


007332ED    894424 10       mov     dword ptr [esp+10], eax


007332F1    04 04           add     al, 4


007332F3    894F 04         mov     dword ptr [edi+4], ecx


007332F6    8D5424 10       lea     edx, dword ptr [esp+10]


007332FA    8AC8            mov     cl, al


007332FC    BE 03000000     mov     esi, 3


00733301    8A42 01         mov     al, byte ptr [edx+1]


00733304    42              inc     edx


00733305    B3 49           mov     bl, 49


00733307    F6EB            imul    bl


00733309    34 15           xor     al, 15


0073330B    02C8            add     cl, al


0073330D    4E              dec     esi


0073330E  ^ 75 F1           jnz     short 00733301


00733310    0FB64424 18     movzx   eax, byte ptr [esp+18]


00733315    0FB6D1          movzx   edx, cl


00733318    8B4F 08         mov     ecx, dword ptr [edi+8]


0073331B    C1E2 08         shl     edx, 8


0073331E    03D0            add     edx, eax


00733320    8A140A          mov     dl, byte ptr [edx+ecx]


00733323    8B4424 1C       mov     eax, dword ptr [esp+1C]


00733327    8855 00         mov     byte ptr [ebp], dl


0073332A    45              inc     ebp                              ; 这里


0073332B    48              dec     eax


0073332C    894424 1C       mov     dword ptr [esp+1C], eax


00733330  ^ 75 AE           jnz     short 007332E0


00733332    5E              pop     esi


00733333    5B              pop     ebx


00733334    5F              pop     edi


00733335    5D              pop     ebp


00733336    59              pop     ecx


00733337    C2 0800         retn    8






我们在硬件断点的第二次F9条到这里。




一般经过N次的观察之后,我们会发现。这里其实就是封包的加密函数。




这里谈谈经验之谈。通常加密函数,都会和普通函数有所不同。因为从意义上来说,加密函数,主要完成的是数据加密,变换。所以其使用的指令,和其指令的方式会和正常函数有所不同。比如涉及到位操作,byte操作,会比较多。比如md5等,一看就shl什么指令就是一篇篇。




这里我们再谈谈,保护上的一些东西。--我觉得,位的变换,和其它的东西,不能一步写死到一个函数头,不然,解密者,就会像我做的一样。找到加密call,分析加密call参数,然后分析出具体加密函数。然后就可以自己写封包加密了。




这游戏这点做的相当之差。




然后一个ctrl+F9,就来到下边这里。






00733340    56              push    esi


00733341    8B7424 08       mov     esi, dword ptr [esp+8]


00733345    8B06            mov     eax, dword ptr [esi]


00733347    57              push    edi


00733348    8BF9            mov     edi, ecx


0073334A    8BCE            mov     ecx, esi


0073334C    FF50 04         call    dword ptr [eax+4]                ; 取长度


0073334F    8B16            mov     edx, dword ptr [esi]


00733351    50              push    eax


00733352    8BCE            mov     ecx, esi


00733354    FF52 10         call    dword ptr [edx+10]               ; 取包明问


00733357    50              push    eax


00733358    8BCF            mov     ecx, edi


0073335A    E8 61FFFFFF     call    007332C0                         ; 堆栈结构依次为-封包明问-长度-解码表地址


0073335F    8B06            mov     eax, dword ptr [esi]


00733361    8BCE            mov     ecx, esi


00733363    FF50 04         call    dword ptr [eax+4]


00733366    5F              pop     edi


00733367    5E              pop     esi


00733368    C2 0400         retn    4






然后就慢慢分析了哈。




下边贴出,一个月前,写的针对这个游戏的内挂的一些测试代码。各位可以配合到看,方便理解。








#include    "InjectDll.h"




//BYTE  nCmd[0x0e]={0x0E,0x00,0xe0,0x55,0x91,0x10,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00};


unsigned long   MyApi = 0;


unsigned long   hookApi = 0;


DWORD     dwWrite = 0;


BYTE      lpResetSend[0x05]={0x8B,0XFF,0X55,0X8B,0XEC};//用于恢复HookSend的5个字节


HINSTANCE hws2_32 = NULL;//ws2_32句柄


HANDLE    my_sendhandle;//保存用语发送send的句柄






int WINAPI DllMain(HANDLE hinstDll, DWORD fdwReason, LPVOID lpvReserved)


{


   // MessageBox( NULL, "yes", "yes", MB_OK);


    hModule = hinstDll;


    DWORD   dwThread;


   // UiThread( NULL);


   // 




   


    switch(fdwReason)


    {


    case DLL_PROCESS_ATTACH:


        MessageBox( NULL, "Debug", "Debug", MB_OK);


        CreateThread( NULL, 0,(unsigned long (__stdcall *)(void *))UiThread,NULL,0,&dwThread);


       


        break;


    case DLL_THREAD_ATTACH:


        break;


    case DLL_THREAD_DETACH:


        break;


    case DLL_PROCESS_DETACH:


        break;


    }


   


    return TRUE;


}




DWORD    WINAPI   UiThread(LPARAM lParam)


{


    MSG msg;


    HWND hWnd;


    hWnd = CreateDialog( (HINSTANCE)hModule, MAKEINTRESOURCE(IDD_MAIN_PAGE), NULL, MainProc);


    ShowWindow( hWnd, SW_SHOW);


    UpdateWindow( hWnd);


    while(GetMessage(&msg,NULL,0,0))


    {


     TranslateMessage(&msg);


     DispatchMessage(&msg);


    }


    return 0;


}




int CALLBACK MainProc( HWND hWnd, UINT uMsg, WPARAM  wParam, LPARAM lParam)


{


    


    BYTE  nCmd[RUN_RIGHT_LEN]={RUN_RIGHT};


    HINSTANCE hws2_32;


    static  DWORD dwCount[2] = {0x58548565,0x00c45878};


    static  HANDLE hFile;


    BYTE    lpBuffer[0x2];


    char    szFormat[7];


    static  DWORD dwWrite = 7;


    RtlZeroMemory( lpBuffer,0x2 );


    DWORD dwRead;


    DWORD lpRead=0;


    int     nindex=0;


    switch( uMsg)


    {


    case WM_COMMAND:


        switch(wParam)


        {


        case IDC_BTN_TEST:


           


          //  MessageBox( NULL,"debug","debug",MB_OK);


           GoRight();


      //Speck_Something( "yangzhihao");


        


        


        /*


                 封包加密call




                 组织然后send


           */


            


             


         //   MessageBox( NULL, "call","call", MB_OK);


            


            /*__asm{


                    pushad


                    push 0x0e


                    lea  eax, nCmd


                    push eax


                    mov  eax,0x23ba078


                    mov  ecx,eax


                    mov  ebx,0x729a00


                    mov  eax,0x0e


                    call ebx


                    popad


                }


         


            hws2_32 = LoadLibrary( "ws2_32.dll");


            (unsigned long)::GetProcAddress( hws2_32, "send");


            __asm


            {


                


                push 0x00


                push 0x0e


                lea  ebx, nCmd


                push ebx


                mov  ebx,my_sendhandle


                push ebx


                call eax


            }


            */


            


            break;


             case    IDC_READ_TABLE:


             MessageBox( NULL, "write", "write", MB_OK);


             for( nindex;nindex<0xa7a9;nindex++)


              {


                


                    lpRead = 0x00c45878+nindex;


                    ReadProcessMemory( GetCurrentProcess(), (LPVOID)lpRead,lpBuffer, 0x01, &dwRead);


                    sprintf( szFormat,"0x%2x",lpBuffer[0]);


                    WriteFile( hFile, szFormat, dwWrite, &dwRead, 0);




              }


                


            break;


             case IDC_BTN_HOOK:


                  hws2_32 = LoadLibrary( "ws2_32.dll");


                  hookApi = (unsigned long)::GetProcAddress( hws2_32, "send");


                  MyApi = (unsigned long )GetSendPara;


                  _HOOK_APIN( MyApi, hookApi);




                 break;


        default:


            break;


        }


        break;


    


    case WM_INITDIALOG:


        hFile = CreateFile( "c:\\MYDebugLog.txt",GENERIC_READ | GENERIC_WRITE ,FILE_SHARE_READ|FILE_SHARE_WRITE,


            NULL,OPEN_ALWAYS ,FILE_ATTRIBUTE_NORMAL,0);


        break;


    case WM_CLOSE:


        EndDialog( hWnd, 0);


        break;


    default:


        DefWindowProc( hWnd, uMsg, wParam, lParam);


    }


    return 0;


}












/*取send句柄*/


void    GetSendPara(void)


{




     __asm


     {


        /*首先要堆栈平衡下 因为VC6前边会有压栈操作*/


        pop eax


        pop eax


        pop eax


        pop eax


       


        /*执行判断操作,取send的句柄*/


        mov eax, dword ptr [esp + 0x0c]


        cmp eax,0x0e


        jnz JMP_HOOM


        mov eax,dword ptr[esp+0x04]


        mov my_sendhandle,eax


     }


     WriteProcessMemory( GetCurrentProcess(), (void*)hookApi, lpResetSend, 0x05, &dwWrite);


     dwWrite = 0;


     __asm


     {


        


JMP_HOOM:


        /*跳会原来的地方*/


        sub ebp,0x04


        mov eax,hookApi


        mov edi,edi


        push ebp


        mov  ebp,esp


        add eax,5


        jmp eax


 


     }


     return;


}








void    Encode( BYTE* pCmd, int nLen)


{


       __asm{


               pushad


               mov  eax,dword ptr[esp+0x34]//长度


               push eax


               mov  eax, dword ptr[esp+0x34]//命令明文序列


               push eax


               mov  eax,0x2fd078 // 硬编码---编码表--和计数表


               mov  ecx,eax


               mov  ebx,0x729a00//加密call


               mov  eax,dword ptr[esp+0x08]//长度


               call ebx


               popad


             }


       return;


}






void    GoRight()


{


    BYTE lpCmd[RUN_RIGHT_LEN] = {RUN_RIGHT};


    Encode( lpCmd,RUN_RIGHT_LEN);


    


    /*发送封包*/


    hws2_32 = LoadLibrary( "ws2_32.dll");


    (unsigned long)::GetProcAddress( hws2_32, "send");


    __asm


     {


                


           push 0x00


           push RUN_RIGHT_LEN


           lea  ebx, lpCmd


           push ebx


           mov  ebx,my_sendhandle


           push ebx


           call eax


       }


}




void    Speck_Something(char* pSpeckBuffer)


{


    //MessageBox( NULL, "speck","speck",MB_OK);


    BYTE  packLen;


  int count = 0;


  WCHAR  wszSpec[MAX_PATH];


  RtlZeroMemory( wszSpec,MAX_PATH*2);




    count = strlen( pSpeckBuffer);




  long nwLong = MultiByteToWideChar( CP_ACP, 0, pSpeckBuffer, strlen(pSpeckBuffer),


                      wszSpec,sizeof(wszSpec));




    BYTE    temp[0x08]={SPECK_ONE};


    BYTE    *lpCmd;


    lpCmd = new BYTE [200] ; //申请一块封包的内存




  RtlZeroMemory( lpCmd, 200);


    


    memcpy( lpCmd,temp,0x08);    //com封包命令


    memcpy( lpCmd,(void*)&count,0x01);


  


  packLen = (BYTE)0x0c+nwLong*2+2;  //包头 封包整个长度


  lpCmd[0] = packLen;


  lpCmd [0x0A] = (BYTE)nwLong+1;//包的11个字节 字符串长度




  memcpy( (lpCmd+12), wszSpec, nwLong*2+1);//把字符放入消息




  Encode( lpCmd, (int)lpCmd[0]);






  hws2_32 = LoadLibrary( "ws2_32.dll");


    (unsigned long)::GetProcAddress( hws2_32, "send");


    __asm


     {


                


        push 0x00


        push packLen


        lea  ebx, lpCmd


        push ebx


        mov  ebx,my_sendhandle


        push ebx


        call eax


     }




   // delete [] lpCmd;


    


}




由于是测试代码,写的相当潦草。这是个dll代码,这个代码是注如到游戏进程的。有hook api操作。




在操作这前,先抓出明文封包动作




#ifndef     __COMMON_H_


#define     __COMMON_H_




#include    <windows.h>






BOOL _HookApi( unsigned long _My_Addr, unsigned long _Hook_Addr);






/*command数据*/


/*向右走*/


#define RUN_RIGHT_LEN   0x0e


#define RUN_RIGHT   0x0e,0x00,0xe0,0x55,0x8d,0xe2,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00




/*喊话*/


//喊话封包的长度不固定-首部为封包长度-然后8个字节的命令.接着4个字节的字符长度.跟到字符串


#define SPECK_ONE   0x00,0x00,0xe0,0x55,0xb9,0x6f,0x00,0x00










#endif












然后就xxxx..... 

转载自原文链接, 如需删除请联系管理员。

原文链接:国服游戏封包解密-外挂制作全过程,转载请注明来源!

0