Contents

Grand Chase Primer

WIP!
This article is still a work in progress - stay tuned for more updates.

Introduction

Grand Chase is a 2D side-scrolling online game developed by the South Korean studio KOG. It was launched in Brazil in 2005 by Level Up! Games, the same publisher that also distributed Gravity’s Ragnarok Online (another time sink of mine), as well as GunZ: The Duel (around 2006) and Perfect World (late 2000s).

All of these games were hugely popular in Brazil during my early teens, and I probably spent more time than I should’ve playing them instead of doing homework or other boring stuff.

Grand Chase, however, holds a special place in my heart. I have fond memories of the time I spent playing it with friends, and I’ve also always had a soft spot for side-scrolling games.

Grande Chase's first mission

That being said, another hobby of mine was trying to hack the game whenever I could - oops. But that is a story for another time.

Games in general, and specially online ones, have always captivated me and made me curious about how they work. I’ve put a lot of time and effort into trying to understand their inner workings (I even ran a World of Warcraft private server at one point), and with Grand Chase, it wasn’t any different.

So, on this page, I’ll be writing about the things I’ve learned by digging into the available material on the game. Hope you find it interesting!

Tip
Although this article focuses mainly on the Season 1 version (my favorite one!), most of this content can be applied to the other seasons. The core of the game, especially the network stuff, is pretty much the same.

Networking

In this section, we’ll talk about how the game’s networking works - the packet structure, security protocols, compression, and so on.

Below, you can see the overall structure of the game’s packets:

To help us understand how everything works together, we’ll separate this structure into three parts: the packet size, the security layer, and the payload layer.

Note
Aside from the decrypted payload data’s content, all the fields here are little-endian.

Packet Size

As the name implies, this field tells us the total size (in bytes) of the received packet, including the size of this field - simple as that. Here’s an example:

4A 00

That value means that we have exactly 74 bytes in our packet.

Security Layer

This layer is built upon a variation of the security protocol called Encapsulating Security Payload (RFC2406), from the IPsec suite (RFC2401), and consists of the Security Parameters Index, Sequence Number, Initialization Vector, and Integrity Check Value fields.

This protocol provides authentication, data integrity, and confidentiality to the data being transmitted, in addition to protection against replay attacks (more on that later).

Security Association

A Security Association (SA) defines a one-way secure connection between two parties (e.g., a game client and server), specifying how they encrypt and authenticate traffic. Each SA includes parameters like encryption algorithms, keys and a Securiry Parameter Index (SPI) to identify it. Those parameters are described individually below.

Because SAs are one-directional, two are needed for full communication: one for outgoing traffic and another for incoming. The server tracks SAs per client, using the client’s IP address and SPI to select the right one.

Security Parameters Index (SPI)

These two bytes represent an arbitrary value used (together with the destination IP address) to identify the security association that should be used by the packet receiver (client or server).

Note
Both the client and the server use different security associations, so an SPI is defined for each of them. Because of this, the packets sent by either party will contain different SPIs in their security layers.

For a new connection, this value will always refer to the default security association, since no key exchange has taken place yet. Examples:

// Default SPI
00 00

// Arbitrary value
13 4F

// Maximum value
FF FF
Trivia
In the original server code, there was a bug where, if there were no more slots available for a new SPI, the code would hang in an infinite loop. However, this was an edge case - you’d need 65,534 players connected to fill up all the slots, which would probably never happen in the practice.

Sequence Number

This is a counter that increments by 1 for every packet sent and is used to protect against replay attacks. Each security association should have its own separate counter.

Used together with a mask, an algorithm performs a check to see wether the packet has already been received or not.

The sequence number cannot be zero, as it would be considered invalid and would cause the communication to be closed. Example:

// Valid sequence number
03 00 00 00

Initialization Vector (IV)

An Initialization Vector is a crucial component in encryption that introduces randomness to secure data. During encryption, the IV scrambles the input, ensuring that identical data produces unique ciphertext every time - even when encrypted with the same key. This encrypted output, along with the IV itself, is then transmitted to enable proper decryption by the server.

In Grand Chase, the IV follows a fixed 8-byte length (as dictated by the cryptographic algorithm, which we’ll detail later). However, the implementation shows an unusual pattern where all bytes share the same value:

// Example of Initialization Vector
23 23 23 23 23 23 23 23
Trivia
While IVs should ideally be unique and random to prevent vulnerabilities, the original server code used the same value for all the bytes each time a vector is generated, and it doesn’t guarantee that it will never be repeated. This approach can lead to security vulnerabilities.

Integrity Check Value (ICV)

The Integrity Check Value (ICV) acts as a checksum to verify that packet hasn’t been tampered with. In Grand Chase, it consists of the last 10 bytes of the packet and is calculated using MD5-HMAC (Hash-based Message Authentication Code).

// Example of Integrity Check Value
0C A6 9E B2 34 FD D3 1A C3 C7

The ICV is calculated using the packet bytes from the first byte after the size field to the last byte of the payload data, along with an 8-byte authentication key defined at the start of the communication (this exchange will be described later).

Authentication Key

The key used for ICV calculation is:

C0 D3 BD C3 B7 CE B8 B8
Trivia
While an MD5-HMAC normally produces a 16-byte output, Grand Chase truncates it to 10 bytes.
Trivia
In the original code, the Authentication Key was initialized with the string “임시로만드는키” (which, when truncated to 8 bytes, produces the byte array shown above). According to ChatGPT, this roughly translates to “A temporary key” 😁

Payload Layer

TODO