September 19, 2018

CSAW Quals 2018 - RE 400 - Not Protobuf

I’m in this company’s network and I’ve MITM’d this weird protocol between a dev client and server, but I can’t figure out how it works. Connect to reversing.chal.csaw.io:9002 and I’ll send the client traffic to you. Forward it on to the dev server at reversing.chal.csaw.io:9001 to figure out what’s going on. Once you’re ready, hit up the prod server at reversing.chal.csaw.io:9000 which should have a flag for you.

Solved with jack2 and Plailect!

This was an interesting reverse engineering challenge we solved during the qualification round for CSAW CTF this year. Only one other team, REdefined Behavior, solved the challenge during the 48 hours given. We are given a number of endpoints to connect to – one which provides a “development client”, and two which provide “development/production” versions of the server this client is intended to talk to. The challenge description implies that our final goal is to reverse engineer the network protocol well enough to communicate with the prod service.

We began exploring the activity of the service by writing a script that would connect the development services and record a conversation between them (acting as a man-in-the-middle). Simply looking at this traffic doesn’t reveal much information, though – we can see two plaintext messages (a hello message, and an acknowledgement) before the client initiates TLS.

The one thing we are able to gather from these messages is a bit of the message structure. It appears that each message sent begins with 4 random bytes, and then ends with those same random bytes. For example, the hello message (0x41ca) could be sent as 0x0102030441ca04030201 and the acknowledgement message (0x0aca) could be encoded 0x414243440aca44434241.

We then modified our script to man-in-the-middle the TLS traffic. This was fairly easy to do, as the client/servers do not require a valid certificate to function. Using this enhanced script, we captured a number of conversations between the client and the servers.

Forensing of the now-decrypted message traffic allowed us to determine the flow of the application traffic:

  1. The client connects to the server and sends a “hello” message.
  2. The server responds with an acknowledgement.
  3. TLS is initiated on the connection between the client/server.
  4. The server sends a “hello message.”
  5. The client responds with an acknowledgement.
  6. The client sends a message that appears to contain a username/password pair.
  7. The server responds with an acknowledgement on success, or an error message on failure.
  8. The client sends some sort of request.
  9. The server sends a response which is a single null byte followed by some portion of the end of the request.
  10. Repeat 89 until the client has finished.
  11. The client sends a message 0xbbca and disconnects.

Further investigation of the traffic (and modification of traffic between server/client) allowed us to serialize our own messages.

Each message begins with a one-byte message type. The official client uses the following message types:

  • 0x00: Value at location
  • 0x0a: Acknowledgement
  • 0x20: Login request
  • 0x41: Hello
  • 0x78: Set value location
  • 0xbb: “Bye-bye”
  • 0xee: Sent by the server on error.

The acknowledgement, hello, and goodbye messages all take no arguments, which is serialized as 0xca. We found that strings are serialized as a space (0x20) followed by a two byte length prefix, followed by the string itself. Any integers are serialized with a type code of ‘0’ followed by a Base 128 varint encoding of the integer.

Most interestingly, though, are the list/tuple tuples (beginning with 0x00 and 0x01 respectively). These types are serialized with their type code, followed by a series of bytes for the length for the serialized form of each list element. A null byte is used to indicate the end of the list. Following this are the serialized forms of each element in order.

For example, the steps to serialize the tuple ("Hello", "serialization!") would be:

  1. Serialize the first element of the tuple (0x20000548656c6c6f)
  2. Serialize the second element of the tuple (0x20000e73657269616c697a6174696f6e21)
  3. Serialize the tuple header (0x01081100)
  4. Append the serialized elements to the end of the header to produce the final serialized value.

Now that we are able to serialize our own messages, we need to figure out what we need to send the server. Trying all 256 possible message types finds us an interesting message type (‘f’) which tells us to repeat the request with the value at a given coordinate pair, which is randomized on each connection to the server.

At this point, we were stuck until the third hint was released:

hint 3: “for the final stage, the server’s prompt is just trying to confirm that the client has the data that an official client would have. It is referring to what the client sends.”

With this hint, we figured out that each time the dev client connected to the server, it would set the value of 10 different coordinates. We then wrote a script to gather the values sent by the client for each location on the grid. After gathering a large enough set of values, we retried sending a flag request until the server requested the value of a location we knew the value of. The server then responded with a message containing the flag!