Decoding an Online Game's Protocol

Table of Contents

Introduction

Massively Multiplayer Online Role Playing Game (MMORPG) or more commonly known as an Online Game are games that allow multiple players to play together or against each other. It uses the network to connect different computers together. There are usually two components in an Online Game, the Client (you) and the Server.

client_server_star

PACKETS
The communication between the server and the client are called packets. These packets have a protocol, making the client and server understand each other. Simply put, protocol is the language of the clients and the server.

Example:

[client] hey i clicked this enemy, attack him.  
[server] ok, enemy 10 damage, you lost 2 life. 
[client] alright, continue attacking.
[server] ok, enemy died. you gained 10 exp.
[client] cool...

The actual packets are just bytes and bytes of streamed data but you get the point.

ENCODED PACKETS
Multiplayer game developers use a vast number of techniques to deter hackers. One of those techniques is called packet encryption. "But why???", you ask. Well, if you knew the protocol, no one can stop you from talking to the server directly and telling it lies.

Example:

[fake_client] hey i leveled up
[server] ok!
[fake_client] hey i leveled up again
[server] ok!
[fake_client] hey i just killed the most powerful enemy
[server] coooool... ok!

Although, the example above is a bit ridiculous, because no self respecting server engineer would trust the client and not do sanity checks you get the point.

WHAT IS THIS
Mostly a documentation of me trying to reverse engineer a certain game's protocol, but you could also say it's a badly written tutorial. :D

Remember that each game is different. You can't treat this as an exact tutorial. This will only demonstrate the methods, approaches, and thinking to complete such task.

This tutorial will assume that you, the "want to cheat" reader, already has:

  • A good understanding of Assembly Code.
  • Experience in Debugging.
  • Deep understanding of Primitive Data Types.
  • Experience in writing Network Code.
  • A lot of time and patience for Trial n Error.

If you think you are not yet ready for this. Do study those related topics and come back.

WHY
There are a few reasons why someone would reverse engineer a game's protocol. Commonly:

  • Educational purposes, a challenge to oneself...
  • Create cheats - specially clientless bots... and this is why you're here. Don't deny it! :P /jk
  • Writing a Server Emulator... this is my reason, seriously, I'm no cheatah. :D

Requirements

Before you can start, you'll need a few tools. I use the following:

  • Brain :D
  • OllyDbg 2.01
  • IDA Pro
  • x64dbg
  • Redox Packet Editor (rPE)
  • Wireshark

It's also important to have good amount familiarity with the game you're trying to reverse engineer.

GAMEGUARDS
Some games have Game Guards (GG) that protects it from being attached with a debugger. If you can't debug the client then you won't be able to decode the protocol.

At this point; you should already have bypassed the GG. Bypassing GGs is outside the scope of this tutorial — I'll probably write a tutorial about that some time.

WINSOCK
If you've written network code for a windows app before, chances are, you already know what Winsock is. No? then read the documentation of Winsock at MSDN.

Profiling

By using packet analyzing tools like WPEPRO, rPE or Wireshark we'll profile the incoming and outgoing packets. We'll try to look for a pattern by repeating a certain action a few times.

hello_chat_packet

In the image above, I repeatedly chatted "hello" in-game 7 times. You would think: "All sent packets should be exactly the same because they are the exact same action with the exact same content..." but you'd be wrong. This is where the packet encryption comes in; each packet will always be different making it harder to profile.

// ACTION_CHAT "hello" | size: 17
11 00 01 2E 82 A8 6A 7F E1 A6 DF 92 6D FF 2F A0 CD
11 00 01 33 05 A9 69 7C 1C A3 DC 91 6A FC 94 97 26
11 00 01 34 84 9A 68 7D DF A0 DD 90 6F C2 FF 55 C9
11 00 01 39 07 A3 6F 82 1A AD DA 97 74 BB E4 89 2B
11 00 01 32 06 A4 6E 83 1D A2 DB 96 71 85 8F 4B C4
11 00 01 37 09 A5 6D 80 18 AF D8 95 6E 86 34 7C 2F
11 00 01 38 08 A6 6C 81 1B AC D9 94 73 B8 5F BE C0

Ah! a pattern...

Considering that most protocols begin with the size of the packet and that 0x11 hex_to_decimal = 17 and size_of_packet = 17. It's safe to assume that the first byte is actually the size of the packet.

 struct pkt {
     uint8_t size;
     char    unk1[2]; // plain     | unknown1
     char    unk2[n]; // encrypted | unknown2
 };

We can guess that unk1 is not encrypted because we already know that encrypted packets will always be different for each iteration.

GUESS UNK1
Lets try to figure out what unk1 really is. My best guess would be: PACKET_ID.

If unk1 is really the PACKET_ID then it should change if I do something other than ACTION_CHAT.

// ACTION_WALK | size: 14
0E 00 01 88 82 A4 F4 14 FB 47 2A FE 9D B5
0E 00 01 7D 81 A1 5C 6C 48 D4 78 E8 D8 D7
0E 00 01 76 80 52 65 65 FA 3A B9 00 2D 1D
0E 00 01 7B 7F 9F 61 69 54 E0 B5 EE 79 DE

0x0E hex_to_decimal = 14 and size_of_packet = 14 confirms that the first byte is the size. But, it looks like unk1 remained the same. This throws our PACKET_ID theory out the window.

GUESS UNK1 /again
Lets try again. Maybe it's the MAP_ID. Lets try to move to another map and check if unk1 changes.

// ACTION_CHAT "whatsup" | size: 19
// on a different map from the previous packets
13 00 03 88 38 B6 B1 B3 26 72 B3 E2 14 7 8A 3E D 1E 09 AA

Ah! it changed... To confirm, lets move to another map if unk1 changes again.

// ACTION_CHAT "ketchup" | size: 19
// on a different map from the previous packets
13 00 04 4E 22 88 C7 A9 23 6C B6 EC 02 4A B1 71 1B 2C 05

Great! confirmed... Looks like our guess this time was right. The 3rd byte is the map where you did the action. But we still have 1 byte of unk1 remaining. Lets fix that by considering that the packet size could be uint16_t, something that makes more sense than uint8_t. Lets update our protocol:

 struct pkt {
     uint16_t size;
     uint8_t  map;
     char     encoded_data[n];
 };

Debugging

Let's put ourselves in the shoes of the developer of this game we're trying to reverse engineer. At what point would I encrypt/encode my packet? Let's think:

  • build_packet() => encrypt() => send()
  • build_packet() => encrypt() => append_size() => send()
  • build_packet() => encrypt() => append_size() => insert_map() => send()

You can continue thinking of other scenarios but I'll stop with the 3rd option because I feel like its the most probable one.

You'll also notice that all encrypt() are done right before sending or pretty close to it. That gives us an idea on where to start looking: 2 or 3 functions before send() is called.

REVERSING THE ENCRYPTION ROUTINE