Retro Gaming Vulnerability Research: Warcraft 2

Retro Gaming Vulnerability Research: Warcraft 2

This blog post is part one in a short series on learning some basic game hacking techniques. I’ve chosen Warcraft 2 for a variety of reasons:
这篇博文是关于学习一些基本游戏黑客技术的简短系列的第一部分。我选择《魔兽争霸2》的原因有很多:

  • Old games have more lax security (no anti-cheat)
    老游戏的安全性比较松懈(没有反作弊)
  • Easy to run on even modern OSes (Runs on Windows XP – 11 using GoG)
    即使在现代操作系统上也易于运行(使用 GoG 在 Windows XP – 11 上运行)
  • Easily obtained in a digital format for relatively cheap ($10 USD on GoG)
    以相对便宜的价格轻松以数字格式获得(GoG 上为 10 美元)

With those things in mind, most older RTS games work in a similar manner, and you should be able to apply these techniques to other games, though maybe not the tooling I’ve developed for this.
考虑到这些事情,大多数较旧的 RTS 游戏都以类似的方式工作,您应该能够将这些技术应用于其他游戏,尽管可能不是我为此开发的工具。

Why reverse engineer games?
为什么要对游戏进行逆向工程?

While hacking apps in general can be complex, games often add additional layers of complexity, such as rendering engines, physics engines, network inputs (peer to peer and server-based, LAN or internet). Anyone interested in this should really have at least a basic understanding of their target architecture (x86/x86-64 for most PC games). It helps to know C or C++, and really you need to know roughly how games work (on a deeper level than just playing them).
虽然黑客应用程序通常可能很复杂,但游戏通常会增加额外的复杂性层,例如渲染引擎、物理引擎、网络输入(点对点和基于服务器、局域网或互联网)。任何对此感兴趣的人都应该至少对他们的目标架构有一个基本的了解(大多数 PC 游戏的 x86/x86-64)。了解 C 或 C++ 会有所帮助,实际上你需要大致了解游戏是如何工作的(在更深层次上,而不仅仅是玩它们)。

So what do you get out of reversing and security testing a game? Quite a few things. You can sometimes make bug fixes, both official and unofficial. Maybe add or update features with your own mods. And of course, you can find
那么,您能从游戏的倒车和安全测试中得到什么呢?相当多的事情。您有时可以进行官方和非官方的错误修复。也许使用您自己的模组添加或更新功能。当然,你可以找到

In this post, I’ll outline the methodology I use for selecting a game, and walk through the methodology using Warcraft 2 as an example for each step. I chose this game because it runs (somewhat well) on modern operating systems like Windows 10/11, it’s cheap at $10 USD, has offline LAN play, and is very old (so there’s no anti-cheat). This makes it an ideal target for teaching black-box reversing of games.
在这篇文章中,我将概述我用于选择游戏的方法,并以魔兽争霸 2 为例来介绍每个步骤的方法。我之所以选择这款游戏,是因为它在 Windows 10/11 等现代操作系统上运行(有点好),它便宜 10 美元,有离线局域网游戏,而且很旧(所以没有反作弊)。这使它成为教授游戏黑盒反转的理想目标。

While modern games have some differences like anti-cheat and encrypted traffic, that’s just security dressing. In the low end it’s still running loops, processing traffic, and updating a game state. Each game client sends packets on any action, and all other clients simulate each other client to show the player an accurate gamestate, even without a dedicated server to synchronize everything.
虽然现代游戏有一些差异,例如反作弊和加密流量,但这只是安全装扮。在低端,它仍在运行循环、处理流量和更新游戏状态。每个游戏客户端在任何动作上发送数据包,所有其他客户端相互模拟,向玩家显示准确的游戏状态,即使没有专用服务器来同步所有内容。

Methodology 方法论

At a high level you can begin a game-hacking project by thinking about the following topics:
在高层次上,您可以通过考虑以下主题来开始游戏黑客项目:

  • End goal 最终目标
  • Prior work 以前的工作
  • Attack Surface 攻击面
  • Analysis 分析
  • Exploitation 开发

So let’s dive into each of these steps, and use Warcraft 2 as an example. The last step, exploitation, will not be shown in this blog post.
因此,让我们深入研究这些步骤中的每一个,并以魔兽争霸 2 为例。最后一步,即利用,不会在本博文中显示。

End Goal 最终目标

