先自我介绍一下,本人菜鸟一个。由于工作主要基于unix/linux平台,所以没有太多windows
编程经验及编程思想。而且。。还是做unix/linux应用层开发。。5555555,汇编丢给老师太
久了。

而本人又热衷游戏。所以看了不少文章和帖子后想提高下自己技能~~
很菜很菜的帖子,各位看官表笑。如果有可能,希望被加精~

开始吧

这是一个老游戏了,在中国开了没多久。。运营商倒闭。。
最近发现欧服可以免费玩了,于是开始重温,代号game1。

首先,拿报文开刀。截取一次正确登录的报文,如下

2009-02-03 19:57:57,310 INFO CLT->SRV length=100:
0x00 0x00 0x00 0x60 0xc2 0xd2 0xa9 0x98 == 0xfa 0x52 0xac 0xca 0x02 0x39 0x27 0x2b 
0x5d 0x64 0x35 0x98 0x2a 0xfe 0x68 0x0a == 0x80 0xb1 0xd4 0x4c 0xcf 0xdc 0xbc 0xb0 
0xb8 0xda 0xea 0xe5 0xa0 0xbc 0x13 0xc3 == 0xf7 0x1e 0x75 0xc2 0x8c 0xdd 0x8d 0xac 
0xe5 0xf6 0x06 0xeb 0xb5 0x2f 0x99 0xa0 == 0x2d 0x67 0xfe 0x42 0xda 0x59 0x23 0xe4 
0xd7 0x24 0x37 0x13 0xfc 0xa2 0xe8 0x2e == 0xed 0x6d 0xae 0x95 0x15 0x79 0xc5 0x18 
0x93 0x56 0xb6 0x73 0x51 0xc1 0x54 0x29 == 0xc3 0x27 0x4d 0x7c 0x54 0xb6 0x95 0x44 
0x9e 0xbb 0xd4 0x67 

2009-02-03 19:57:57,733 INFO SRV->CLT length=100:
0x00 0x00 0x00 0x60 0x3d 0x3a 0xf4 0x0f == 0x39 0x5d 0x1d 0x99 0x36 0x82 0xdc 0xeb 
0x5e 0x1a 0xd4 0x00 0xa7 0x16 0xaf 0x42 == 0x26 0x0e 0x90 0x43 0xfb 0x65 0x1d 0x56 
0x0d 0x17 0x31 0xff 0xc5 0x5f 0xff 0x91 == 0xe4 0x0c 0xb0 0x62 0xb8 0x04 0x92 0x16 
0xbf 0x20 0x38 0x4a 0xfa 0xaf 0xee 0x3b == 0xd0 0xc1 0x80 0xd1 0xdb 0xa7 0xab 0x2f 
0x95 0x57 0x60 0x30 0xdb 0x96 0x66 0xef == 0x75 0x9a 0x6f 0xf5 0x85 0xe8 0xa4 0x4b 
0x2c 0xd5 0x59 0xe2 0x5c 0x68 0x3f 0xaf == 0xaf 0x63 0xc0 0xdd 0x11 0x63 0x37 0x2a 
0x16 0xd7 0x21 0xa6 

2009-02-03 19:57:57,756 INFO CLT->SRV length=40:
0x5d 0x61 0xf7 0xda 0x84 0x5a 0x69 0x61 == 0x3b 0x80 0x45 0xfb 0x14 0x30 0x7e 0x3c 
0x7a 0x9c 0xc2 0x27 0xe0 0xb7 0xad 0xef == 0x5e 0x12 0x5b 0xd3 0xa2 0xe7 0x9f 0x4d 
0xff 0x75 0x24 0x6b 0x4f 0xf9 0xe6 0xd2 == 

2009-02-03 19:57:58,219 INFO SRV->CLT length=40:
0x5d 0x61 0xf7 0xda 0x84 0x5a 0x69 0x61 == 0x3b 0x80 0x45 0xfb 0x14 0x30 0x7e 0x3c 
0x7a 0x9c 0xc2 0x27 0xe0 0xb7 0xad 0xef == 0x5e 0x12 0x5b 0xd3 0xa2 0xe7 0x9f 0x4d 
0xff 0x75 0x24 0x6b 0x4f 0xf9 0xe6 0xd2 == 

