zhenyu 发表于 2013-4-30 08:28:47

国服游戏封包解密-辅助制作全过程

[软件]国服游戏-路尼亚战记
[工具]OD,Wep,以及其它的一些文本工具
[目的]研究游戏保护技术,深论协议级分析。

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

开始正文。

一个多月前,有看过一些游戏,DNF,路尼亚战记。他们大概是属于那种靠操作,连招一张地图一张地图那种游戏。DNF是由腾迅公司代理的,自己做了点不强的小保护,但是还是被别人开发出了智辅。居然还有全屏秒杀怪功能。
从朋友那里大概了解了点游戏开发的一些设计思路,就服务器和客户端而言,有强服务器弱客户端,和强客户端,弱服务器。之类的分别。

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

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

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


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

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


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

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

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


71A24288      90            nop
71A24289      90            nop
71A2428A >    8BFF          mov   edi, edi                         ;dword ptr == 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 , 71A29448   ;入口地址
71A242A0   .0F84 AD730000 je      71A2B653
71A242A6   >8D45 F8       lea   eax, dword ptr
71A242A9   .50            push    eax


下条件断点。

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

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

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

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

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

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

接下来继续。

007332C0    51            push    ecx
007332C1    8B4424 0C       mov   eax, dword ptr
007332C5    85C0            test    eax, eax
007332C7    55            push    ebp
007332C8    8B6C24 0C       mov   ebp, dword ptr
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 , eax
007332D6    56            push    esi
007332D7    EB 07         jmp   short 007332E0
007332D9    8DA424 00000000 lea   esp, dword ptr
007332E0    8A45 00         mov   al, byte ptr
007332E3    884424 18       mov   byte ptr , al
007332E7    8B47 04         mov   eax, dword ptr
007332EA    8D48 01         lea   ecx, dword ptr
007332ED    894424 10       mov   dword ptr , eax
007332F1    04 04         add   al, 4
007332F3    894F 04         mov   dword ptr , ecx
007332F6    8D5424 10       lea   edx, dword ptr
007332FA    8AC8            mov   cl, al
007332FC    BE 03000000   mov   esi, 3
00733301    8A42 01         mov   al, byte ptr
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
00733315    0FB6D1          movzx   edx, cl
00733318    8B4F 08         mov   ecx, dword ptr
0073331B    C1E2 08         shl   edx, 8
0073331E    03D0            add   edx, eax
00733320    8A140A          mov   dl, byte ptr
00733323    8B4424 1C       mov   eax, dword ptr
00733327    8855 00         mov   byte ptr , dl
0073332A    45            inc   ebp                              ; 这里
0073332B    48            dec   eax
0073332C    894424 1C       mov   dword ptr , 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
00733345    8B06            mov   eax, dword ptr
00733347    57            push    edi
00733348    8BF9            mov   edi, ecx
0073334A    8BCE            mov   ecx, esi
0073334C    FF50 04         call    dword ptr                 ; 取长度
0073334F    8B16            mov   edx, dword ptr
00733351    50            push    eax
00733352    8BCE            mov   ecx, esi
00733354    FF52 10         call    dword ptr                ; 取包明问
00733357    50            push    eax
00733358    8BCF            mov   ecx, edi
0073335A    E8 61FFFFFF   call    007332C0                         ; 堆栈结构依次为-封包明问-长度-解码表地址
0073335F    8B06            mov   eax, dword ptr
00733361    8BCE            mov   ecx, esi
00733363    FF50 04         call    dword ptr
00733366    5F            pop   edi
00733367    5E            pop   esi
00733368    C2 0400         retn    4


然后就慢慢分析了哈。

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



#include    "InjectDll.h"

//BYTEnCmd={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={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, WPARAMwParam, LPARAM lParam)
{
   
    BYTEnCmd={RUN_RIGHT};
    HINSTANCE hws2_32;
    staticDWORD dwCount = {0x58548565,0x00c45878};
    staticHANDLE hFile;
    BYTE    lpBuffer;
    char    szFormat;
    staticDWORD 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
                  leaeax, nCmd
                  push eax
                  moveax,0x23ba078
                  movecx,eax
                  movebx,0x729a00
                  moveax,0x0e
                  call ebx
                  popad
                }
         
            hws2_32 = LoadLibrary( "ws2_32.dll");
            (unsigned long)::GetProcAddress( hws2_32, "send");
            __asm
            {
               
                push 0x00
                push 0x0e
                leaebx, nCmd
                push ebx
                movebx,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);
                  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
      cmp eax,0x0e
      jnz JMP_HOOM
      mov eax,dword ptr
      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
      movebp,esp
      add eax,5
      jmp eax

   }
   return;
}



void    Encode( BYTE* pCmd, int nLen)
{
       __asm{
               pushad
               moveax,dword ptr//长度
               push eax
               moveax, dword ptr//命令明文序列
               push eax
               moveax,0x2fd078 // 硬编码---编码表--和计数表
               movecx,eax
               movebx,0x729a00//加密call
               moveax,dword ptr//长度
               call ebx
               popad
             }
       return;
}


void    GoRight()
{
    BYTE lpCmd = {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
         leaebx, lpCmd
         push ebx
         movebx,my_sendhandle
         push ebx
         call eax
       }
}

void    Speck_Something(char* pSpeckBuffer)
{
    //MessageBox( NULL, "speck","speck",MB_OK);
    BYTEpackLen;
int count = 0;
WCHARwszSpec;
RtlZeroMemory( wszSpec,MAX_PATH*2);

    count = strlen( pSpeckBuffer);

long nwLong = MultiByteToWideChar( CP_ACP, 0, pSpeckBuffer, strlen(pSpeckBuffer),
                      wszSpec,sizeof(wszSpec));

    BYTE    temp={SPECK_ONE};
    BYTE    *lpCmd;
    lpCmd = new BYTE ; //申请一块封包的内存

RtlZeroMemory( lpCmd, 200);
   
    memcpy( lpCmd,temp,0x08);    //com封包命令
    memcpy( lpCmd,(void*)&count,0x01);

packLen = (BYTE)0x0c+nwLong*2+2;//包头 封包整个长度
lpCmd = packLen;
lpCmd = (BYTE)nwLong+1;//包的11个字节 字符串长度

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

Encode( lpCmd, (int)lpCmd);


hws2_32 = LoadLibrary( "ws2_32.dll");
    (unsigned long)::GetProcAddress( hws2_32, "send");
    __asm
   {
               
      push 0x00
      push packLen
      leaebx, lpCmd
      push ebx
      movebx,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.....
页: [1]
查看完整版本: 国服游戏封包解密-辅助制作全过程