免责声明:逆向猎龙游戏完全 出于学习 目的,我 反对本文 提供的信息被用于任何恶意 行为,并且目前 发现的所有漏洞 已提交至 Aeria 游戏或已经被修复。
在 这篇 博客中, 我 准备向大家展示如何逆向在线游戏猎龙,为其创建一个包记录器和编辑器。 而 在 后续 的博客中 我 可能 还 会展示如何发送我们自己的数据包。
从数据包着手,可以做你想做的任何事情,包括寻找漏洞,相关功能函数。猎龙是一款Aeria Games开发的大型多人在线游戏。尽管现今这些游戏都捆绑了一系列的反作弊软件,例如 nProtect、GameGuard、AhnLab、HackShield 和 XIGNCODE3 , 但事实上 本文中的方法适用于任何游戏 。
这些反作弊软件的功能类似杀毒软件, 即 扫描已知类型的作弊手段 。 如果你运行了任何这种类型的作弊软件,反作弊软件就会将游戏关闭。这种软件同时还有一个内核模式的驱动,阻止使用分析类软件,例如Ollydbg或Cheat Engine,通过在ring0上HOOK某种API函数可以阻止在ring3上使用 OpenProcess, ReadProcessMemory 和 WriteProcessMemory 。(这些API函数经常用作分析)。更重要的是,这些反作弊软件使用 了 反调试技术,扫描内存以确保反作弊软件的代码不被修改,游戏时刻被保护着。
而 绕过反作弊软件 最简单的办法 就是阻止其运行,但是一旦不运行, 就有 95% 的几率在 1-5 分钟之内与游戏服务端断开连接。这是由于大部分的反作弊软件会有一个叫做 ” heartbeat packet ” 来确保软件时刻运行。服务端发送一个数据包至客户端,然后反作弊软件调用函数执行算法程序,返回一个正确的值,接着这个值再发送至服务端。如果返回的数据不正确或者压根没有返回,此时就会断开连接。这种工作机制类似于安装软件的许可证。如果你键入错误的 Key ,软件不会运行。 但 这些反作弊软件的代码通常都是运行在虚拟机下的字节码,导致这些算法非常难以被逆向。
更多细节,可以阅读这些博客:
http://www.unknowncheats.me/forum/index.php
https://mega.nz/#!8xkhFQJb!pSzoOHIqZqz5jIpQi39usyoGD2kHeTukwc4UU4b9P9c
猎龙游戏是一款大型多人在线游戏,不具备任何类型的反作弊软件,也没有包括很复杂的代码,因此这款游戏适合作为逆向在线游戏的教程,那下面 就 开始吧。
注意: 2016 年 1 月 28 日更新后 Aeria Games 为猎龙游戏增加了 XIGNCODE3 ,如果你不知道怎么绕过,那么接下来的分析将不能完成。 而 目前, 并 不需要通过一个 ” heartbeat ” 包,只需找到 XIGNCODE3 的初识化函数,然后修改其中的代码导致其不能初始化即可。(在函数返回值前,植入 mov eax,1 即可)。
l 熟悉 x86 汇编 , 逆向工程以及 Win32 APIs
l Aeria Games 账户 ( http://aeriagames.com )
l 猎龙游戏在线 ( http://dragomonhunter.aeriagames.com/ )
l Ollydbg v1.10 ( http://www.ollydbg.de/ )
l IDA Pro ( https://www.hex-rays.com/products/ida/support/download_freeware.shtml )
l 通过 Sirmabus 将 IDA 函数字符串 关联到 插件上
( http://sourceforge.net/projects/idafunct ionstringassociate/)
l Keygener Assistant v2.0 ( http://www.softpedia.com/get/Programming/Other-Program ming-Files/Keygener-Assistant.shtml)
警告 :如果分析这些类型的恶意软件,为了防止感染你的电脑应在 VMware 上运行。 但 由于 本次的 软件来源是可信的,因此可以放心运行。
再一次确保你的猎龙游戏是最新版的。
打开 Keygener Assistant ,点击 ” scanning ” ,在猎龙游戏的安装文件夹下找到 Game.bin 。请确保将“文件类型”选为“所有文件”,否则在在下图的 Keygener Assistant 中不会找到该文件(尽量它是 .bin 的扩展名,但是根据 PE 文件头,其实它是 .exe 文件)。
Keygener Assistant 显示 Game.bin 文件没有加壳,同时指出了 hash 和加密特征的偏移以及编译器。下面通过 IDA 进一步分析。
根据 IDA 的分析,代码没有被混淆。
输入表也没有混淆。
字符串也没有加密。
在多数情况下,如果分析的是恶意软件,上述的步骤还需要做进一步的确认。
这个游戏使用的是 Gamebryo Game Engine ( http://www.gamebryo.com/ ) 来阻止游戏被逆向的,根据我多年的经验,断定使用的是下图 IDA 中相关的字符串:
当我分析在线游戏的时候,经常会用到 IDA 的字符串关联插件。这个插件可以在函数入口处创建引用字符串的注释。在 IDA 中,按 ALT+F6 ,如下图:
点击继续,插件开始分析。软件的大小决定分析的时间。而这个软件只有 14MB ,因此不会太久。之后你会看见函数引用字符串的注释:
所有就绪,我们开始创建包编辑器。问题是,从哪里着手呢?发送和接受数据包函数的路径有太多太多。
一种方法就是找到一些成员的指针,例如游戏人物的坐标,这些可能存在数据包中并发送至服务端。接着找到那个代码访问该指针,移动你的人物角色,之后不断跟踪直到你找到发送人物位置数据包的代码。
最好的办法就是从结束的地方开始。我这么说是什么意思呢?在 Windows 上运行,数据包最后发送的地方或者数据包刚开始接收的地方都会出现一个由 WS2_32.dll 导出的 Winsock 函数。因此在这里我会展示如何追踪发送加密数据包之前的函数,可能在以后的博客中会展示如何记录加密后的数据包。
send ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms740149 (v=vs.85).aspx)
sendto ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms740148 (v=vs.85).aspx)
WSASend ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms742203 (v=vs.85).aspx)
recv ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms740121 (v=vs.85).aspx)
recvfrom ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms740120 (v=vs.85).aspx)
WSARecv ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms741688 (v=vs.85).aspx)
现在检查这些函数的导入表,(最简单的方法就是在 IDA 的 ” library ” 中滚动查找 WS2_32 )。
可以看到,游戏使用 WSASend 发送数据包,使用 WSA Recv 接收数据包。
通过交叉引用功能查看发送数据包的函数。
可以看到两个函数调用 WSASend 。查看第一个函数。
如果 WSASend 返回值为 -1 即十六进制的 0xFFFFFFFF ,将会执行 WSAGetLastError ,随后从该函数返回一个 0 值,意味着返回错误,这样由于数据包的错误导致游戏关闭。如果 WSASend 返回 0 意味着数据包正确,发生跳转继续执行后续函数。
在 WSASend 函数的下面有一个 WSAAsyncselect 函数。我猜测可能这个游戏在运行的时候不仅仅只开放一个连接,可能是两个,因为有两个函数调用 WSASend 。可以看到 WSASend 的 buffer 计数器参数一直是 1 ,这个游戏同样可以使用 WSAAsyncSelect 让游戏知道数据包正确发送了,这样保持分组的顺序让下一个数据包继续发送。
在 WSAAsyncSelect 之前的 “mov [esi+288Ch], eax” 也很有意思。可能是一种互斥元素,或者网络数据结构或者是一个类,其他数据包发送之前必须要被设置。
看看第二个调用 WSASend 的函数。
call ds:WSASend
cmp eax, 0FFFFFFFFh
jnz short loc_F1E730
和第一个函数有些类似,如果向上回溯,可以看到另一个调用 WSAAsyncSelect 的地方
mov [eax+288Ch], esi
我们又发现了这个指针 +0x288C 可能是网络数据结构或者类。通过 Ollydbg 的内存监视在这个地址处下断点,能够找到这个指针成员的值,接着回溯该网络成员指针。
这里我使用的是 Ollydbg 的动态调试功能。因此将游戏附加至 Ollydbg 。
在 WSASend 处下断点(使用 OD 的热键 F2 即可)
通常我会通过游戏的聊天窗口回溯。输入 ” Hello ” ,点击 enter , OD 就会断在 WSASend ,看到如下图所示:(注意断点时间不宜过长,否则就会断开连接)
检查一下数据包是否被加密了。跟踪 WSABUF 数组( pBuffers )在这里是 0x0018FC10 。
这是一个 WSABUF 的结构, C++ 形式:
“ len ” 是 buffer 的长度, ” buf ” 是指向 buffer 的指针。这里我们只有一个 buffer ,( nBuffers=1 )这个数组是这样的:
0x0018FC10 (pBuffers) + 0×00 = the length of the buffer (0x0018FC10)
0x0018FC10 (pBuffers) + 0×04 = pointer to the buffer (0x0018FC14)
由于软件是 32-bit 的无符号数据格式,总共 4 个字节,因此要在这个基础上加 4 得到 Buffer 地址,指向 buffer 同样是一个 4 字节的地址。
我们可以看到 buffer 大小为 0×19 十进制为 25 。 B uffer 保存地址为 0x180c4940 。在内存中跟踪这个 Buffer 。我在聊天栏里键入 ” hello ” ,因此在 buffer 中可以看到保存的文本,如果这个值没加密,我们的记录发送数据包的工作就可以结束了。
但是上述数据包中的数据看起来像是被加密的,因为在内存中没有看到 ” hello ” 的 ASCII 。这个是现今在线游戏的常见问题,目前还有很多游戏是没有加密数据包的。
在次键入 ” hello ” 之后,出现这种情况:
这个很有趣,显示加密方案可能是一个初始化的 vector (IV) 或者 starting variable (SV) ,这就能解释为什么输入一样而输出确不能始终一样,而且两个 buffer 的前两个字节是相同的( 0×17 0×00 )。
尝试键入 ” hello1 ” 看看有什么:
“ 1700 ” 变为 ” 1800 ” ,这就说明前两个字节应该是数据包的大小。
现在我们知道数据包是被加密的,现在我们要尝试找到加密前的 buffer 以及 buffer 大小,因此我们可以在这里 hook 到发送至服务器的数据包。通常这个位置就在加密 buffer 函数之前。为了验证,可以在 WSASend 上再下一个断点,发送聊天数据包,在栈中找到函数的返回地址。这个返回地址在栈顶,如下图:
OD 中显示了这个函数来源自 IDA 分析的第二个函数,跟踪这个函数直到看到这个函数的调用者。
如果数据包发送成功,则函数跳转至结束,如果继续回溯是在 WindowProc 处结束。似乎我们能够回溯到 buffer 是如何加密的点。
上述显示这个游戏是通过 Window 的消息来通告一个数据包是否准备发送或者已经被发送。这种通过 WSAAsyncSelect 的消息参数 0x9C40 发送 Windows 消息的途径有很多种,在下图可以看到,使用不同的消息参数 0x9C41 可以调用 PostMessageW 。由于当数据包不发送时, PostMessageW 才会被执行,我就通过消息 0x9C41 通知这个游戏数据包没有发送。
在 WSAAsyncSelect 处下断点。
在 OD 中找到 WSAAsyncSelect ,看下这个函数:
这个函数看起来有点意思,因为调用 WSAAsyncSelect 函数和指针,假设是网络数据结构或者类,这里的 ESI 和 WSASend 的 ESI 一样。
根据我的经验和这段代码,这个调用约定是 ” thiscall ” ,因为网络指针在函数开始处通过 ECX 向 ESI 传值,并且在函数结束处我们可以看到 RETN 4 。 Keygen Assistant 告诉我们游戏是被 MSVC++ 编译的,这就意味着这个是 ” this ” 指针,同样根据网络指针,传入 ECX ,因此函数的功能是清洁栈空间。
如果阅读汇编代码,第二个参数是一个指向结构体的指针。
在函数开始处下断点,分析压进栈区的参数。
在上图可以看到指向结构体的指针,下面会解释这个结构体,但是现在我们看看结构体的第二个成员:
可以看到加密前的数据包, buffer 大小同样是 0×0017 。在发送 ” hello ” 数据包之前找到了该 buffer 。
这个结构体的第一个参数可能是 buffer 的 hash 或者一个 ID 用来后续整理的,第二个参数指向 buffer 起始地址,第三个参数指向 buffer 结束地址,第四个参数看起来应该是指向分配给 buffer 的内存结束的地址。如果将第三个成员和第二个成员相减( 0x0E680279 - 0x0E680260 )结果是 0×19 ,这个就是最终服务器读取的包括头两个字节完整 buffer 的大小。
为了确认这个 buffer 是加密前的,将 ” hello ” 修改为 ” hello1 ” ,看下结果:
现在看来,我们可以在数据包发送至服务器前修改 buffer 值,而在你接收聊天信息前不会显示任何消息。
为了构造一个数据包记录器,可以在让程序跳转至你的代码中打印数据包内容。这样工作搞定!
现在已经可以成功修改数据包了,可以分析服务端和客户端之间发送的数据包。现在我们也可以发送我们自己定义的数据包,甚至通过逆向工程可以创建自己的游戏客户端。通过向服务器发送不正常的数据包而无需通过软件的开发者,通常就可以引发漏洞。你也可以根据数据包来回溯函数功能,例如聊天栏,人物移动信息,购买条目信息更直接的是直接调用游戏功能而不通过按键的形式。
这里我展示一个简单的方法利用这个在线游戏。大多数在线游戏有一个 ” swear ” 的过滤系统,可以防止玩家互喷脏话。
例如,如果我在游戏里说 ” shit ” ,是这样的:
游戏的过滤系统比较输入的文本,如果是脏话则用垃圾字符代替,下面看看如果我将 buffer 修改为 ” shit ” 会怎样:
现在客户端绕过了过滤系统,显示了 ” shit ” 。这个只是众多可利用的一部分而已。当和其他玩家一起登录的时候有时会意外崩溃,当然这个漏洞几小时之后就被修复了。
个人认为 游戏公司以及其他任何一家公司,应该把更多的精力投入到自己的网络安全 , 而且应当由服务器来控制过滤器。总是会有人试图利用,攻击和黑掉这些安全性低的产品。游戏公司经常收到不计其实的盗号,欺骗投诉从而导致玩家退出,这 必然 意味着亏损。
然而 不幸的是,大多数游戏公司往往不愿意在安全上开销。他们经常通过反作弊软件来抵抗攻击。 但 这种做法通常只能阻止比较低级的攻击, 这里建议这些 公司应更专注于自己的服务器的安全性。
*参考来源: 0xbaadf00dsec ,FB小编老王隔壁的白帽子翻译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)