With Warcraft 2, I would like to look for security bugs, such as memory corruption, that are remotely exploitable via other players in a match. This was first brought to my attention when GOG re-released Warcraft 2 with it still supporting online and LAN play. Warcraft 2 was first released in 1995, with the Battle.net edition for online play being released in 1999. Smashing the Stack for Fun and Profit was released in 1996. Playing a game online that was released while Stack Smashing was new is probably not a wise move. But why just assume it’s unwise when we could spend hours reverse engineering it and confirming that one way or the other?
在《魔兽争霸 2》中,我想寻找安全漏洞,例如内存损坏,这些漏洞可以通过比赛中的其他玩家远程利用。当GOG重新发布《魔兽争霸2》时,我首先注意到了这一点,它仍然支持在线和局域网游戏。魔兽争霸 2 于 1995 年首次发布,在线游戏 Battle.net 版于 1999 年发布。Smashing the Stack for Fun and Profit 于 1996 年发布。玩一款在 Stack Smashing 是新游戏时发布的在线游戏可能不是明智之举。但是,当我们可以花费数小时对其进行逆向工程并以一种或另一种方式确认这一点时,为什么只是假设这是不明智的呢?

Ultimately I probably wouldn’t play this outside of a LAN setting with trusted friends either way, but it should be fun to dig into it.
最终,无论哪种方式,我都不会在局域网设置之外与值得信赖的朋友一起玩这个游戏,但深入研究它应该很有趣。

When selecting a target, depending on your goal, it’s useful to consider the following:
选择目标时,根据您的目标,考虑以下几点很有用:

  • Is my target single-player, multi-player, or both? If it is multiplayer, is it peer-to-peer or server based? Online or LAN?
    我的目标是单人游戏、多人游戏,还是两者兼而有之?如果是多人游戏,是点对点还是基于服务器?在线还是局域网?

    • WC2 is both, peer-to-peer, online and LAN.
      WC2 既是点对点的,也是在线的,也是局域网的。
  • How old is this game? Older games tend to be somewhat simpler, but may be hard to run. New games will usually run, but often have more sophisticated anti-cheat.
    这个游戏几岁了?较旧的游戏往往更简单,但可能难以运行。新游戏通常会运行,但通常具有更复杂的反作弊功能。

    • If you use the GoG installer, and run the Enhanced Edition, it should run fine. Good luck if you have the older CD releases.
      如果您使用 GoG 安装程序,并运行增强版,它应该可以正常运行。如果您有较旧的 CD 版本,祝你好运。
  • What is the attack surface? Custom maps, in-game chat, chat markdown, game-state traffic… all of this could be interesting.
    攻击面是什么?自定义地图、游戏内聊天、聊天降价、游戏状态流量……所有这些都可能很有趣。

    • These blog posts will look mostly just at network traffic, though fuzzing via map parsing is another interesting attack surface.
      这些博客文章主要只关注网络流量,尽管通过地图解析进行模糊测试是另一个有趣的攻击面。
  • Is there prior work done on this game?
    之前有没有做过这个游戏的工作?

    • Quite a lot, though we won’t look at all of it.
      相当多,尽管我们不会全部查看。

Prior Work 以前的工作

