国服游戏封包解密-辅助制作全过程
[软件]国服游戏-路尼亚战记[工具]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]