2009-02-03 19:57:58,239 INFO CLT->SRV length=84:
0x77 0xa6 0xcd 0xd6 0x29 0x48 0xa8 0xce == 0xe2 0xe1 0x9d 0xfe 0x49 0x03 0x4b 0xf8 
0x50 0x13 0x49 0xaf 0xa2 0x94 0x68 0xe0 == 0x2a 0xda 0x53 0x7d 0x8d 0xf6 0x09 0xa8 
0x7e 0x84 0xd7 0xbe 0x4d 0x72 0xa4 0x7b == 0x51 0x82 0x4f 0x0e 0x86 0xaf 0x64 0xf3 
0x6d 0x21 0x14 0x05 0xb9 0x58 0xdf 0xf8 == 0x0e 0xd7 0x9c 0x7d 0xb4 0x57 0x89 0xcd 
0xde 0xc8 0x36 0x57 0x58 0xdb 0xb2 0x0b == 0x58 0x17 0xb4 0x43 0xe8 0x96 0xaf 0xcc 
0xec 0x81 0xd0 0x95 

2009-02-03 19:57:58,978 INFO SRV->CLT length=4096:
0x77 0xa6 0xcd 0xd6 0x29 0x48 0xa8 0xce == 0xe2 0xe1 0x9d 0xfe 0x49 0x03 0x4b 0xf8 
0x50 0x13 0x49 0xaf 0xa2 0x94 0x68 0xe0 == 0x2a 0xda 0x53 0x7d 0x8d 0xf6 0x09 0xa8 
0x7e 0x84 0xd7 0xbe 0x4d 0x72 0xa4 0x7b == 0x51 0x82 0x4f 0x0e 0x86 0xaf 0x64 0xf3 
0x6d 0x21 0x14 0x05 0xb9 0x58 0xdf 0xf8 == 0x0e 0xd7 0x9c 0x7d 0xb4 0x57 0x89 0xcd 
0xde 0xc8 0x36 0x57 0x58 0xdb 0xb2 0x74 == 0xd4 0x85 0x28 0xf7 0xaf 0x3b 0x58 0x93 
0xc1 0x44 0x1b 0xcc 0x6e 0xc5 0x9a 0x24 == 0xa7 0x1f 0x96 0x05 0x2a 0x99 0x7e 0xc3 
0x86 0x7c 0xdf 0x75 0x6d 0x0e 0x3f 0xfe == 0x70 0xa7 0xc4 0xc2 0x40 0xd7 0x51 0x3e 
......
......

嗯,先拿报文(主要是游戏中的报文,不是登录报文)端详了半天,看不出什么共性,初步判断
报文加密了(题外话:对于未加密的报文,一般都有规律可循,例如选怪报文,头几位一般都有
相同的opcode,可能就是后面的值不同代表不同的怪),那么就是说先用用WPE的可能性为0了(貌似现在报文不加密就是外挂多的代名词)

另外进行了小小的报文猜测,开始clt->srv和srv->clt 100 字节应该是交换密钥
接下来的 40 字节可能是客户端用自己密钥加密一段数据后上送服务器,服务器解密后再用服务器
密钥加密返回客户端。 客户端通过再解密确定整个通讯过程无问题。 如果不能解密出和原始发送
报文一致的数据则需要重新互换密钥
再往下84字节应该是密码口令等认证信息加密上送
再往后数据应该就是登录成功后的角色信息了。
(以上纯属猜想)

嗯,本小鸟也祭出OD,打开,运行,经过按了n次shift-F9后,登录界面来到眼前。
依葫芦画瓢,来个鼠标点击断点,详情请看:
http://bbs.pediy.com/showthread.php?s=&threadid=21532

可结果,每次在WM_LBUTTONUP 下断都提示:
”无法读取调试进程的内存,位于FFFF09CF的断点已被删除“

日日,一下子傻眼了。
这怎么下断呢。
经过n久摸索(谁让我菜呢),终于会断send了,(汗。真够菜的)
http://bbs.pediy.com/showthread.php?s=&threadid=21330