This is an interesting topic, and depends partly on your goals. I wanted to perform clean-room reversing on Warcraft 2, so while there is a known leak of source code for the Warcraft 2 PS1 version, I’ve not downloaded or viewed it in any way, to ensure I am not tainted. I can’t comment on how similar it is to the PC version I’m playing, though I know the PS1 version lacked multiplayer, so that attack surface is likely not in the code (given the very limited 2MB of main ram the PS1 used, I’m betting Blizzard stripped out anything unnecessary.) I also believe it did not have custom maps, so the attack surface would be very limited, and ultimately not very useful to my efforts.
这是一个有趣的话题,部分取决于你的目标。我想在《魔兽争霸 2》上执行洁净室反转,所以虽然魔兽争霸 2 PS1 版本的源代码已知泄露,但我没有以任何方式下载或查看它,以确保我没有受到污染。我无法评论它与我正在玩的 PC 版本有多相似,尽管我知道 PS1 版本缺乏多人游戏,因此攻击面可能不在代码中(鉴于 PS1 使用的 2MB 主内存非常有限,我敢打赌暴雪去掉了任何不必要的东西。我还认为它没有自定义地图,因此攻击面将非常有限,最终对我的工作没有多大用处。

For any other game, there are many options to get a leg up on hacking. Many of these will only apply to closed-source games, as open-source means you can pretty easily read/modify it.
对于任何其他游戏,有很多选择可以在黑客攻击中占得先机。其中许多仅适用于闭源游戏,因为开源意味着您可以非常轻松地阅读/修改它。

  • Are there any open-source re-implementations?
    是否有任何开源的重新实现?

  • Existing mods 现有模组
  • Known hacks/bugs 已知的黑客/错误
  • Source code availability
    源代码可用性

    • Open-source intentionally (such as Arx Fatalis or the original Doom)
      有意开源(例如Arx Fatalis或原始的Doom)
    • Or leaked accidentally (such as Warcraft 2 PS1)
      或意外泄露(如魔兽争霸2 PS1)
  • Debugging symbols 调试符号
  • Leaked dev/beta builds 泄露的开发/测试版版本

Any of the above can give you a huge leg up. For Warcraft 2, while I will not view the leaked source (if you want to find it I leave that up to you, and will not link to something that breaches copyright law), Stratagus/Wargus sounds promising initially, however upon deeper analysis it is a completely separate open-source RTS engine that has a Warcraft 2-ish mod over the top of it. It plays pretty similarly to the end-user, but the underlying code is not at all the same, so any bugs in it won’t help our purposes.
以上任何一项都可以给你带来巨大的优势。对于魔兽争霸 2,虽然我不会查看泄露的来源(如果你想找到它,我会把它留给你,并且不会链接到违反版权法的东西),Stratagus/Wargus 最初听起来很有希望,但经过深入分析,它是一个完全独立的开源 RTS 引擎,上面有一个魔兽争霸 2 式的模组。它的玩法与最终用户非常相似,但底层代码完全不同,因此其中的任何错误都无助于我们的目的。

Attack Surface 攻击面

You will want to consider what is the attack surface of a given game, and how will this line up with your goals? If you want to mod/bug fix, then remote attack surface isn’t usually necessary (though Ratchet and Clank: Up Your Arsenal reportedly did get a patch via a remotely-exploitable buffer overflow, since it lacked patching by default, so being creative is always cool).
您需要考虑给定游戏的攻击面是什么,以及这将如何与您的目标保持一致?如果你想修改/修复错误,那么远程攻击面通常不是必需的(尽管据报道,《瑞奇与叮当:你的武器库》确实通过可远程利用的缓冲区溢出获得了补丁,因为它默认缺乏补丁,所以有创意总是很酷的)。

In the case of Warcraft 2, we’ll considered the following attack surfaces:
对于魔兽争霸 2,我们将考虑以下攻击面:

  • Gamestate traffic Gamestate 流量
  • In-game chat 游戏内聊天
  • Custom maps 自定义地图

Analysis 分析

This next section will be long, and I will attempt to outline how I reversed packet structures for gamestate and chat. Please be warned, there is some x86 disassembly, and it is probably a bit technical and dry. I apologize, if reverse engineering is not your jam, I am unable to make it seem cooler than it is.
下一节将很长,我将尝试概述我如何反转游戏状态和聊天的数据包结构。请注意,有一些 x86 拆卸,它可能有点技术性和枯燥。我很抱歉,如果逆向工程不是你的果酱,我无法让它看起来比实际更酷。

Starting off, I connected two Warcraft 2 clients via LAN connection. Then I used Wireshark to start capturing traffic. I narrowed it down to the two clients by setting the following filter for just their IPs and UDP traffic.
首先,我通过LAN连接连接了两个魔兽争霸2客户端。然后我使用 Wireshark 开始捕获流量。我通过仅为其 IP 和 UDP 流量设置以下过滤器,将其缩小到两个客户端。

Like many games, Warcraft 2 sends constant heartbeat packets back and forth between clients. This is to ensure that both clients are still running. If we break into the game in a debugger, the other client will pop-up a warning that it can’t reach the first client. Luckily, Warcraft 2 handles this very gracefully. Some games will kick the paused client or even crash.
与许多游戏一样,《魔兽争霸 2》在客户端之间来回发送恒定的心跳数据包。这是为了确保两个客户端仍在运行。如果我们在调试器中闯入游戏,另一个客户端将弹出一个警告,指出它无法访问第一个客户端。幸运的是,《魔兽争霸2》非常优雅地处理了这一点。有些游戏会踢暂停的客户端甚至崩溃。

All that being said, if you start watching traffic, your Wireshark UI quickly looks like this and becomes hard to read.
话虽如此,如果您开始关注流量,您的 Wireshark UI 很快就会看起来像这样并且变得难以阅读。

Retro Gaming Vulnerability Research: Warcraft 2

Here are three of those repeating heartbeats in a row, notice any patterns?
这是连续重复的三个心跳,注意到任何模式了吗?

000000000166a7c8f5c39687c2000000015a0604fa6c5187c2000ca1ffffff0801017108620180​

00000000015a0604fa6c5187c20000000166a7c8f5c39687c2000cb7ffffff080000702ce20180​

000000000166a7c8f5c39687c2000000015a0604fa6c5187c2000ca0ffffff0801017209e20180

Note: The packets all start with the null bytes followed by a x01, then two 8-byte patterns. That second 8-byte pattern is flipped with the first, this is consistent when packets are sent from one client to another. Finally there are 2 bytes that are the length of the remaining bytes. Based on limited reversing, I believe this is used as the IPX wrapper for networking, which Warcraft 2 used for networking, as it’s old enough that TCP/IP was not as common. We’ll ignore those first 27 bytes on each packet (this is a shortcut, it took me reversing the game, breaking on packet receive, and seeing what the code did with the bytes to notice those are used in an IPX library, while gamestate packet parsing starts with the x00c1.) I may be wrong about this, once I realized in the debugger that the gamestate parsing didn’t read these, I mostly ignored them.
注意:数据包都以空字节开头,后跟一个 x01,然后是两个 8 字节模式。第二个 8 字节模式与第一个模式翻转,当数据包从一个客户端发送到另一个客户端时,这是一致的。最后,有 2 个字节是剩余字节的长度。基于有限的反转,我相信它被用作网络的 IPX 包装器,魔兽争霸 2 将其用于网络,因为它足够古老,以至于 TCP/IP 并不常见。我们将忽略每个数据包上的前 27 个字节(这是一个快捷方式,我花了很长时间反转游戏,中断数据包接收,并查看代码对字节做了什么,以注意到这些字节在 IPX 库中使用,而游戏状态数据包解析从 x00c1 开始。我可能错了,一旦我在调试器中意识到游戏状态解析没有读取这些内容,我大多忽略了它们。

So now a single packet (spaced into bytes) looks like this:
因此,现在单个数据包(以字节为间隔)如下所示:

a1 ff ff ff 08 01 01 71 08 62 01 80​

If we look at some non-heartbeat packets, we can find some other similarities, and begin to suss out meanings to these bytes. If you are used to reading network protocol formats on the wire, you may notice some potential fields here, such as lengths or counters. For now we’ll move on.
如果我们查看一些非心跳数据包,我们可以发现一些其他相似之处,并开始确定这些字节的含义。如果您习惯于在线路上读取网络协议格式,您可能会注意到此处的一些潜在字段,例如长度或计数器。现在,我们将继续前进。

In the case of Warcraft 2, we are lucky that heartbeats are a static length, and very few gamestate updates are that same length, so an easy fix is to apply the following view filter in Wireshark
在《魔兽争霸 2》中,我们很幸运,心跳是静态长度,很少有游戏状态更新是相同的长度,因此一个简单的解决方法是在 Wireshark 中应用以下视图过滤器

ip.addr == [client0-IP]  amp; amp; ip.addr == [client1-IP]  amp; amp; udp  amp; amp; frame.len != 81

Once we do, the traffic looks like this, with only game state traffic showing:
完成此操作后,流量如下所示,仅显示游戏状态流量:

Retro Gaming Vulnerability Research: Warcraft 2

If heartbeats were differing lengths we’d have to reverse the traffic format more so that we could create a more nuanced filter. For instance, eventually we’ll learn that the packets contain a certain byte in a certain position to indicate what the packet is used for. If we reversed some of the game parsing we could learn what command byte is used for a heartbeat, and look for that specifically (which is what I did in the tooling to auto-ignore heartbeats).
如果检测信号的长度不同,我们将不得不更多地反转流量格式,以便我们可以创建更细致的过滤器。例如,最终我们将了解到数据包在特定位置包含某个字节,以指示数据包的用途。如果我们反转一些游戏解析,我们可以了解用于心跳的命令字节,并专门查找它(这就是我在工具中自动忽略心跳所做的)。

Once we can analyze traffic in a more simple manner, we can start to issue commands in game, and look at the results in Wireshark. Let’s take an in-game chat for example. Press Enter, then type any arbitrary message, and Enter again to send it. We can repeat this two or three times, and do it from the second client back.
一旦我们可以以更简单的方式分析流量,我们就可以开始在游戏中发出命令,并在 Wireshark 中查看结果。让我们以游戏内聊天为例。按 Enter 键,然后键入任意消息,然后再次按 Enter 键发送该消息。我们可以重复两到三次,然后从第二个客户端开始。

These similar messages will let us begin to see similarities and differences in each message.
这些相似的信息将让我们开始看到每条消息的相似之处和不同之处。

Ignoring the beginning 25 bytes, the following is heartbeat packet from earlier compared to a chat message that said “asdfasdf”. You may begin to notice similarities and differences between these packets:
忽略开头的 25 个字节,以下是与显示“asdfasdf”的聊天消息相比之前的检测信号数据包。您可能会开始注意到这些数据包之间的相似之处和不同之处:

a0 ff ff ff 08 01 01 72 09 e2 01 80​

4a eb ff ff 13 01 01 c2 3b 41 23 23 fd 01 61 73 64 66 61 73 64 66 00​

If we keep the message length the same, I.E. send “asdfasdf” each time, the packet lengths are the same, and each will look something like the following (grouped for ease of viewing):
如果我们保持消息长度相同,即每次发送“asdfasdf”,则数据包长度相同,并且每个数据包将如下所示(分组以方便查看):

a0ffffff 08 0101 7209e2 01 80​

4aebffff 13 0101 c23b41 23 23fd01 617364666173646600​

  • The first four bytes seem to be counting down
    前四个字节似乎在倒计时

    • Each subsequent packet has it decremented by one, these were quite a few packets apart so it’s decremented a quite a bit
      随后的每个数据包都递减了一个,这些数据包相隔了好几个数据包,所以它递减了很多
  • The next byte is the length of the remaining bytes, inclusive of itself
    下一个字节是剩余字节的长度,包括自身

    • x08 = 8 bytes for heartbeat, x13 = 19 bytes for the chat
      x08 = 8 个字节用于检测信号,x13 = 19 个字节用于聊天
  • The next two bytes indicate which player sent them
    接下来的两个字节表示哪个玩家发送了它们

    • Both exampled above show a x0101, for coming from player 01. Other packets not shown use x0000, coming from player 00
      上面的两个示例都显示了一个 x0101,表示来自玩家 01。未显示的其他数据包使用 x0000,来自玩家 00
  • The next three bytes changed for each packet
    接下来的三个字节为每个数据包更改

    • including from otherwise nearly identical heartbeats or chats.
      包括来自其他几乎相同的心跳或聊天。
    • Eventually we’ll learn these are checksums, for now we’ll ignore them.
      最终,我们将了解到这些是校验和,现在我们将忽略它们。
  • The next byte (number 11) is static within similar packets
    下一个字节(数字 11)在类似数据包中是静态的

    • Eventually we’ll learn this is the “command byte” as I call it. It’s checked in a few different switch statements to determine how to process each packet. All heartbeats usex01, all chats use x23
      最终,我们将了解到这就是我所说的“命令字节”。它会在几个不同的 switch 语句中进行检查,以确定如何处理每个数据包。所有心跳 usex01,所有聊天都使用 x23
  • Then there is a static x80 at the end of heart beats
    然后在心跳结束时有一个静态 x80
  • Instead of the single static x80, chat messages have another three bytes before the message
    聊天消息不是单个静态 x80,而是在消息之前还有另外三个字节

    • Eventually I realized this was another checksum, by watching how it changes with each message, and trying to modify it on the wire and seeing the game handle it differently.
      最终,我意识到这是另一个校验和,通过观察它如何随着每条消息的变化,并尝试在网络上修改它,并看到游戏以不同的方式处理它。
  • Finally we have the 9 bytes (in this case) that make up the actual chat
    最后,我们有 9 个字节(在本例中)组成实际聊天

    • x617364666173646600 = “asdfasdfx00”, or our message with a null byte to terminate the string
      x617364666173646600 = “asdfasdfx00”,或者我们的消息带有空字节来终止字符串

Some of this took me reversing the game, breaking on the recvfrom function, and following the flow through code to see what it did. Some of this just took many similar packets to determine what each part is. Following this same idea over and over for each type of packets, we can slowly learn what packets look like for each gamestate update.
其中一些需要我反转游戏,打破 recvfrom 函数,并按照代码流程查看它的作用。其中一些只是需要许多类似的数据包来确定每个部分是什么。对于每种类型的数据包,一遍又一遍地遵循同样的想法,我们可以慢慢了解每个游戏状态更新的数据包是什么样子的。

Armed with this knowledge, I began building out my tooling to be a little more dynamic than just network monitoring and a debugger. I’m a huge fan of Frida, having used it many times in the past, and decided to use that to write the test tooling, since this game lacks an anti-cheat. The version published at the same time as this blog post shows messages inbound and outbound, but does not offer the user the chance to modify them at this time (there is code for that, but it’s partially complete and commented out for now.)
有了这些知识,我开始构建我的工具,使其比网络监控和调试器更具动态性。我是 Frida 的忠实粉丝,过去曾多次使用它,并决定用它来编写测试工具,因为这款游戏缺少反作弊功能。与这篇博文同时发布的版本显示了入站和出站消息,但目前不为用户提供修改它们的机会(有代码,但它是部分完成的,目前已被注释掉。

Retro Gaming Vulnerability Research: Warcraft 2

You’ll notice at least one of the gamestat updates listed in the screenshot above shows another command (select unit). Reversing other commands was similar. Perform a lot of them, ignore the fields we knew, and look for new fields. I was able to make a partial table of command bytes by reading the 11th byte of any command.
您会注意到上面屏幕截图中列出的至少一个 gamestat 更新显示了另一个命令(select unit)。反转其他命令也类似。执行很多,忽略我们知道的字段,并寻找新的字段。我能够通过读取任何命令的第 11 个字节来制作命令字节的部分表。

  • x1c – Move UI button pressed​
    x1c – 按下移动 UI 按钮
  • x1c – Cancel UI button pressed​
    x1c – 按下取消 UI 按钮
  • x1e – Place building​
    x1e – 场所建设
  • x1f – Stop moving​
    x1f – 停止移动
  • x21 – Build/research​ x21 – 构建/研究
  • x22 – Cancel build/research​
    x22 – 取消构建/研究
  • x23 – Send chat​
    x23 – 发送聊天
  • x28 – Move unit​
    x28 – 移动单元
  • x05 – Broadcast game​ lobby to network
    x05 – 将游戏大厅广播到网络
  • x13 – Update selected scenario​ to lobby
    x13 – 将所选方案更新到大厅
  • x1b – Select unit
    x1b – 选择单位

Note: in WC2 commands such as move or attack don’t include a unit ID to say which unit is moving. The Select command does, then the next command sent applies to the last-selected unit. This is different than some RTS games, including the open-source re-implementation Wargus mentioned above.
注意:在 WC2 中,移动或攻击等命令不包含用于指示哪个单位正在移动的单位 ID。“选择”命令执行,然后发送的下一个命令将应用于最后选择的单元。这与一些 RTS 游戏不同,包括上面提到的开源重新实现 Wargus。

Up until now, I had to do minimal reversing to figure out the gamestate traffic. It was at this point I really dug in with my debugger to start to figure out the more advanced parts, such as how are the checksums generated. If we want to spoof most any packets, we’re going to need one or more checksums. So let’s do a quick overview of how I began to do this.
到目前为止,我必须进行最少的反转才能弄清楚游戏状态流量。正是在这一点上,我真正深入研究了我的调试器,开始弄清楚更高级的部分,例如校验和是如何生成的。如果我们想欺骗大多数数据包,我们将需要一个或多个校验和。因此,让我们快速概述一下我是如何开始这样做的。

Note: I was using Ida Pro for this project, along with it’s debugger, but the free NSA-provided Ghidra should work, or really any debugger you prefer. Screenshots are a work copy of Ida Pro, so may look different than yours.
注意:我在这个项目中使用了 Ida Pro,以及它的调试器,但 NSA 提供的免费 Ghidra 应该可以工作,或者您喜欢的任何调试器。屏幕截图是 Ida Pro 的工作副本,因此可能看起来与您的屏幕截图不同。

First, I decided to break on the sendto function. I know from past experience that that is the native windows networking call for sending UDP packets, likewise recvfrom is for reading them. We know this game will likely be using these as Wireshark has shown us all UDP traffic up to this point.
首先,我决定中断 sendto 函数。我从过去的经验中知道,这是用于发送 UDP 数据包的本机 Windows 网络调用,同样,recvfrom 用于读取它们。我们知道这个游戏可能会使用这些,因为 Wireshark 已经向我们展示了到目前为止的所有 UDP 流量。

Both of these are loaded in the process when it begins. Given that this game lacks ASLR, they can be found at x0048c88c for recvfrom and x0048c892 for sendto

Retro Gaming Vulnerability Research: Warcraft 2

You can follow code flow in the debugger from sendto, and look at the function arguments to see what will be sent. Note that the listed pointer and length show a buffer with a familiar structure, and lacks the beginning 25 bytes, since that isn’t part of what UDP is expecting.
可以从 sendto 跟踪调试器中的代码流,并查看函数参数以查看将发送的内容。请注意,列出的指针和长度显示了具有熟悉结构的缓冲区,并且缺少开头的 25 个字节,因为这不是 UDP 预期的一部分。

Alternatively, since you know the address of the function, you can use Frida with onEnter to read the arguments, shown below:
或者,由于您知道函数的地址,因此可以使用带有 onEnter 的 Frida 来读取参数,如下所示:

Retro Gaming Vulnerability Research: Warcraft 2

This was the start of my tooling, and originally it spit all packet buffers out like this:
这是我工具的开始,最初它像这样吐出所有数据包缓冲区:

Retro Gaming Vulnerability Research: Warcraft 2

The next thing we really want to track down is the checksum bytes, as we’ll need to be able to generate valid ones based on new payloads in order to start spoofing more interesting payloads, and move beyond read-only.
我们真正想要追踪的下一件事是校验和字节,因为我们需要能够根据新的有效负载生成有效的有效字节,以便开始欺骗更有趣的有效负载,并超越只读。

I setup a conditional break in Ida Pro to break on recvfrom, but only if the command byte is decimal 40 (command byte x28, which is when a unit is given the Move order, based on our above table). This was done by setting the break on memory address x0045cd3f (the first step after recvfrom completes), but only if the debug byte for the buffer that was read is equal to decimal 40. This is shown below:
我在 Ida Pro 中设置了一个条件中断以在 recvfrom 中断,但前提是命令字节是十进制 40(命令字节 x28,这是根据上表为单元分配移动顺序时)。这是通过设置内存地址 x0045cd3f 上的中断(recvfrom 完成后的第一步)来完成的,但前提是读取的缓冲区的调试字节等于十进制 40。如下所示:

Retro Gaming Vulnerability Research: Warcraft 2

Note: The buffer is in a different static location for different packet types, such as a heartbeat vs a gamestate update. You may have to monitor the debugger if you want to break on certain packets. I used Frida to spit out the pointer to the buffer that was argument 1 for the sendto function.

At this point, we can follow the flow of execution one step at a time, and take a lot of notes (shown in blue to the right of each line in Ida):
此时,我们可以一步一步地遵循执行流程,并做很多笔记(在 Ida 中每行右侧以蓝色显示):

Retro Gaming Vulnerability Research: Warcraft 2

Then more notes: 然后更多笔记:

Retro Gaming Vulnerability Research: Warcraft 2

I took even more notes, outside of Ida:
除了艾达之外,我还做了更多的笔记:

Retro Gaming Vulnerability Research: Warcraft 2

I also took yet more notes in my nearby, trusty notepad:
我还在附近可靠的记事本上做了更多笔记:

Retro Gaming Vulnerability Research: Warcraft 2

And this is the reality of reverse engineering, at least for a mere mortal such as myself. It’s a lot of slowly crawling through execution in a debugger, seeing how things work, making notes of that, then confirming it on the next run. Also, accidentally hitting Run instead of Step-Into, and having to start that trace over. (I really should patch out that shortcut…)
这就是逆向工程的现实,至少对于像我这样的凡人来说是这样。在调试器中缓慢地爬行执行,查看工作原理,记下它,然后在下一次运行时确认它。此外,不小心点击了 Run 而不是 Step-Into,并且必须重新开始该跟踪。(我真的应该修补那个捷径……

So let’s take these notes and the above approach, and start to look at the first checksum in a packets (this is in all gamestate packets, found in bytes 8 – 10.) Some packets have more than one checksum, such as chat messages, but this methodology can be repeated to also learn how to generate those.
因此,让我们采用这些笔记和上述方法,开始查看数据包中的第一个校验和(这是在所有游戏状态数据包中,以字节 8 – 10 为单位。某些数据包具有多个校验和,例如聊天消息,但可以重复此方法以学习如何生成这些校验和。

Following the execution after it conditionally breaks on a Move command, we can see that at memory location x0045ba15 the code looks at two bytes from the checksum, by comparing register di to ax, where previously it had generated two bytes and stored them in this register, and stored the bytes from the checksum part of the packet in this second register. It turns out that it does two bytes, then the first byte later, depending on the packet type (a Join Game packet for instance leaves one of the first three checksums equal to x00, due to not knowing required info such as the counter bytes).
在有条件地中断 Move 命令后执行之后,我们可以看到,在内存位置 x0045ba15 处,代码通过比较寄存器 di 和 ax 来查看校验和中的两个字节,以前它生成了两个字节并将它们存储在该寄存器中,并将数据包校验和部分的字节存储在第二个寄存器中。事实证明,它执行两个字节,然后是第一个字节,具体取决于数据包类型(例如,由于不知道计数器字节等所需信息,Join Game 数据包将前三个校验和之一保留为 x00)。

Retro Gaming Vulnerability Research: Warcraft 2

Note: The blue note on line 2 was placed by me to remember what this was later once I realized what it was doing.

So once I knew I was near the checksum generation logic, I was able to recognize the following flow that had common checksum-generation code, such as shifting bits, repeating operations in a loop equal to the number of times based on the length of the packet, then compare the bytes generated. Length-based shifting patterns are common for simple checksums, so this is promising:
因此,一旦我知道我接近校验和生成逻辑,我就能够识别以下具有常见校验和生成代码的流程,例如移动位,根据数据包的长度在等于次数的循环中重复操作,然后比较生成的字节。基于长度的移位模式对于简单的校验和很常见,因此这是很有希望的:

Retro Gaming Vulnerability Research: Warcraft 2

So we just need to see what fields it generates based on, and do those in the reverse-order for any given buffer. If the app logic is very complex (this isn’t really) then I’d stick with doing it all in assembly like above. If it’s somewhat simple like the above, a decompiler can often turn it into easier-to-read C code. Below is Ghidra’s decompilation of this function:
因此,我们只需要查看它基于哪些字段生成,并对任何给定的缓冲区以相反的顺序执行这些字段。如果应用程序逻辑非常复杂(这不是真的),那么我会坚持在汇编中完成所有操作,如上所述。如果它像上面这样简单,反编译器通常可以将其转换为更易于阅读的 C 代码。以下是 Ghidra 对这个函数的反编译:

Retro Gaming Vulnerability Research: Warcraft 2

I re-wrote the checksum instructions in Python, to more easily integrate with wc2shell, partly shown below:
我在 Python 中重写了校验和指令,以便更轻松地与 wc2shell 集成,部分如下所示:

Retro Gaming Vulnerability Research: Warcraft 2

In this case, this code only checks/generates the second and third byte of the first three checksum bytes.
在这种情况下,此代码仅检查/生成前三个校验和字节的第二个和第三个字节。

Some Join Game packets only use only those two (there are two similar packets for joining, command x0a and x09) this is a good packet to start fuzzing on. Additionally it involves an 11 character string for the player name.
一些加入游戏数据包只使用这两个数据包(有两个类似的加入数据包,命令 x0a 和 x09),这是一个很好的数据包,可以开始模糊测试。此外,它还涉及玩家名称的 11 个字符的字符串。

I leave it as an exercise to the reader to extend wc2shell further to add the first checksum byte and attempt to fuzz other traffic. The current version of wc2shell can generate the two checksum bytes shown above and inject traffic (if you uncomment the writes in the Python and JS files).
我把它留给读者进一步扩展 wc2shell 以添加第一个校验和字节并尝试模糊其他流量的练习。wc2shell 的当前版本可以生成上面所示的两个校验和字节并注入流量(如果取消注释 Python 和 JS 文件中的写入)。

Thanks for reading! 感谢您的阅读!

 

原文始发于Caleb Watt:Retro Gaming Vulnerability Research: Warcraft 2

版权声明:admin 发表于 2023年12月21日 上午9:54。
转载请注明:Retro Gaming Vulnerability Research: Warcraft 2 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...