TLDR:
In this post, we'll use Binary Ninja and IDA to disassemble the libGameLogic.so library and we'll do some debugging with gdb.


Watch on YouTube-

Introduction

We've played the Pwn Adventure 3 game a little bit, we've also set up our server to mess around with, and we did some investigations on a high level which led us to the discovery of libGameLogic library. The game dynamically loads this library, and it looks important. So now it's time for us to dig deeper into this and get our hands dirty by opening this file in a disassembler.

Which disassembler?

First off there's Binary Ninja, which is developed by the creators of this game. Unfortunately, the demo version doesn't support 64 bit, so you might have to buy it. Then there's IDA freeware version with 64bit support, which is awesome. There's more like Hopper and radare.

Crack it Open

Now let's open the libGameLogic.so file in Binary Ninja and IDA. It takes a few seconds to analyze the binary. After the analysis is complete, we see a lot of interesting symbol names. The function names look a bit strange, and that's because it's mangled. We need to de-mangle this.

Just by looking at these symbols, we can tell that this is written in C++.

1

We can de-mangle these cryptic strings with the help of Demangler.

2

In the above example, there exists a GameAPI class which has a function named GetSpawnPoints and it accepts character pointer as it's argument.

All these information are available to us because these are exported symbols, and the binary is not stripped. This is great because it's easy for us to explore the functionalities and the capabilities of the game client.

However, before we go on with this, I want to make two points.

  1. This game was part of a CTF that was running for maybe 2 days, so giving out the names
    of these functions is a huge speed boost. This makes it much easier for the players to understand the game and focus on the actual challenges rather than having to spend a lot of time on reversing.
  2. It would be easy to make the challenges harder and time-consuming by just stripping the binary, but a group doing reverse engineering can recover all of the class related information; obviously, not the exact names but close and this just takes a lot of time. Once a group has successfully reverse-engineered the structures, they will be at the point we are right now. So even if it is a bit unrealistic that we have the debug information, it just saves us some time. It doesn't mean that it's impossible to solve otherwise. And sometimes, game developers ship debug builds of the game which are in beta version, and this would be a lot of help for the researchers. So my point is that if you try this on another game, you might not have all the class names included, but that doesn't make it impossible, it just means you need to do a bit of reversing to get to a stage where you understand the game internals.

Anyways, let's go back to the disassembler and keep looking around. There are so many interesting classes and functions to look at. For example, there's this SubmitDLCKey function, which is probably the function responsible for submitting the DLC key that we need to enter when we try to open the chest on a ship.

By the way, the orange colored text in the binary ninja is how the game would call a function and it's from the procedure linkage table. Basically this is a jump to an address contained in the global offset table and the actual implemented code is in the white colored version.

So here in the SubmitDLCKey function from the GameServerConnection class.

4

Moreover, it seems to get a function pointer to something and calls ServerConnection::ServerEnqueue, So it looks like it places some actions into a queue and sends it to the server.

5

If we look at the ServerEnqueue, we see that it acquires a lock to prevent race conditions on the queue and then pushes a new item into that queue.

You can easily spend a couple of hours learning about different functions and objects, and it’s enjoyable.

I won’t let you know about each discovery I make, but here, for example, we have a class for Magmarok, the boss monster in that cave which healed itself. Also, in the function GetDisplayName it references a fixed string as a name which leads us into a section with lots of other strings.

6

There it says "find all of the golden eggs". Interesting.

IDA time

Let’s see how this looks like in IDA. IDA also de-mangles these names. For example, there is a ClientHandler that seems to be responsible for handling chat messages. IDA also can detect these structures, so for example, here it found an actor class.

7

So IDA says it's a "struct" and not a class because classes are primarily just made out of structs. The implementation is essentially just a struct, with some more information like a vtable pointer.

IDA also found some enums, for example, ItemRarity and DamageTypes.

So after just scrolling around and reading a lot of class names and functions, I decided
its time for us to look at this in gdb.

$ sudo gdb -p $(pidof ./PwnAdventure3-Linux-Shipping)

Now in gdb, we can do many things such as listing all the threads, viewing variables, and more. From the disassemblers, we know a lot of interesting functions. For example, the functions related to the Jump action like GameServerConnection::Jump. We can set a breakpoint by doing the following.

pwndbg> break GameServerConnection::Jump(bool)

Now if we jump in the game, we see our breakpoint hit in the debugger. Also, we also see the call stack.

8

We can also use some other cool gdb features, such as ptype which simply means Print Type. So let's print the type of the Player.

pwndbg> ptype Player
type = class Player : public Actor, public IPlayer {
    ...
}

Player is a class that we know exists and gdb prints the whole Player class. We also see that it inherits from Actor and IPlayer. So now we can explore all these classes easily with gdb ptype.

There's one other thing I discovered in the data section of the binary. There is a global variable called GameWorld. Printing the GameWorld shows that this variable is a pointer to a World object.

pwndbg> p Game
$1 = (GameAPI *) 0x8a24bd8
pwndbg> p GameWorld
$2 = (World *) 0x90f4480

Now if we de-reference that pointer, we get the actual World object.

pwndbg> p *GameWorld
$3 = {
    _vptr$World = 0x7f7fe92ff040 <vtable for ClientWorld+16>,
    m_players = std::set with 1 elements = {
        [0] = {
            m_object = 0x907dd68
        }
    }
    ...
}
pwndbg> p GameWorld.m_players
$4 = std::set with 1 elements = {
     [0] = {
         m_object = 0x907dd68
     }
}

m_players shows the list of players and there's only one, which is the character we are playing with. Also, we see a vtable pointer. So I think this object here is not a World object but a ClientWorld object.

pwndbg> p (ClientWorld *)GameWorld
$7 = (ClientWorld *) 0x90f4480
pwndbg> p *(ClientWorld *)GameWorld
9

Basically, this is the same, of course, similar to a World Object, but we also get
a variable m_activePlayer and that variable was only part of ClientWorld.

pwndbg> ptype ClientWorld
type = class ClientWorld : public World {
    private:
        ActorRef<IPlayer> m_activePlayer;
        ...
    ...
}

This is cool and everything, but do you know what we can do with all these?
We can now extract all of these classes from gdb, and create a C++ header file called libgameLogic.h which we can use for creating our first actual "Game Hack". And we'll do that in the next post.

Resources