往上一层,来到send前一层。个中注释是小菜当时的想法。。   

call    <jmp.&MSVCRT.operator new>
mov     dword ptr [esi+18], eax          ;  new出一块空间用于send
push    eax                              ;  esi+14存放长度60H,[esi+18]存放要发送数据
mov     eax, dword ptr [esi+4]
mov     ecx, dword ptr [eax+14]
push    ecx
call    <getfirstkey>
mov     edx, dword ptr [esi+14]
add     esp, 10
push    edx                              ; /NetLong
call    <jmp.&WSOCK32.#8_htonl>          ; \ntohl
mov     ecx, dword ptr [ebp+8]
lea     edi, dword ptr [esi+10]
push    eax
mov     byte ptr [ebp-4], 0B
lea     ebx, dword ptr [ecx+4]
mov     dword ptr [edi], eax
mov     ecx, ebx
mov     edx, dword ptr [ebx]
call    dword ptr [edx+18]               ;  send:开头4字节
mov     ecx, dword ptr [esi+14]
mov     edx, dword ptr [esi+18]
mov     eax, dword ptr [ebx]
push    ecx
push    edx
mov     ecx, ebx
call    dword ptr [eax+30]               ;  send:4H字节后的60H字节的client key
mov     eax, dword ptr [ebx]
mov     ecx, ebx
call    dword ptr [eax+4]
mov     ebx, dword ptr [ebp+8]
push    edi
mov     ecx, ebx
mov     edx, dword ptr [ebx]
call    dword ptr [edx+14]               ;  recieve 4字节。。
mov     eax, dword ptr [edi]
mov     dword ptr [ebp-4], 0
push    eax                              ; /NetLong
call    <jmp.&WSOCK32.#14_ntohl>         ; \ntohl
cmp     eax, 400
mov     dword ptr [edi], eax
jle     short 00517F5B
mov     ecx, dword ptr [esi+18]
push    ecx
call    <FREE>
add     esp, 4
lea     ecx, dword ptr [ebp-58]
push    0
push    016CE1D8                         ;  ASCII "ArcDHKeyExchange::GenerateSharedKey: key size too long."
call    00420A40
lea     edx, dword ptr [ebp-58]
push    015C5980
push    edx
mov     dword ptr [ebp-58], 01541880
call    <jmp.&MSVCRT._CxxThrowException>
cmp     eax, dword ptr [esi+14]
jle     short 00517F77
mov     eax, dword ptr [esi+18]
push    eax
call    <FREE>
mov     eax, dword ptr [edi]
push    eax
call    <jmp.&MSVCRT.operator new>
add     esp, 8
mov     dword ptr [esi+18], eax
mov     eax, dword ptr [edi]
mov     ecx, dword ptr [esi+18]
mov     edx, dword ptr [ebx]
push    eax
push    ecx                              ;  [esi+18]保存recieve的数据
mov     ecx, ebx
mov     byte ptr [ebp-4], 0D
call    dword ptr [edx+2C]               ;  recieve 后面 60H的字节
mov     dword ptr [ebp-4], 0
call    00CD97F0                         ;  貌似加密函数
mov     edx, dword ptr [edi]
mov     ebx, eax
mov     eax, dword ptr [esi+18]
push    ebx
push    edx
push    eax
call    <serverkey处理>                    ;  都是算法。。。
add     esp, 0C
test    eax, eax
jnz     short 00517FDB
mov     ecx, dword ptr [esi+18]
push    ecx
call    <FREE>
add     esp, 4
lea     ecx, dword ptr [ebp-40]
push    0
push    016CE194                         ;  ASCII "ArcDHKeyExchange::GenerateSharedKey: failed to get key."
call    00420A40
lea     edx, dword ptr [ebp-40]
push    015C5980
push    edx
mov     dword ptr [ebp-40], 01541880
call    <jmp.&MSVCRT._CxxThrowException>
mov     eax, dword ptr [esi+4]
push    eax
call    00CDA980                         ;  不知道干嘛
mov     ecx, dword ptr [ebp+10]
push    eax
mov     dword ptr [ecx], eax
call    <jmp.&MSVCRT.operator new>
mov     edx, dword ptr [ebp+C]
mov     dword ptr [edx], eax
mov     ecx, dword ptr [esi+4]
push    ecx
push    ebx
push    eax
call    00CDA9C0                         ;  又是加密算法,还调用到了getfirstkey
mov     esi, dword ptr [esi+18]
mov     edi, eax
push    esi
call    <FREE>                           ;  Free函数
push    ebx
call    00CD9770                         ;  不知道是啥 莫非是计算key?
add     esp, 1C
cmp     edi, -1
jnz     short 0051804A
mov     edx, dword ptr [ebp+C]
mov     eax, dword ptr [edx]
push    eax
call    <FREE>
add     esp, 4
lea     ecx, dword ptr [ebp-4C]
push    0
push    016CE14C                         ;  ASCII "ArcDHKeyExchange::GenerateSharedKey: error computing key."
call    00420A40
lea     ecx, dword ptr [ebp-4C]
push    015C5980
push    ecx
mov     dword ptr [ebp-4C], 01541880
call    <jmp.&MSVCRT._CxxThrowException>
mov     ecx, dword ptr [ebp-18]
mov     dword ptr [ebp-4], -1
test    ecx, ecx
je      short 0051805D
call    004149C5
mov     ecx, dword ptr [ebp-C]
pop     edi
pop     esi
mov     dword ptr fs:[0], ecx

按下逐个call F7进去看不表,这是体力活,没啥可讲的,关键是耐心和毅力。。
终于功夫不负有心人呀。找到了这样一个函数入口

push    ebp                              ;  encode函数
mov     ebp, esp
mov     eax, dword ptr [ebp+10]          ;  形参4 ebp+10 待encode长度
test    eax, eax
jbe     short 005115DF
lea     edx, dword ptr [ecx+1068]
push    1                                ;  可能是参数1代表encode,0代表decode
push    edx                              ;  ecx+1068 压栈
lea     edx, dword ptr [ecx+1058]
add     ecx, 0C
push    edx                              ;  ecx+1058压栈
push    ecx                              ;  ecx+0C 压栈
mov     ecx, dword ptr [ebp+8]           ;  形参2 [ebp+8] 待encdoe buff
push    eax                              ;  encode字符串长度
mov     eax, dword ptr [ebp+C]           ;  形参3 [ebp+C] encode后结果
push    eax                              ;  encode后结果指针
push    ecx                              ;  encode字符串指针
call    <encode/decode函数>
add     esp, 1C                          ;  未知参数来源于外层ecx
pop     ebp
retn    0C

嘿嘿嘿,未encode前的buff展露眼前~
好了,菜鸟哥现在总算到达了类似爆破找到关键call时的快感~
一切数据尽在眼前。
某猥琐男在后面提醒。。那现在不是可以。。。username->save() password->save()了。
,本菜鸟不干这一行,只想了解下本游戏协议报文。。

接下来又傻眼了。要知道,本菜鸟此时对什么CreateRemoteThread, DEBUG API 完全没有概念。
就想把未encode报文先log下来分析。。怎么办呢?还能怎么办。论坛问,google搜,自学呗

