Before reading the writeup you may want to attemp to solve the challenge youself. I wrote a replacement of the server in Python so you can still try to solve this challenge even if the original server is not live anymore.
Warning: if you want to solve the challenge do not cheat by reading the gist code! Just download both files and run python keyserver.py and then python fuckpyjails.py.
How the hanoiati team solved it
Trying to connect to the specified address you get something that looks like a Python prompt:
If you insert a python expression it seems to just fail. But if we try to input some python statements this is what we get:
From this we gain a couple of interesting informations:
Our input is passed to eval
It seems that we have to get a key in order to make the if statement succeed and hopefully give us the flag
The source code of the jail is at /home/ctf/fuckpyjails.py
The print failed because in Python 2.x print is a statement and eval does not allow execution of arbitrary Python code, but only expressions. From this we can conclude that the jail is not running on Python3.
The most obvious thing to try of course doesn’t work:
Now this is strange! We should find a way to see the output of get_key to understand what is going on.
Ok, the server says that we are stupid :S
Let’s see if can understand what is happening by looking at the source code of the jail:
Great we have the code!
The good news is that we finally understand why get_key was insulting us: it connects to a socket and returns the correct value only the first time.
The really bad news is that we don’t have to make just the if succeed, but we have to find the actual value of get_key the first time that it was executed!
We have to get that value from the Python stack. It doesn’t seem too hard to do since there are some pretty powerful modules like inspect and gc.
Before trying to solve the actual challenge though, the first thing I tried to do was getting a full interpreter, because being allowed to enter only one-line expressions gets frustrating pretty quickly.
After a few tries with the code module I ended up with this:
I had to patch the runcode method to flush the output, otherwise I wouldn’t receive anything from the server until I disconnect.
The unobfuscated code is this:
Solving the challenge
Now that we can execute any Python code, we can start having some fun (at least we thought).
This is the things we tried that dind’t work:
Looking for the flag in the file system with os.listdir.
Spawning other processes with os.system.
Using the gc module to find a reference to the flag.
The main problem is that the flag is not assigned to any variable, this is why the last two methods dind’t work!
The only thing left to do was to access the raw memory of the Python process using libc (through ctypes) and look there.
Ok but where is the flag? We allocated an other string, got its memory address and started looking around. After a few tries here it is:
And here is the human readable version:
An other, more elaborate, solution
Here I present an other solution, inspired by this writeup that does not involve using libc, but requires that you know a little bit of how CPython works internally.
Instead of just dumping the process memory and searching for the flag, like our team did, you can directly get the solution if you know exactly where to look for.
Let’s get a full Python shell using the code I showed above, and try looking at the stack:
The last one seems the one we are looking for. We can get the corresponding frame object and browse it’s attributes
You can look at the documentation of the frame object to understand what those attributes are.:
Unfortunately our flag is not there since it has not be assigned to a variable.
But it’s not over jet. We can look at the structure of the underlying C frame object (i.e. PyFrameObject). As a quick search on github reveals frameobject.h is the file that contains the definition:
As we suspected some of the attributes of the PyFrameObject are not exposed in Python. We now have to solutions: