先自我介绍一下,本人菜鸟一个。由于工作主要基于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
哈哈,目的达成了~
(最后一步的代码就不提供了。因为我也还没调试好,而且代码写的很垃圾。。。。谁让我菜呢,继续学习)
通过此文,主要是给广大初学者一点鼓励,只要有耐心,有毅力,有基本编程基础也可以学破解。
当然了。记住一点,学破解只学到会爆破就心满意足是绝对不行的。
好了,菜文到此结束,希望各位看官见谅。
菜就一个字,请别再说一次~
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)