CSAW'14 - 'saturn' writeup

This writeup was cross-posted from balidani.blogspot.hu. Author: Dániel Bali.

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 (libchallengeresponse.so). 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 libchallengeresponse.so -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 )
  exit(1);
puts("CSAW ChallengeResponseAuthenticationProtocol Flag Storage");
fflush(stdout);
for ( i = 0; i <= 0; ++i )
{
  v0 = read_one_char();
  char_all = v0;
  char_high = v0 & 0xF0;
  switch ( char_high )
  {
    case 0xA0:
      print_challenge(char_all);
      i -= 2;
      break;
    case 0xE0:
      check_response(char_all);
      i -= 2;
      break;
    case 0x80:
      get_flag();
      break;
  }
}
return 0;

Let's play around with these commands. First, let's try to read some challenge values.

python -c 'print "\xA0"' | ./saturn
"abcd"

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
"ABCD"

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!