Static Analysis of OJ's Police Quest

Posted on Mon 03 December 2018 in Reverse

Roughly one week ago (end of November 2018), OJ published a CTF challenge, the goal (rather simple) is to exploit this binary. I'm trying to write those article while I reverse/exploit this game. In this first article I will focus on static analysis to gather information that will be usefull later to find and write an exploit.

Static analysis

I always like to spend a lot (too much ?) time on static analysis, try to guess how the code would behave when run. In this binary, symbols were not removed and it's hence much easier for us to find the relevant part of the source code.

Rooms

The setup function creates the links between the various rooms of the game we can already spot six rooms (study, kitchen, bathroon, hall, library and bar) from the function's body. Using the game's symbols (use_* functions), we can safely deduce that there are five items available in this game:

  • Revolver

  • Bullet

  • Tissue

  • Target

  • Magnifying glass

After a deeper look to the 'GET' action (action_get), we are able to spot the link between an item and a room. A room can have a single item type, but there can be more than one item in the room. We are now able to write a type for a room in our game:

room type

We can now apply this type to the list of rooms at offset 0x02051C0 (all_rooms):

List of the rooms

As said, a room can have an item, and when it does, the pointer to the item datastructure is stored in the field called room_item, and the item structure looks like:

Room's item data structure

It was actually not that straightforward to re-create this, but my goal here is not to give an IDA tutorial, but to describe the steps I performed looking at an unkown binary. The first two fields are explicit, the count is the number of this item in this room followed by a function pointer that points to one of the create_* function, used to create an item that can be use by the user.

Creating an item

As shown previously, there is a link between a room and an item. From the rooms' list we can see that the first item we are interested in is located in the study room: the Revolver. In the setup function (or playing around in the game), we see that the study room is connected to the north of the hall room (the starting room).

When using the GET action in a room, many checks are performed, the first one checks that the room's item pointer is not null, compares the name given by the user to the item's one, checks that the item's count is not 0. Then it tries to find the item in the user's stuff (we will come back to that later).

There are three possible outcomes to this last check:

  • the player already has the object with a count that is not 0 (he/she did not use the item yet), then the object can not be picked.

  • the count is 0, then the user once had this object, but used it, and the item can be picked up again (the counter is simply incremented).

  • the user never had this objet: this is the first time the user uses the GET action on this item's type. The game will create a new datastructure representing this item (which is not the same as the datastructure used by the rooms to keep track of their items).

To create an new item, the user must give it a name, the function then creates a hash (more info that later), tries to find an item with this hash in the user's stuff (I did not really figure the usage of the hash yet, so I can't explain why). If none is found, the item_create_fn function pointer that can be found in the item_room structure is called.

In order to be added to the user's stuff, the item is created on the heap (which can be interesting as, don't get me wrong, we are still trying to exploit this binary) by the item_create_fn function of the room_item structure.

room type

User's stuff

Before we dig into the create_* function, I need to describe a bit the stuff symbol that can be found in this binary.

Found at address 0x0205320, this symbol represent the user's stuff. Surprisingly, it is a single 64bits pointer. In fact in this game, the stuff is a linked list of items, the stuff symbols simply points to the first item in the list, and each one points to the next (actually the list is reverse, the most recent item is the first one), we can deduce that from the end of the action_get:

add item to stuff

In this picture, IDA shows revolver.prev_item but it could actually be any of the item, they all share common fields in their first 0x18 bytes. The revolver structure that would be inserted into the linked list when the user picks the revolver up looks like:

item revolver structure

In this structure we find a pointer to the next/previous item in the list, a pointer to the room's item that was picked by the user, a pointer to the room's name and the item's hash (the one that was computed from the user's input). We can also find data related to a specific item, in this case, the serial number of the revolver as well as a boolean is_loaded that will be set to true only if we load a bullet into our revolver.

The last field of the structure is hidden_string this is filled for each item by the corresponding create_* function and does mostly nothing (I probably missed something here, but I can't find what yet).

Now that we have a rough overview of the user's stuff mechanism, let's focus on the functions that create those items.

create_* functions

As said previously, the goal of those functions is to create and fill an item when it's picked by the player. All of those function do at least a heap allocation using calloc and then, if needed, will set a few item specific fields. We find the hidden_string in all the item except the magnifying glass. The bullet and revolver item have an extra field storing the serial number. This serial number is a hex digit of length 18 (including the 0x prefix) that will be prompted to the player.

Conclusion

To conclude this part on user's item: each room can be linked to an item_type, when the user gets the item for the first time, a new object is created on the heap and added to the player's stuff, this item is a node of a linked list and contains information about its type and some information that I could not make any sense of yet.

While reading through this part of the binary, I did not find any vulnerability: bounds seems to be checks, safe functions are used, we can control many parameters. On the other hand, having some data on the heap open the door to a whole new range of exploit if something goes wrong: heap exploits.

Using an item

The action_use is the common entry point to any item usage, it goes through the list of available items and compares each name with the one given by the player (USE Revolver for instance). If such an item exists, the player must give (inside the get_matched_object function) the ID of the item. This is basically the value of the secret hash computed from the name given during the action_get. The get_match_object function then asks for the secret used to generate the hash, to make sure the player knows the original value that was used during the item's creation. The function then goes through the user's stuff and return the matching object (based on the hash). If the item exists, the corresponding use_item_fn is called. A full execution of this scenario is described below:

> GET Revolver

What secret name shall we give this Revolver? > test
This Revolver has no serial number. What serial number should we give it? > 0x1234567891234567
You sureptitiously acquire a single Revolver, we've given it the secret hash of 0xCC9EE3AB.

You are currently standing in the Study. To the SOUTH lies the Hall.

What do you want to do?
LOOK MOVE USE GET INVENTORY

> USE Revolver

What's the ID of the Revolver you want to use? > 0xCC9EE3AB
What's the secret of the Revolver that matches the ID? > test
You can't shoot a gun that hasn't got any bullets in it. Numpty.

The following picture shows where the item's specific use function is called, it's actually computed from the counter of the matching item, and, using some arithmetic, moved to the correct address in the all_items array.

use item call