In this blog post, we will look at the network traffic generated between the client and the PwnAdventure3 game server in order to reverse the protocol and understand its content. For this, we will use Wireshark and some methodology.
Here is a summary of the methodology I followed to reverse the custom network protocol used by Pwn Adventure 3. This methodology is most likely not suitable for any network protocol, but I hope it will at least give you an idea of the process.
Usually, when you are about to reverse a network protocol, it is easier if you have an idea of what the network should contain. Raising some assumptions help the analyst to narrow its research to a specific set of data, e.g. players position, health, inventory list, etc. Therefore, before starting with the reversing part, we need to draw a list of elements that we expect in the network communication between the client and the server. For this, we should identify the „need to know“ information required by the different actors in order to operate the game properly. For instance, in order to see my character in the game, other users need to know my position. Based on that statement, I raise the following assumption:
- my position is sent – regularly – to the server
- I receive – regularly – the position from other players
If my inventory is persistent through session, the list of items should be stored somewhere in a retrievable form. Maybe from the server maybe locally.
- Username and password (or hash) used during authentication
- My characters (name and colours)
- My position when I join the game
- Positions of elements (me, other players, enemies, NPC, etc) on at least 3 axis
- Attack events with the weapon used and the direction
- PvP status
- Loot items
- My inventory
Note that some elements might be stored/process locally, in which case, it would be easier to „hack“ the client. For instance, let say my inventory is actually stored on my local file system (and not verified on the server side), it would then be easy for me to edit that file and add lots of very expensive items (as long as I know the format).
Local vs Online
In order to determine whether a value is stored locally, we will use a technique from the malware analysis realm: a snapshot diff. For Windows user, you can use RegShot.
Regshot is an open-source (LGPL) registry compare utility that allows you to quickly take a snapshot of your registry and then compare it with a second one – done after doing system changes or installing a new software product.
RegShot can also build a snapshot of a the file system. Another useful tool is Process Monitor, which does a real-time monitoring of the file system, Registry and process/thread activity. While Process Monitor is usually more accurate, it also produce lots of noise due to the normal Windows behaviour. However, since we are on Linux, we will use find and diff to create snapshots and compare them.
We installed Pwn Adventure 3 on a freshly deployed Ubuntu system. We then open the Pwn Adventure 3 without registering or authenticating. We then closed the application and create our first snapshot with with the following command:
find /path/to/PwnAdventure3 > snapshot1 find /path/to/PwnAdventure3 -print0 | xargs -0 md5sum | tee md5sum.txt
We then re-open Pwn Adventure 3, create a new account, create a character and logged in. We then acquire the Great Fire Ball, then we try to loot at least one item. After that, we closed Pwn Adventure 3 and create our second snapshot with the this command:
find /path/to/PwnAdventure3 > snapshot2
We then compare the snapshot and analyse the result:
diff -crB snapshot1 snapshot2 md5sum -c md5sum.txt 2> /dev/null | grep -I 'FAIL'
Both comparisons did not output any file. It seem that everything is stored on the server side.
Now that we know that no user-related information are stored locally, let’s have a look at the network traffic. Here we just want to have an overview of the communication. We first want to know the format used for the communication (e.g. HTTP, XML, binary, JSON, etc) and try to identify some pattern and frequency. For instance, we assume that we will have packets send frequently to update our location to the server. We should also have additional packets whenever we fire, or get attacked.
Let see if our assumptions are correct. Open Wireshark and start playing Pwn Adventure 3.
In order to remove the noise, I used the following filter (192.168.1.238 being the IP address of our Master and Game server):
ip.src == 192.168.1.238 || ip.dst == 192.168.1.238
I can also append to the filter “ && data.data“ so I only get packets that contain data. I added the Destination Port and the Data as column so I can quickly identify to which service is the packet sent (i.e. Master server or Game server) since both are located on the same IP.
The first thing to note is that the data is binary. We can also see that most of the connections go to the Game server (port 3000) but some also go to the Master server (port 3333). As expected, we can see frequent packets sent to the server (every 0.8 second), then sometimes, we can see bigger packet, which might correspond to attacks from bears or me shooting at them, but from now, we can’t say much more.
Now that we have the game and Wireshark set up, let’s dig deeper and dissect those packets in order to make sense out of it. For this, based on our assumptions, we will try to isolate an element (our location for instance), then we will try to alter only this value and see in the packets what has change and what remains the same. Maybe it’s not quite clear for now, but I hope the examples later will make sense to you.
Before we start with our tests, let’s discuss about the concept of delimiter. A delimiter is what separate a data from another. In XML for instance, the delimiter are the tags (e.g. <data> and </data>). However, in binary, it is not that obvious. Usually, there are three kind of delimiters. The first one, we know exactly the size and the format of the data. Therefore, there is no delimiter within the data. The server and client both know the format, so they forge the packet accordingly. However, this solution cannot contain values with variable length. The second solution is to use a sequence of specific characters (for instance just a NULL character, or another arbitrary value such as 0x13 0x37). This is what XML is doing, but the delimiter does not have to be in the printable ASCII range. And finally, the third one is delimiting based on the length given prior to the data in the packet. For instance, if I want to send „Hello“, the packet will contains the value 0x05 (the length of „Hello“), then 0x48 0x65 0x6c 0x6c 0x65 (hexadecimal equivalent of the string „Hello“ in ASCII).
Now that we know what I mean by delimiter, let’s start our experiments. For this first test, I will try to identify where and how is my location sent to the server. Based on the previous analysis and assumptions, I think my location is send in those packets sent at regular interval (0.8s). The data is quite small (22 bytes) so that should not be too complicated to dissect the packet and spot the location (if present).
My location should contains three parameters according to the three axis (x, y and z) from the three dimensions. For this first test, I would like to alter only one parameter. Since I don’t know the references for the x and y axis, I will just jump, as I hope that the gravity is aligned to the z axis. Let’s start the capture with Wireshark and jump in Pwn Adventure 3. Since the jump last more than 0.8 second, I should be able to see the changes in the captured packets.
This is the value send whenever I do not move:
And this is the values whenever I keep jumping:
We can clearly see the difference located at between the 20th and 27th character:
[????????????????????] [ZZZZZZ] [??????????????????]
However, bear in mind that we just jump, the world of Pwnie Island is big and the Z axis is most likely represented on a bigger variable than 3 bytes. However, we still don’t know the endianness.
Now let’s move a few steps into Pwnie Island. We will most likely alter our position on the x and y axis (and even z since the ground is not perfectly flat) but at least we will be able to identify them. Before my first step, I was still sending this packet (I masked the z axis for clarity):
Then after the first step, I was sending this packet:
And finally, after my second step, I was sending this packet:
Now if we highlight the bytes that changed after each steps, we have the following:
[????????????????????] [ZZZZZZ] [??????????????????]
Here again, we only did small steps, and considering the size of the map, we assume that the x and y parameters should be stored in a bigger variable than 3 bytes. However, we can see that the two elements that changes are actually almost next to each other. Since the x and y parameter should not overflow over each other, we can assume that the variable are stored in a 4 bytes variable in little-endian:
[????] [XXXXXXXX] [YYYYYYYY] [ZZZZZZZZ] [????????????????]
Now let’s think about the remaining unknown values that are related to my character and should be send regularly. What about the direction where I look at. In order for the other player to display my character in the game looking at the right direction, I need to send to the server where I look at. Usually, video games use the three aircraft principal axes, i.e. pitch, nose and roll. Here in this test, I will logout then login to make sure the direction is reset. Then I will stay stationary, but look 90° right, then wait, then repeat the steps until I reach my initial direction.
In this case, I will use the right arrow instead of the mouse to make sure I do not alter the up/down axis. Here are the values I got at each steps (pause):
- 6d76XXXXXXXXYYYYYYYYZZZZZZZZ0000000000000000 (initial position)
- 6d76XXXXXXXXYYYYYYYYZZZZZZZZ0000bb4e00000000 (I turned 1/4 right)
- 6d76XXXXXXXXYYYYYYYYZZZZZZZZ000070a400000000 (I turned another 1/4 right)
- 6d76XXXXXXXXYYYYYYYYZZZZZZZZ0000102500000000 (I turned another 1/4 right)
Here we can clearly see that the yaw axis is locate between the 32nd and 37th hex. Now let’s do the same test for the pitch axis. Here, it is a bit more complicate because we have to move on the axis with the mouse, so we are less accurate but let see the results:
- 6d76XXXXXXXXYYYYYYYYZZZZZZZZ0000LLLL00000000 (reset)
- 6d76XXXXXXXXYYYYYYYYZZZZZZZZ0040LLLL69f80000 (looking up)
- 6d76XXXXXXXXYYYYYYYYZZZZZZZZ00c0LLLL2ffa0000 (looking down)
We now can locate two new blocks. One starting at an offset of 14 bytes and the second at an offset of 18 bytes. The second block seems to vary a lot, so I assume this should be the pitch. Therefore, the first block that varies less might be the roll.
[????] [XXXXXXXX] [YYYYYYYY] [ZZZZZZZZ] [RRRR] [YYYY] [PPPP] [????]
After further movement tests, I also noticed that when I’m moving/strafing, the last two bytes changes:
- Moving forward = 7f00
- Moving backward = 8100
- Strafing left = 007f
- Strafing right = 0081
We finally have the meaning of our last 2 bytes:
[????] [XXXXXXXX] [YYYYYYYY] [ZZZZZZZZ] [UUUU] [LLLL] [UUUU] [FF] [SS]
- X = My position on the x axis on the map
- Y = My position on the y axis on the map
- Z = My position on the z axis on the map
- R = My direction on the roll axis
- Y = My direction on the yaw axis
- P = My direction on the pitch axis
- F = The direction where I go (forward or backward)
- S = The direction where I strafe (left or right)
The unknown two bytes at the beginning did not change despite all the weird moves I could do in the game. Those could be an identified to tell the server the content of the packet, i.e. 6d76 = location, direction and movement of a user. Note that 0x6D 0x76 is „mv“ in ascii.
So far, we reversed the network solely on the analysis of the network traffic. We could also reverse the client (and server if available) binary to look at how the network packets are crafted. This could give us additional information about the structure of the packets.
I hope that you have now a better idea of how to reverse a custom network protocol. The more you practice the more you will be able to guess accurate assumptions, which will help you to speed up the reversing process. Always thing about the minimum required information for the application to work as such, then put yourself in the developer’s shoes and think how you would implement it. Usually the actual implementation is not that far from that.
In the next chapter, I will give you the different packet structure I reversed, however, I will not go that deep as you now know the methodology.
A wireshark plug-in to reverse engineer PwnAdventure 3 traffic (Eric – maetrics – Gragsone)
Blog series: Introduction – Reverse Engineering Network Protocol – Pwn Adventure 3 Network Protocol – Building a Wireshark Parser – Asynchronous Proxy in Python – Intercepting Packets – Reverse Engineering Binary – Patching Binary – Hooking shared library