又经过天折腾。有基本思路了
使用DEBUG API,在call encode/decode函数前下断。运行到encode/decode函数时 保存待发送数据和其长度,
写入log,然后eip--,恢复原始eip内容。
类似如下代码

  if(DbgEv.u.Exception.ExceptionRecord.ExceptionAddress == (PVOID)encode_base)
  {
    dwThreadPid=DbgEv.dwThreadId;                                                                
    DebugHandle=OpenThread(THREAD_ALL_ACCESS,FALSE,dwThreadPid);                                 
                                                                                                 
    thContext.ContextFlags = CONTEXT_FULL;                                                       
    GetThreadContext(DebugHandle,&thContext);                                                    
    thContext.Eip--;                                                                             
                                                                                                 
    memaddr=thContext.Ebp+0x10;     //[ebp+c]result buff                                         
                                    //ebp+10 origin len                                            
                                    //[ebp+8]origin buff                                           
    ReadProcessMemory(DbgHandle,(LPVOID)(memaddr),&addr,4,NULL);                                 
    len=(int)addr;                                                                               
                                                                                                 
    memaddr=thContext.Ebp+0x08;     //[ebp+8]origin len                                          
    ReadProcessMemory(DbgHandle,(LPVOID)(memaddr),&addr,4,NULL);                                 
    memset(Buf,0x00,sizeof(Buf));                                                                
    memset(Hex,0x00,sizeof(Hex));                                                                
    ReadProcessMemory(DbgHandle,(LPVOID)(addr),Buf,len,NULL);                                    
                                                                                                 
    BinToHexFormat(Buf,Hex,len);                                                                 
                                                                                                 
    sprintf(loghead,"\nSrv->Clt: %d ",len);                                                      
    logger->Add( loghead );                                                                      
    logger->Add(  Hex );                                                                         
                                                                                                 
    WriteProcessMemory(DbgHandle,(LPVOID)decode_base,decode_temp1,1,&dwWritten);  // 恢复原有指令
                                                                                                 
    WriteProcessMemory(DbgHandle,(LPVOID)decode_after,int3,1,&dwWritten);                        
    WriteProcessMemory(DbgHandle,(LPVOID)encode_base,int3,1,&dwWritten);                         
    WriteProcessMemory(DbgHandle,(LPVOID)encode_after,int3,1,&dwWritten);      
  }  
(由于本人依旧很菜的原因,SINGLE_STEP总不成功,只好用此土法,两个断点,互相写int3)

各位应该已经开始发笑了。。这人代码风格怎么这么不统一呀。。。嗯。。。。其实。。俺是C程序员。。。
通过这么一处理,报文都存下来了。现在可以开始分析报文了。

通过在游戏里做一些简单的选怪,以及log信息,可以判断出当clt选怪时候,会发如下报文

0x64 0xe1 0x09 0x38 0x0c 0x80 0x00 0x00 == 0x00 0xee 0x93 0xe4            
0x64 0xe1 0x09 0x38 0x0c 0x80 0x00 0x00 == 0x00 0xac 0x55 0xd2 
0x64 0xe1 0x09 0x38 0x0c 0x80 0x00 0x00 == 0x00 0xbb 0xa7 0xe4  
0x64 0xe1 0x09 0x38 0x0c 0x80 0x00 0x00 == 0x00 0xe1 0x91 0x78 
0x64 0xe1 0x09 0x38 0x0c 0x80 0x00 0x00 == 0x00 0x32 0x40 0xa3 

前面8字节是一致的,再次开始猜测
0x64 0xe1 0x09 0x38 是opcode,代表是选择npc
0x0c 0x80 0x00 0x00 是类型,怪,npc,player值分别不同
0x00 0xee 0x93 0xe4 .... 是id,应该是整个game世界唯一id号之类。

忘了说一句,前面未获得原始报文时候对于处理clt<-->srv 40字节的猜测是错误的,其实是客户端版本号信息上传。。

现在,我此行的最主要目标来了。
咳咳。。外置选上一个怪的函数,,你不是想做外挂吧。非也,就是方便自己,趁机学习而已。

好了,再次开始论坛问,google搜。基本思路:
过滤未加密报文,对于0x64 0xe1 0x09 0x38开头的,保存后面的 global id备用
编写函数,参数为一个指针,指向一个global id
在目标进程分配空间,将此函数写入目标进程内存
在目标进程分配空间,将global id写入目标进程内存
最后CreateRemoteThread,在目标进程create一个线程,线程执行发送那个选怪的报文。
over

哈哈,目的达成了~
(最后一步的代码就不提供了。因为我也还没调试好,而且代码写的很垃圾。。。。谁让我菜呢,继续学习)

通过此文,主要是给广大初学者一点鼓励,只要有耐心,有毅力,有基本编程基础也可以学破解。
当然了。记住一点,学破解只学到会爆破就心满意足是绝对不行的。

好了,菜文到此结束,希望各位看官见谅。
菜就一个字,请别再说一次~