网络游戏协议封包需注意点
发表于2016-09-01
对于强客户端的游戏,就有很大可能出现脱机挂,因为一切客户端的计算、处理逻辑都不需要进行,只需要将最理想的作弊数据包直接发给服务器,就能达到游戏收益最大化。
下面这几点是需要多加注意的点。
1、send函数
根据send调用的buffer,进行回溯,找到加密函数。对于send调用的buffer,最好是保持随机调用,如果固定的话会非常容易分析。
2、耦合、离散
软件工程中提倡低耦合,高内聚。但是就耦合这点在封包发送阶段,应该做到高耦合,这样可以让逆向分析陷入迷茫,找不到关键点。对于包结构或者组包过程要做到高离散,这样会让逆向分析无法理解其确实的意义。
3、加密函数
加密函数主要完成数据加密,变换。使用的指令也会跟正常函数有所不同,比如位操作,byte操作。对于加密函数,最好不要一步完成,不然很容易分析出加密参数,每次将构造的协议交由加密函数处理,做出封包外挂。
下面用三个游戏来说明
一、B游戏
1、首先对send函数下断点
2、可以看到发送的加密数据(必然加密)
3、向上回溯,寻找加密函数
4、逆向解密函数,或者黑盒调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | [cpp] view plain copy print? sub esp, 8 mov eax, dword ptr [esp+C] push ebx push ebp mov edx, ecx mov ecx, dword ptr [eax] push esi mov esi, dword ptr [edx+18] mov edx, dword ptr [edx+C] add eax, 4 mov ebx, dword ptr [eax+4] push edi mov edi, dword ptr [esi] mov ebp, eax add edi, dword ptr [ebp] mov ebp, dword ptr [eax+8] add ebp, dword ptr [esi+4] add eax, 4 add esi, 8 test edx, edx mov dword ptr [esp+1C], ebx jbe short 06FE50A7 mov dword ptr [esp+10], edx lea ebx, dword ptr [ebx] lea edx, dword ptr [edi+edi+1] imul edx, edi lea eax, dword ptr [ebp+ebp+1] imul eax, ebp rol edx, 5 rol eax, 5 mov ebx, edx xor ebx, ecx mov cl, al xor eax, dword ptr [esp+1C] rol ebx, cl mov cl, dl rol eax, cl mov dword ptr [esp+1C], ebp mov ebp, dword ptr [esi] mov dword ptr [esp+14], edi add eax, dword ptr [esi+4] mov ecx, dword ptr [esp+14] add ebp, ebx add esi, 8 sub dword ptr [esp+10], 1 mov edi, eax jnz short 06FE5060 mov ebx, dword ptr [esp+1C] add ebx, dword ptr [esi+4] mov esi, dword ptr [esi] mov eax, dword ptr [esp+20] add esi, ecx test eax, eax je short 06FE50BA mov ecx, dword ptr [eax] jmp short 06FE50BC xor ecx, ecx mov edx, dword ptr [esp+24] xor ecx, esi test eax, eax mov dword ptr [edx], ecx lea ecx, dword ptr [edx+4] je short 06FE50D4 add eax, 4 je short 06FE50D4 mov edx, dword ptr [eax] jmp short 06FE50D6 xor edx, edx xor edx, edi mov dword ptr [ecx], edx add ecx, 4 test eax, eax je short 06FE50EA add eax, 4 je short 06FE50EA mov edx, dword ptr [eax] jmp short 06FE50EC xor edx, edx xor edx, ebx mov dword ptr [ecx], edx add ecx, 4 test eax, eax je short 06FE510C add eax, 4 je short 06FE510C mov eax, dword ptr [eax] pop edi pop esi xor eax, ebp pop ebp mov dword ptr [ecx], eax pop ebx add esp, 8 retn 0C |
F5之后,还是直接黑盒调用比较方便。
B游戏的加密比较简单,可以直接构造数据包,然后基于加密算法自己加密后发送至服务器。
二、W游戏
分析W游戏的TCP部分,可以发现相较B游戏的数据包,数据包结构要严格并且复杂一些。
但是还是存在一个固定的点可以进行HOOK,存在风险。
三、N游戏实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | [cpp] view plain copy print? #ifndef __COMMON_H_ #define __COMMON_H_ #include 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 #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 { /*首先对堆栈进行平衡*/ 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; }
|