Facebook CTF 2019’s homework_assignment_1337 was an interesting challenge which forced me to learn Apache Thrift, a language agnostic RPC client-server sort of language. The actual explanation from the Apache Thrift website is:
The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.
This challenge forced me to learn a bit of it, so here we go.
homework_assignment_1337.tar.gz
The given ping.thrift
file looks like the following:
ping.thrift
1 | /* |
There’s quite a bit of comments, which are there to teach Thrift. The actual meat of the code isn’t very… meaty. In fact, it’s rather straightforward. The idea is that the the problem wants me to call the ping
method on the server. The ping
method takes a single argument, a special struct Ping
which has three fields: proto
, host
, and data
. All of this is abstract, however, as Thrift compiles servers and clients to other major programming langauges.
I had initially installed Thrift on my VM by downloading the source code, compiling, building, and installing Thrift 0.12.0. This took a lot longer than expected but I found out later that Thrift can simply be installed via a simple:
1 | $ sudo apt install python-thrift thrift-compiler |
After installing Thrift, I generated the Python server/client code by running:
1 | $ thrift --gen py ping.thrift |
Note that I could have generated the server/client code in another language by changing py
. I also believe that you can generate code for multiple languages at once.
After running the thrift python generation, a new folder was created in the current directory:
1 | gen-py |
So it looks like I got a lot of files with all of the datatypes defined and the data transport defined in libraries for me to call. Looking through most of the files gave me some insight. The most important file was ttypes.py
, as it defined the datatypes, objects, and how to build them. For example, I was able to figure out how to build a Ping()
object.
Drawing off of example tutorials on Thrift, I wrote up a quick prototype which would call the ping
function on the remote side:
1 | import sys, glob |
Running this Python client script gave me the following output:
1 | Pong(code=0, data='HTTP/1.1 301 Moved Permanently\r\n') |
Hmm… That seems.. kind of right? So it seems like this ping
function doesn’t actually do the ping
that I thought of initially, which is the ICMP ping. I was initially pretty confused at why there was a data
field in the Ping
struct. Also, calling ping
seemed to continually throw exceptions if I didn’t provide a port number, which was also confusing because ICMP has no port!
In effect, it looks like the ping
function is more like a “send a packet with this data to this host and this port” function!
Looking back at the original ping.thrift
file, I see that there’s a function pingdebug
:
1 | // You do not have to call this method as part of your homework. |
How snarky! In fact, I feel like this is very common, where there is some sort of function or piece of code which I’m not supposed to call, but in fact ends up being the solution. I’ve written a challenge myself which has this level of snarkiness. Presumably, I’ll have to call the pingdebug
function to get the flag.
With what I know so far, this seems fairly straightforward, since the ping
function can be used to send a pingdebug
RPC to localhost:9090
. Before I can do this, I need to know what the data
would need to be for a pingdebug
RPC. I can figure this out by calling this function directly from my client. I assume that it won’t succeed but I can take a tcpdump of the data and grab the raw hex.
I modified the middle portion of my Thrift python client code:
1 | dbg = ttypes.Debug(dummy=1337) |
I used tcpdump
to monitor what this data looked like so I could replicate it. Here’s the packet we care about in Wireshark:
The highlighted portion is the contents of the pingdebug
packet, which I care about to send through ping
. I grab the hex representation of this data and set it as ping.data
.
Reformatting the data I send through:
1 | ping.host = 'localhost:9090' |
This yields the flag as the Pong
response:
1 | Pong(code=0, data='\x80\x01\x00\x02\x00\x00\x00\tpingdebug\x00\x00\x00\x00\x0c\x00\x00\x0f\x00\x01\x0c\x00\x00\x00\x02\x08\x00\x01\x00\x00\x00\x01\x0b\x00\x02\x00\x00\x00\x0elocalhost:9090\x0b\x00\x03\x00\x00\x00\x1f"fb{congr@ts_you_w1n_the_g@me}"\x00\x08\x00\x01\x00\x00\x00\x02\x0b\x00\x02\x00\x00\x00\x0elocalhost:9090\x0b\x00\x03\x00\x00\x00!\x80\x01\x00\x01\x00\x00\x00\tpingdebug\x00\x00\x00\x00\x0c\x00\x01\x08\x00\x01\x00\x00\x059\x00\x00\x00\x00\x00') |